From 541bb378ddece2eab135a8066a16994e94436dea Mon Sep 17 00:00:00 2001
From: Giulio Cesare Solaroli How to add a new card or a direct login to an existing card for this website: Applies drag handles to an element to make it resizable. The drag handles are inserted into the element
+ * and positioned absolute. Some elements, such as a textarea or image, don't support this. To overcome that, you can wrap
+ * the textarea in a div and set "resizeChild" to true (or the id of the textarea), or set wrap:true in your config and
+ * the element will be wrapped for you automatically.
+ * To hide a particular handle, set it's display to none in CSS, or through script: Usage: var myAnim = new YAHOO.util.Anim(el, { width: { from: 10, to: 100 } }, 1, YAHOO.util.Easing.easeOut); Usage: Usage: Usage: To construct the placeholder for the calendar widget, the code is as
+* follows:
+* ' +
+ '' +
+ '
' +
+ '' +
+ '
' +
+ '
' +
+ '
');
+ }
+ return results;
+ },
+
+ show : function(){
+ alert(this.toString());
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/CSS.js b/frontend/beta/js/YUI-extensions/CSS.js
new file mode 100644
index 0000000..4fba37c
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/CSS.js
@@ -0,0 +1,208 @@
+/**
+ * @class YAHOO.ext.util.CSS
+ * Class for manipulating CSS Rules
+ * @singleton
+ */
+YAHOO.ext.util.CSS = new function(){
+ var rules = null;
+
+ var toCamel = function(property) {
+ var convert = function(prop) {
+ var test = /(-[a-z])/i.exec(prop);
+ return prop.replace(RegExp.$1, RegExp.$1.substr(1).toUpperCase());
+ };
+ while(property.indexOf('-') > -1) {
+ property = convert(property);
+ }
+ return property;
+ };
+
+ /**
+ * Very simple dynamic creation of stylesheets from a text blob of rules.
+ * @param {String} cssText The text containing the css rules
+ * @return {StyleSheet}
+ */
+ this.createStyleSheet = function(cssText){
+ var ss;
+ if(YAHOO.ext.util.Browser.isIE){
+ ss = document.createStyleSheet();
+ ss.cssText = cssText;
+ }else{
+ var head = document.getElementsByTagName("head")[0];
+ var rules = document.createElement('style');
+ rules.setAttribute('type', 'text/css');
+ try{
+ rules.appendChild(document.createTextNode(cssText));
+ }catch(e){
+ rules.cssText = cssText;
+ }
+ head.appendChild(rules);
+ ss = document.styleSheets[document.styleSheets.length-1];
+ }
+ this.cacheStyleSheet(ss);
+ return ss;
+ };
+
+ this.removeStyleSheet = function(id){
+ var existing = document.getElementById(id);
+ if(existing){
+ existing.parentNode.removeChild(existing);
+ }
+ };
+
+ this.swapStyleSheet = function(id, url){
+ this.removeStyleSheet(id);
+ var ss = document.createElement('link');
+ ss.setAttribute('rel', 'stylesheet');
+ ss.setAttribute('type', 'text/css');
+ ss.setAttribute('id', id);
+ ss.setAttribute('href', url);
+ document.getElementsByTagName("head")[0].appendChild(ss);
+ };
+
+ /**
+ * Refresh the rule cache if you have dynamically added stylesheets
+ * @return {Object} An object (hash) of rules indexed by selector
+ */
+ this.refreshCache = function(){
+ return this.getRules(true);
+ };
+
+ this.cacheStyleSheet = function(ss){
+ try{// try catch for cross domain access issue
+ var ssRules = ss.cssRules || ss.rules;
+ for(var j = ssRules.length-1; j >= 0; --j){
+ rules[ssRules[j].selectorText] = ssRules[j];
+ }
+ }catch(e){}
+ };
+
+ /**
+ * Gets all css rules for the document
+ * @param {Boolean} refreshCache true to refresh the internal cache
+ * @return {Object} An object (hash) of rules indexed by selector
+ */
+ this.getRules = function(refreshCache){
+ if(rules == null || refreshCache){
+ rules = {};
+ var ds = document.styleSheets;
+ for(var i =0, len = ds.length; i < len; i++){
+ try{
+ this.cacheStyleSheet(ds[i]);
+ }catch(e){}
+ }
+ }
+ return rules;
+ };
+
+ /**
+ * Gets an an individual CSS rule by selector(s)
+ * @param {String/Array} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
+ * @param {Boolean} refreshCache true to refresh the internal cache
+ * @return {CSSRule} The CSS rule or null if one is not found
+ */
+ this.getRule = function(selector, refreshCache){
+ var rs = this.getRules(refreshCache);
+ if(!(selector instanceof Array)){
+ return rs[selector];
+ }
+ for(var i = 0; i < selector.length; i++){
+ if(rs[selector[i]]){
+ return rs[selector[i]];
+ }
+ }
+ return null;
+ };
+
+
+ /**
+ * Updates a rule property
+ * @param {String/Array} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
+ * @param {String} property The css property
+ * @param {String} value The new value for the property
+ * @return {Boolean} true if a rule was found and updated
+ */
+ this.updateRule = function(selector, property, value){
+ if(!(selector instanceof Array)){
+ var rule = this.getRule(selector);
+ if(rule){
+ rule.style[toCamel(property)] = value;
+ return true;
+ }
+ }else{
+ for(var i = 0; i < selector.length; i++){
+ if(this.updateRule(selector[i], property, value)){
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ /**
+ * Applies a rule to an element without adding the class
+ * @param {HTMLElement} el The element
+ * @param {String/Array} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
+ * @return {Boolean} true if a rule was found and applied
+ */
+ this.apply = function(el, selector){
+ if(!(selector instanceof Array)){
+ var rule = this.getRule(selector);
+ if(rule){
+ var s = rule.style;
+ for(var key in s){
+ if(typeof s[key] != 'function'){
+ if(s[key] && String(s[key]).indexOf(':') < 0 && s[key] != 'false'){
+ try{el.style[key] = s[key];}catch(e){}
+ }
+ }
+ }
+ return true;
+ }
+ }else{
+ for(var i = 0; i < selector.length; i++){
+ if(this.apply(el, selector[i])){
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ this.applyFirst = function(el, id, selector){
+ var selectors = [
+ '#' + id + ' ' + selector,
+ selector
+ ];
+ return this.apply(el, selectors);
+ };
+
+ this.revert = function(el, selector){
+ if(!(selector instanceof Array)){
+ var rule = this.getRule(selector);
+ if(rule){
+ for(key in rule.style){
+ if(rule.style[key] && String(rule.style[key]).indexOf(':') < 0 && rule.style[key] != 'false'){
+ try{el.style[key] = '';}catch(e){}
+ }
+ }
+ return true;
+ }
+ }else{
+ for(var i = 0; i < selector.length; i++){
+ if(this.revert(el, selector[i])){
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ this.revertFirst = function(el, id, selector){
+ var selectors = [
+ '#' + id + ' ' + selector,
+ selector
+ ];
+ return this.revert(el, selectors);
+ };
+}();
diff --git a/frontend/beta/js/YUI-extensions/CompositeElement.js b/frontend/beta/js/YUI-extensions/CompositeElement.js
new file mode 100644
index 0000000..7b9c875
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/CompositeElement.js
@@ -0,0 +1,140 @@
+/**
+ * @class YAHOO.ext.CompositeElement
+ * Standard composite class. Creates a YAHOO.ext.Element for every element in the collection.
+ *
+ * NOTE: Although they are not listed, this class supports all of the set/update methods of YAHOO.ext.Element. All YAHOO.ext.Element
+ * actions will be performed on all the elements in this collection.
+ *
+ * All methods return this and can be chained.
+
+ */
+YAHOO.ext.CompositeElement = function(els){
+ this.elements = [];
+ this.addElements(els);
+};
+YAHOO.ext.CompositeElement.prototype = {
+ isComposite: true,
+ addElements : function(els){
+ if(!els) return this;
+ var yels = this.elements;
+ var index = yels.length-1;
+ for(var i = 0, len = els.length; i < len; i++) {
+ yels[++index] = getEl(els[i], true);
+ }
+ return this;
+ },
+ invoke : function(fn, args){
+ var els = this.elements;
+ for(var i = 0, len = els.length; i < len; i++) {
+ YAHOO.ext.Element.prototype[fn].apply(els[i], args);
+ }
+ return this;
+ },
+ /**
+ * Adds elements to this composite.
+ * @param {String/Array} els A string CSS selector, an array of elements or an element
+ * @return {CompositeElement} this
+ */
+ add : function(els){
+ if(typeof els == 'string'){
+ this.addElements(YAHOO.ext.Element.selectorFunction(string));
+ }else if(els instanceof Array){
+ this.addElements(els);
+ }else{
+ this.addElements([els]);
+ }
+ return this;
+ },
+ /**
+ * Calls the passed function passing (el, this, index) for each element in this composite.
+ * @param {Function} fn The function to call
+ * @param {Object} scope (optional) The this object (defaults to the element)
+ * @return {CompositeElement} this
+ */
+ each : function(fn, scope){
+ var els = this.elements;
+ for(var i = 0, len = els.length; i < len; i++){
+ fn.call(scope || els[i], els[i], this, i);
+ }
+ return this;
+ }
+};
+/**
+ * @class YAHOO.ext.CompositeElementLite
+ * @extends YAHOO.ext.CompositeElement
+ * Flyweight composite class. Reuses the same YAHOO.ext.Element for element operations.
+ *
+ var els = getEls('#some-el div.some-class');
+ // or
+ var els = YAHOO.ext.Element.select('#some-el div.some-class');
+ els.setWidth(100); // all elements become 100 width
+ els.hide(true); // all elements fade out and hide
+ // or
+ els.setWidth(100).hide(true);
+
+ * NOTE: Although they are not listed, this class supports all of the set/update methods of YAHOO.ext.Element. All YAHOO.ext.Element
+ * actions will be performed on all the elements in this collection.
+ */
+YAHOO.ext.CompositeElementLite = function(els){
+ YAHOO.ext.CompositeElementLite.superclass.constructor.call(this, els);
+ this.el = YAHOO.ext.Element.get(this.elements[0], true);
+};
+YAHOO.extendX(YAHOO.ext.CompositeElementLite, YAHOO.ext.CompositeElement, {
+ addElements : function(els){
+ if(els){
+ this.elements = this.elements.concat(els);
+ }
+ return this;
+ },
+ invoke : function(fn, args){
+ var els = this.elements;
+ var el = this.el;
+ for(var i = 0, len = els.length; i < len; i++) {
+ el.dom = els[i];
+ YAHOO.ext.Element.prototype[fn].apply(el, args);
+ }
+ return this;
+ }
+});
+YAHOO.ext.CompositeElement.createCall = function(proto, fnName){
+ if(!proto[fnName]){
+ proto[fnName] = function(){
+ return this.invoke(fnName, arguments);
+ };
+ }
+};
+for(var fnName in YAHOO.ext.Element.prototype){
+ if(typeof YAHOO.ext.Element.prototype[fnName] == 'function'){
+ YAHOO.ext.CompositeElement.createCall(YAHOO.ext.CompositeElement.prototype, fnName);
+ }
+}
+if(typeof cssQuery == 'function'){// Dean Edwards cssQuery
+ YAHOO.ext.Element.selectorFunction = cssQuery;
+}else if(typeof document.getElementsBySelector == 'function'){ // Simon Willison's getElementsBySelector
+ YAHOO.ext.Element.selectorFunction = document.getElementsBySelector.createDelegate(document);
+}
+/**
+ * @member YAHOO.ext.Element
+* Selects elements based on the passed CSS selector to enable working on them as 1.
+* @param {String/Array} selector The CSS selector or an array of elements
+* @param {Boolean} unique (optional) true to create a unique YAHOO.ext.Element for each element (defaults to a shared flyweight object)
+* @return {CompositeElementLite/CompositeElement}
+* @method @static
+*/
+YAHOO.ext.Element.select = function(selector, unique){
+ var els;
+ if(typeof selector == 'string'){
+ els = YAHOO.ext.Element.selectorFunction(selector);
+ }else if(selector instanceof Array){
+ els = selector;
+ }else{
+ throw 'Invalid selector';
+ }
+ if(unique === true){
+ return new YAHOO.ext.CompositeElement(els);
+ }else{
+ return new YAHOO.ext.CompositeElementLite(els);
+ }
+};
+
+var getEls = YAHOO.ext.Element.select;
diff --git a/frontend/beta/js/YUI-extensions/CustomTagReader.js b/frontend/beta/js/YUI-extensions/CustomTagReader.js
new file mode 100644
index 0000000..12faaa9
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/CustomTagReader.js
@@ -0,0 +1,40 @@
+/**
+ * @class YAHOO.ext.CustomTagReader
+ * Utility class to normalize reading of custom tags across browsers.
+ */
+YAHOO.ext.CustomTagReader = function(namespace){
+ this.namespace = namespace;
+};
+YAHOO.ext.CustomTagReader.prototype = {
+ getAttribute : function(el, name, defaultValue){
+ return (this.useNS ?
+ v = el.getAttributeNS(this.namespace, name) : null) ||
+ el.getAttribute(this.namespace+':'+name) ||
+ el.getAttribute(name);
+ },
+
+ getElements : function(tagName, targetEl){
+ targetEl = targetEl || document.body;
+ var els;
+ if(this.useNS){ // no namespaces in IE
+ els = targetEl.getElementsByTagNameNS(this.namespace, tagName);
+ }
+ if(!els || els.length < 1){ // ie6, firefox 1.5, firefox 2 depending on doc type
+ els = targetEl.getElementsByTagName(this.namespace+':'+tagName);
+ }
+ if(!els || els.length < 1){ // everyone else
+ els = targetEl.getElementsByTagName(tagName);
+ }
+ return els;
+ },
+
+ eachElement : function(tagName, targetEl, fn, scope){
+ var els = this.getElements(tagName, targetEl);
+ for(var i = 0, len = els.length; i < len; i++) {
+ var el = els[i];
+ fn.call(scope || el, el);
+ }
+ },
+
+ useNS : (!YAHOO.ext.util.Browser.isIE && document.getElementsByTagNameNS) ? true : false
+};
diff --git a/frontend/beta/js/YUI-extensions/Date.js b/frontend/beta/js/YUI-extensions/Date.js
new file mode 100644
index 0000000..f79c8a5
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/Date.js
@@ -0,0 +1,407 @@
+/*
+ * All the Date functions below are the excellent work of Baron Schwartz
+ * They generate precompiled functions from date formats instead of parsing and processing
+ * the format everytime you do something with a date.
+ */
+/** @ignore */
+Date.parseFunctions = {count:0};
+/** @ignore */
+Date.parseRegexes = [];
+/** @ignore */
+Date.formatFunctions = {count:0};
+
+/**
+ * Formats a date given to the supplied format - the format syntax is the same as PHP's date() function.
+ */
+Date.prototype.dateFormat = function(format) {
+ if (Date.formatFunctions[format] == null) {
+ Date.createNewFormat(format);
+ }
+ var func = Date.formatFunctions[format];
+ return this[func]();
+};
+
+/**
+ * Same as {@link #dateFormat}
+ */
+Date.prototype.format = Date.prototype.dateFormat;
+
+/** @ignore */
+Date.createNewFormat = function(format) {
+ var funcName = "format" + Date.formatFunctions.count++;
+ Date.formatFunctions[format] = funcName;
+ var code = "Date.prototype." + funcName + " = function(){return ";
+ var special = false;
+ var ch = '';
+ for (var i = 0; i < format.length; ++i) {
+ ch = format.charAt(i);
+ if (!special && ch == "\\") {
+ special = true;
+ }
+ else if (special) {
+ special = false;
+ code += "'" + String.escape(ch) + "' + ";
+ }
+ else {
+ code += Date.getFormatCode(ch);
+ }
+ }
+ eval(code.substring(0, code.length - 3) + ";}");
+};
+
+/** @ignore */
+Date.getFormatCode = function(character) {
+ switch (character) {
+ case "d":
+ return "String.leftPad(this.getDate(), 2, '0') + ";
+ case "D":
+ return "Date.dayNames[this.getDay()].substring(0, 3) + ";
+ case "j":
+ return "this.getDate() + ";
+ case "l":
+ return "Date.dayNames[this.getDay()] + ";
+ case "S":
+ return "this.getSuffix() + ";
+ case "w":
+ return "this.getDay() + ";
+ case "z":
+ return "this.getDayOfYear() + ";
+ case "W":
+ return "this.getWeekOfYear() + ";
+ case "F":
+ return "Date.monthNames[this.getMonth()] + ";
+ case "m":
+ return "String.leftPad(this.getMonth() + 1, 2, '0') + ";
+ case "M":
+ return "Date.monthNames[this.getMonth()].substring(0, 3) + ";
+ case "n":
+ return "(this.getMonth() + 1) + ";
+ case "t":
+ return "this.getDaysInMonth() + ";
+ case "L":
+ return "(this.isLeapYear() ? 1 : 0) + ";
+ case "Y":
+ return "this.getFullYear() + ";
+ case "y":
+ return "('' + this.getFullYear()).substring(2, 4) + ";
+ case "a":
+ return "(this.getHours() < 12 ? 'am' : 'pm') + ";
+ case "A":
+ return "(this.getHours() < 12 ? 'AM' : 'PM') + ";
+ case "g":
+ return "((this.getHours() %12) ? this.getHours() % 12 : 12) + ";
+ case "G":
+ return "this.getHours() + ";
+ case "h":
+ return "String.leftPad((this.getHours() %12) ? this.getHours() % 12 : 12, 2, '0') + ";
+ case "H":
+ return "String.leftPad(this.getHours(), 2, '0') + ";
+ case "i":
+ return "String.leftPad(this.getMinutes(), 2, '0') + ";
+ case "s":
+ return "String.leftPad(this.getSeconds(), 2, '0') + ";
+ case "O":
+ return "this.getGMTOffset() + ";
+ case "T":
+ return "this.getTimezone() + ";
+ case "Z":
+ return "(this.getTimezoneOffset() * -60) + ";
+ default:
+ return "'" + String.escape(character) + "' + ";
+ };
+};
+
+/**
+ * Parses a date given the supplied format - the format syntax is the same as PHP's date() function.
+ */
+Date.parseDate = function(input, format) {
+ if (Date.parseFunctions[format] == null) {
+ Date.createParser(format);
+ }
+ var func = Date.parseFunctions[format];
+ return Date[func](input);
+};
+
+/** @ignore */
+Date.createParser = function(format) {
+ var funcName = "parse" + Date.parseFunctions.count++;
+ var regexNum = Date.parseRegexes.length;
+ var currentGroup = 1;
+ Date.parseFunctions[format] = funcName;
+
+ var code = "Date." + funcName + " = function(input){\n"
+ + "var y = -1, m = -1, d = -1, h = -1, i = -1, s = -1;\n"
+ + "var d = new Date();\n"
+ + "y = d.getFullYear();\n"
+ + "m = d.getMonth();\n"
+ + "d = d.getDate();\n"
+ + "var results = input.match(Date.parseRegexes[" + regexNum + "]);\n"
+ + "if (results && results.length > 0) {"
+ var regex = "";
+
+ var special = false;
+ var ch = '';
+ for (var i = 0; i < format.length; ++i) {
+ ch = format.charAt(i);
+ if (!special && ch == "\\") {
+ special = true;
+ }
+ else if (special) {
+ special = false;
+ regex += String.escape(ch);
+ }
+ else {
+ obj = Date.formatCodeToRegex(ch, currentGroup);
+ currentGroup += obj.g;
+ regex += obj.s;
+ if (obj.g && obj.c) {
+ code += obj.c;
+ }
+ }
+ }
+
+ code += "if (y > 0 && m >= 0 && d > 0 && h >= 0 && i >= 0 && s >= 0)\n"
+ + "{return new Date(y, m, d, h, i, s);}\n"
+ + "else if (y > 0 && m >= 0 && d > 0 && h >= 0 && i >= 0)\n"
+ + "{return new Date(y, m, d, h, i);}\n"
+ + "else if (y > 0 && m >= 0 && d > 0 && h >= 0)\n"
+ + "{return new Date(y, m, d, h);}\n"
+ + "else if (y > 0 && m >= 0 && d > 0)\n"
+ + "{return new Date(y, m, d);}\n"
+ + "else if (y > 0 && m >= 0)\n"
+ + "{return new Date(y, m);}\n"
+ + "else if (y > 0)\n"
+ + "{return new Date(y);}\n"
+ + "}return null;}";
+
+ Date.parseRegexes[regexNum] = new RegExp("^" + regex + "$");
+ eval(code);
+};
+
+/** @ignore */
+Date.formatCodeToRegex = function(character, currentGroup) {
+ switch (character) {
+ case "D":
+ return {g:0,
+ c:null,
+ s:"(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)"};
+ case "j":
+ case "d":
+ return {g:1,
+ c:"d = parseInt(results[" + currentGroup + "], 10);\n",
+ s:"(\\d{1,2})"};
+ case "l":
+ return {g:0,
+ c:null,
+ s:"(?:" + Date.dayNames.join("|") + ")"};
+ case "S":
+ return {g:0,
+ c:null,
+ s:"(?:st|nd|rd|th)"};
+ case "w":
+ return {g:0,
+ c:null,
+ s:"\\d"};
+ case "z":
+ return {g:0,
+ c:null,
+ s:"(?:\\d{1,3})"};
+ case "W":
+ return {g:0,
+ c:null,
+ s:"(?:\\d{2})"};
+ case "F":
+ return {g:1,
+ c:"m = parseInt(Date.monthNumbers[results[" + currentGroup + "].substring(0, 3)], 10);\n",
+ s:"(" + Date.monthNames.join("|") + ")"};
+ case "M":
+ return {g:1,
+ c:"m = parseInt(Date.monthNumbers[results[" + currentGroup + "]], 10);\n",
+ s:"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"};
+ case "n":
+ case "m":
+ return {g:1,
+ c:"m = parseInt(results[" + currentGroup + "], 10) - 1;\n",
+ s:"(\\d{1,2})"};
+ case "t":
+ return {g:0,
+ c:null,
+ s:"\\d{1,2}"};
+ case "L":
+ return {g:0,
+ c:null,
+ s:"(?:1|0)"};
+ case "Y":
+ return {g:1,
+ c:"y = parseInt(results[" + currentGroup + "], 10);\n",
+ s:"(\\d{4})"};
+ case "y":
+ return {g:1,
+ c:"var ty = parseInt(results[" + currentGroup + "], 10);\n"
+ + "y = ty > Date.y2kYear ? 1900 + ty : 2000 + ty;\n",
+ s:"(\\d{1,2})"};
+ case "a":
+ return {g:1,
+ c:"if (results[" + currentGroup + "] == 'am') {\n"
+ + "if (h == 12) { h = 0; }\n"
+ + "} else { if (h < 12) { h += 12; }}",
+ s:"(am|pm)"};
+ case "A":
+ return {g:1,
+ c:"if (results[" + currentGroup + "] == 'AM') {\n"
+ + "if (h == 12) { h = 0; }\n"
+ + "} else { if (h < 12) { h += 12; }}",
+ s:"(AM|PM)"};
+ case "g":
+ case "G":
+ case "h":
+ case "H":
+ return {g:1,
+ c:"h = parseInt(results[" + currentGroup + "], 10);\n",
+ s:"(\\d{1,2})"};
+ case "i":
+ return {g:1,
+ c:"i = parseInt(results[" + currentGroup + "], 10);\n",
+ s:"(\\d{2})"};
+ case "s":
+ return {g:1,
+ c:"s = parseInt(results[" + currentGroup + "], 10);\n",
+ s:"(\\d{2})"};
+ case "O":
+ return {g:0,
+ c:null,
+ s:"[+-]\\d{4}"};
+ case "T":
+ return {g:0,
+ c:null,
+ s:"[A-Z]{3}"};
+ case "Z":
+ return {g:0,
+ c:null,
+ s:"[+-]\\d{1,5}"};
+ default:
+ return {g:0,
+ c:null,
+ s:String.escape(character)};
+ }
+};
+
+Date.prototype.getTimezone = function() {
+ return this.toString().replace(
+ /^.*? ([A-Z]{3}) [0-9]{4}.*$/, "$1").replace(
+ /^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, "$1$2$3");
+};
+
+Date.prototype.getGMTOffset = function() {
+ return (this.getTimezoneOffset() > 0 ? "-" : "+")
+ + String.leftPad(Math.floor(this.getTimezoneOffset() / 60), 2, "0")
+ + String.leftPad(this.getTimezoneOffset() % 60, 2, "0");
+};
+
+Date.prototype.getDayOfYear = function() {
+ var num = 0;
+ Date.daysInMonth[1] = this.isLeapYear() ? 29 : 28;
+ for (var i = 0; i < this.getMonth(); ++i) {
+ num += Date.daysInMonth[i];
+ }
+ return num + this.getDate() - 1;
+};
+
+Date.prototype.getWeekOfYear = function() {
+ // Skip to Thursday of this week
+ var now = this.getDayOfYear() + (4 - this.getDay());
+ // Find the first Thursday of the year
+ var jan1 = new Date(this.getFullYear(), 0, 1);
+ var then = (7 - jan1.getDay() + 4);
+ return String.leftPad(((now - then) / 7) + 1, 2, "0");
+};
+
+Date.prototype.isLeapYear = function() {
+ var year = this.getFullYear();
+ return ((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
+};
+
+Date.prototype.getFirstDayOfMonth = function() {
+ var day = (this.getDay() - (this.getDate() - 1)) % 7;
+ return (day < 0) ? (day + 7) : day;
+};
+
+Date.prototype.getLastDayOfMonth = function() {
+ var day = (this.getDay() + (Date.daysInMonth[this.getMonth()] - this.getDate())) % 7;
+ return (day < 0) ? (day + 7) : day;
+};
+
+Date.prototype.getDaysInMonth = function() {
+ Date.daysInMonth[1] = this.isLeapYear() ? 29 : 28;
+ return Date.daysInMonth[this.getMonth()];
+};
+
+/** @ignore */
+Date.prototype.getSuffix = function() {
+ switch (this.getDate()) {
+ case 1:
+ case 21:
+ case 31:
+ return "st";
+ case 2:
+ case 22:
+ return "nd";
+ case 3:
+ case 23:
+ return "rd";
+ default:
+ return "th";
+ }
+};
+
+/** @ignore */
+Date.daysInMonth = [31,28,31,30,31,30,31,31,30,31,30,31];
+
+/**
+ * Override these values for international dates, for example...
+ * Date.monthNames = ['JanInYourLang', 'FebInYourLang', ...];
+ */
+Date.monthNames =
+ ["January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December"];
+
+/**
+ * Override these values for international dates, for example...
+ * Date.dayNames = ['SundayInYourLang', 'MondayInYourLang', ...];
+ */
+Date.dayNames =
+ ["Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday"];
+
+/** @ignore */
+Date.y2kYear = 50;
+
+/** @ignore */
+Date.monthNumbers = {
+ Jan:0,
+ Feb:1,
+ Mar:2,
+ Apr:3,
+ May:4,
+ Jun:5,
+ Jul:6,
+ Aug:7,
+ Sep:8,
+ Oct:9,
+ Nov:10,
+ Dec:11};
diff --git a/frontend/beta/js/YUI-extensions/DomHelper.js b/frontend/beta/js/YUI-extensions/DomHelper.js
new file mode 100644
index 0000000..d9e7484
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/DomHelper.js
@@ -0,0 +1,416 @@
+/**
+ * @class YAHOO.ext.DomHelper
+ * Utility class for working with DOM and/or Templates. It transparently supports using HTML fragments or DOM.
+ * For more information see this blog post with examples.
+ * @singleton
+ */
+YAHOO.ext.DomHelper = new function(){
+ /**@private*/
+ var d = document;
+ var tempTableEl = null;
+ /** True to force the use of DOM instead of html fragments @type Boolean */
+ this.useDom = false;
+ var emptyTags = /^(?:base|basefont|br|frame|hr|img|input|isindex|link|meta|nextid|range|spacer|wbr|audioscope|area|param|keygen|col|limittext|spot|tab|over|right|left|choose|atop|of)$/i;
+ /**
+ * Applies a style specification to an element
+ * @param {String/HTMLElement} el The element to apply styles to
+ * @param {String/Object/Function} styles A style specification string eg "width:100px", or object in the form {width:"100px"}, or
+ * a function which returns such a specification.
+ */
+ this.applyStyles = function(el, styles){
+ if(styles){
+ var D = YAHOO.util.Dom;
+ if (typeof styles == "string"){
+ var re = /\s?([a-z\-]*)\:([^;]*);?/gi;
+ var matches;
+ while ((matches = re.exec(styles)) != null){
+ D.setStyle(el, matches[1], matches[2]);
+ }
+ }else if (typeof styles == "object"){
+ for (var style in styles){
+ D.setStyle(el, style, styles[style]);
+ }
+ }else if (typeof styles == "function"){
+ YAHOO.ext.DomHelper.applyStyles(el, styles.call());
+ }
+ }
+ };
+
+ // build as innerHTML where available
+ /** @ignore */
+ var createHtml = function(o){
+ var b = '';
+ b += '<' + o.tag;
+ for(var attr in o){
+ if(attr == 'tag' || attr == 'children' || attr == 'html' || typeof o[attr] == 'function') continue;
+ if(attr == 'style'){
+ var s = o['style'];
+ if(typeof s == 'function'){
+ s = s.call();
+ }
+ if(typeof s == 'string'){
+ b += ' style="' + s + '"';
+ }else if(typeof s == 'object'){
+ b += ' style="';
+ for(var key in s){
+ if(typeof s[key] != 'function'){
+ b += key + ':' + s[key] + ';';
+ }
+ }
+ b += '"';
+ }
+ }else{
+ if(attr == 'cls'){
+ b += ' class="' + o['cls'] + '"';
+ }else if(attr == 'htmlFor'){
+ b += ' for="' + o['htmlFor'] + '"';
+ }else{
+ b += ' ' + attr + '="' + o[attr] + '"';
+ }
+ }
+ }
+ if(emptyTags.test(o.tag)){
+ b += ' />';
+ }else{
+ b += '>';
+ if(o.children){
+ for(var i = 0, len = o.children.length; i < len; i++) {
+ b += createHtml(o.children[i], b);
+ }
+ }
+ if(o.html){
+ b += o.html;
+ }
+ b += '' + o.tag + '>';
+ }
+ return b;
+ }
+
+ // build as dom
+ /** @ignore */
+ var createDom = function(o, parentNode){
+ var el = d.createElement(o.tag);
+ var useSet = el.setAttribute ? true : false; // In IE some elements don't have setAttribute
+ for(var attr in o){
+ if(attr == 'tag' || attr == 'children' || attr == 'html' || attr == 'style' || typeof o[attr] == 'function') continue;
+ if(attr=='cls'){
+ el.className = o['cls'];
+ }else{
+ if(useSet) el.setAttribute(attr, o[attr]);
+ else el[attr] = o[attr];
+ }
+ }
+ YAHOO.ext.DomHelper.applyStyles(el, o.style);
+ if(o.children){
+ for(var i = 0, len = o.children.length; i < len; i++) {
+ createDom(o.children[i], el);
+ }
+ }
+ if(o.html){
+ el.innerHTML = o.html;
+ }
+ if(parentNode){
+ parentNode.appendChild(el);
+ }
+ return el;
+ };
+
+ /**
+ * @ignore
+ * Nasty code for IE's broken table implementation
+ */
+ var insertIntoTable = function(tag, where, el, html){
+ if(!tempTableEl){
+ tempTableEl = document.createElement('div');
+ }
+ var node;
+ if(tag == 'table' || tag == 'tbody'){
+ tempTableEl.innerHTML = ''+html+'
';
+ node = tempTableEl.firstChild.firstChild.firstChild;
+ }else{
+ tempTableEl.innerHTML = '
';
+ node = tempTableEl.firstChild.firstChild.firstChild.firstChild;
+ }
+ if(where == 'beforebegin'){
+ el.parentNode.insertBefore(node, el);
+ return node;
+ }else if(where == 'afterbegin'){
+ el.insertBefore(node, el.firstChild);
+ return node;
+ }else if(where == 'beforeend'){
+ el.appendChild(node);
+ return node;
+ }else if(where == 'afterend'){
+ el.parentNode.insertBefore(node, el.nextSibling);
+ return node;
+ }
+ }
+
+ /**
+ * Inserts an HTML fragment into the Dom
+ * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
+ * @param {HTMLElement} el The context element
+ * @param {String} html The HTML fragmenet
+ * @return {HTMLElement} The new node
+ */
+ this.insertHtml = function(where, el, html){
+ where = where.toLowerCase();
+ if(el.insertAdjacentHTML){
+ var tag = el.tagName.toLowerCase();
+ if(tag == 'table' || tag == 'tbody' || tag == 'tr'){
+ return insertIntoTable(tag, where, el, html);
+ }
+ switch(where){
+ case 'beforebegin':
+ el.insertAdjacentHTML(where, html);
+ return el.previousSibling;
+ case 'afterbegin':
+ el.insertAdjacentHTML(where, html);
+ return el.firstChild;
+ case 'beforeend':
+ el.insertAdjacentHTML(where, html);
+ return el.lastChild;
+ case 'afterend':
+ el.insertAdjacentHTML(where, html);
+ return el.nextSibling;
+ }
+ throw 'Illegal insertion point -> "' + where + '"';
+ }
+ var range = el.ownerDocument.createRange();
+ var frag;
+ switch(where){
+ case 'beforebegin':
+ range.setStartBefore(el);
+ frag = range.createContextualFragment(html);
+ el.parentNode.insertBefore(frag, el);
+ return el.previousSibling;
+ case 'afterbegin':
+ if(el.firstChild){ // faster
+ range.setStartBefore(el.firstChild);
+ }else{
+ range.selectNodeContents(el);
+ range.collapse(true);
+ }
+ frag = range.createContextualFragment(html);
+ el.insertBefore(frag, el.firstChild);
+ return el.firstChild;
+ case 'beforeend':
+ if(el.lastChild){
+ range.setStartAfter(el.lastChild); // faster
+ }else{
+ range.selectNodeContents(el);
+ range.collapse(false);
+ }
+ frag = range.createContextualFragment(html);
+ el.appendChild(frag);
+ return el.lastChild;
+ case 'afterend':
+ range.setStartAfter(el);
+ frag = range.createContextualFragment(html);
+ el.parentNode.insertBefore(frag, el.nextSibling);
+ return el.nextSibling;
+ }
+ throw 'Illegal insertion point -> "' + where + '"';
+ };
+
+ /**
+ * Creates new Dom element(s) and inserts them before el
+ * @param {String/HTMLElement/Element} el The context element
+ * @param {Object} o The Dom object spec (and children)
+ * @param {Boolean} returnElement (optional) true to return a YAHOO.ext.Element
+ * @return {HTMLElement} The new node
+ */
+ this.insertBefore = function(el, o, returnElement){
+ el = el.dom ? el.dom : YAHOO.util.Dom.get(el);
+ var newNode;
+ if(this.useDom){
+ newNode = createDom(o, null);
+ el.parentNode.insertBefore(newNode, el);
+ }else{
+ var html = createHtml(o);
+ newNode = this.insertHtml('beforeBegin', el, html);
+ }
+ return returnElement ? YAHOO.ext.Element.get(newNode, true) : newNode;
+ };
+
+ /**
+ * Creates new Dom element(s) and inserts them after el
+ * @param {String/HTMLElement/Element} el The context element
+ * @param {Object} o The Dom object spec (and children)
+ * @param {Boolean} returnElement (optional) true to return a YAHOO.ext.Element
+ * @return {HTMLElement} The new node
+ */
+ this.insertAfter = function(el, o, returnElement){
+ el = el.dom ? el.dom : YAHOO.util.Dom.get(el);
+ var newNode;
+ if(this.useDom){
+ newNode = createDom(o, null);
+ el.parentNode.insertBefore(newNode, el.nextSibling);
+ }else{
+ var html = createHtml(o);
+ newNode = this.insertHtml('afterEnd', el, html);
+ }
+ return returnElement ? YAHOO.ext.Element.get(newNode, true) : newNode;
+ };
+
+ /**
+ * Creates new Dom element(s) and appends them to el
+ * @param {String/HTMLElement/Element} el The context element
+ * @param {Object} o The Dom object spec (and children)
+ * @param {Boolean} returnElement (optional) true to return a YAHOO.ext.Element
+ * @return {HTMLElement} The new node
+ */
+ this.append = function(el, o, returnElement){
+ el = el.dom ? el.dom : YAHOO.util.Dom.get(el);
+ var newNode;
+ if(this.useDom){
+ newNode = createDom(o, null);
+ el.appendChild(newNode);
+ }else{
+ var html = createHtml(o);
+ newNode = this.insertHtml('beforeEnd', el, html);
+ }
+ return returnElement ? YAHOO.ext.Element.get(newNode, true) : newNode;
+ };
+
+ /**
+ * Creates new Dom element(s) and overwrites the contents of el with them
+ * @param {String/HTMLElement/Element} el The context element
+ * @param {Object} o The Dom object spec (and children)
+ * @param {Boolean} returnElement (optional) true to return a YAHOO.ext.Element
+ * @return {HTMLElement} The new node
+ */
+ this.overwrite = function(el, o, returnElement){
+ el = el.dom ? el.dom : YAHOO.util.Dom.get(el);
+ el.innerHTML = createHtml(o);
+ return returnElement ? YAHOO.ext.Element.get(el.firstChild, true) : el.firstChild;
+ };
+
+ /**
+ * Creates a new YAHOO.ext.DomHelper.Template from the Dom object spec
+ * @param {Object} o The Dom object spec (and children)
+ * @return {YAHOO.ext.DomHelper.Template} The new template
+ */
+ this.createTemplate = function(o){
+ var html = createHtml(o);
+ return new YAHOO.ext.DomHelper.Template(html);
+ };
+}();
+
+/**
+* @class YAHOO.ext.DomHelper.Template
+* Represents an HTML fragment template.
+* For more information see this blog post with examples.
+* '+html+'
+* This class is also available as YAHOO.ext.Template.
+* @constructor
+* @param {String/Array} html The HTML fragment or an array of fragments to join('') or multiple arguments to join('')
+*/
+YAHOO.ext.DomHelper.Template = function(html){
+ if(html instanceof Array){
+ html = html.join('');
+ }else if(arguments.length > 1){
+ html = Array.prototype.join.call(arguments, '');
+ }
+ /**@private*/
+ this.html = html;
+};
+YAHOO.ext.DomHelper.Template.prototype = {
+ /**
+ * Returns an HTML fragment of this template with the specified values applied
+ * @param {Object} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @return {String}
+ */
+ applyTemplate : function(values){
+ if(this.compiled){
+ return this.compiled(values);
+ }
+ var empty = '';
+ var fn = function(match, index){
+ if(typeof values[index] != 'undefined'){
+ return values[index];
+ }else{
+ return empty;
+ }
+ }
+ return this.html.replace(this.re, fn);
+ },
+
+ /**
+ * The regular expression used to match template variables
+ * @type RegExp
+ * @property
+ */
+ re : /\{([\w|-]+)\}/g,
+
+ /**
+ * Compiles the template into an internal function, eliminating the RegEx overhead
+ */
+ compile : function(){
+ var body = ["this.compiled = function(values){ return ['"];
+ body.push(this.html.replace(this.re, "', values['$1'], '"));
+ body.push("'].join('');};");
+ eval(body.join(''));
+ return this;
+ },
+
+ /**
+ * Applies the supplied values to the template and inserts the new node(s) before el
+ * @param {String/HTMLElement/Element} el The context element
+ * @param {Object} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @param {Boolean} returnElement (optional) true to return a YAHOO.ext.Element
+ * @return {HTMLElement} The new node
+ */
+ insertBefore: function(el, values, returnElement){
+ el = el.dom ? el.dom : YAHOO.util.Dom.get(el);
+ var newNode = YAHOO.ext.DomHelper.insertHtml('beforeBegin', el, this.applyTemplate(values));
+ return returnElement ? YAHOO.ext.Element.get(newNode, true) : newNode;
+ },
+
+ /**
+ * Applies the supplied values to the template and inserts the new node(s) after el
+ * @param {String/HTMLElement/Element} el The context element
+ * @param {Object} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @param {Boolean} returnElement (optional) true to return a YAHOO.ext.Element
+ * @return {HTMLElement} The new node
+ */
+ insertAfter : function(el, values, returnElement){
+ el = el.dom ? el.dom : YAHOO.util.Dom.get(el);
+ var newNode = YAHOO.ext.DomHelper.insertHtml('afterEnd', el, this.applyTemplate(values));
+ return returnElement ? YAHOO.ext.Element.get(newNode, true) : newNode;
+ },
+
+ /**
+ * Applies the supplied values to the template and append the new node(s) to el
+ * @param {String/HTMLElement/Element} el The context element
+ * @param {Object} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @param {Boolean} returnElement (optional) true to return a YAHOO.ext.Element
+ * @return {HTMLElement} The new node
+ */
+ append : function(el, values, returnElement){
+ el = el.dom ? el.dom : YAHOO.util.Dom.get(el);
+ var newNode = YAHOO.ext.DomHelper.insertHtml('beforeEnd', el, this.applyTemplate(values));
+ return returnElement ? YAHOO.ext.Element.get(newNode, true) : newNode;
+ },
+
+ /**
+ * Applies the supplied values to the template and overwrites the content of el with the new node(s)
+ * @param {String/HTMLElement/Element} el The context element
+ * @param {Object} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @param {Boolean} returnElement (optional) true to return a YAHOO.ext.Element
+ * @return {HTMLElement} The new node
+ */
+ overwrite : function(el, values, returnElement){
+ el = el.dom ? el.dom : YAHOO.util.Dom.get(el);
+ el.innerHTML = '';
+ var newNode = YAHOO.ext.DomHelper.insertHtml('beforeEnd', el, this.applyTemplate(values));
+ return returnElement ? YAHOO.ext.Element.get(newNode, true) : newNode;
+ }
+};
+/**
+ * Alias for applyTemplate
+ * @method
+ */
+YAHOO.ext.DomHelper.Template.prototype.apply = YAHOO.ext.DomHelper.Template.prototype.applyTemplate;
+
+YAHOO.ext.Template = YAHOO.ext.DomHelper.Template;
diff --git a/frontend/beta/js/YUI-extensions/Element.js b/frontend/beta/js/YUI-extensions/Element.js
new file mode 100644
index 0000000..4019923
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/Element.js
@@ -0,0 +1,2157 @@
+/**
+ * @class YAHOO.ext.Element
+ * Wraps around a DOM element and provides convenient access to Yahoo
+ * UI library functionality (and more).
+ * Usage:
+ *
+ * Using YAHOO.ext.Element.get() instead of calling the constructor directly ensures you get the same object
+ * each call instead of constructing a new one.
+ * var el = YAHOO.ext.Element.get('myElementId');
+ * // or the shorter
+ * var el = getEl('myElementId');
+ *
+ * For working with collections of Elements, see YAHOO.ext.CompositeElement
+ * @requires YAHOO.util.Dom
+ * @requires YAHOO.util.Event
+ * @requires YAHOO.util.CustomEvent
+ * @requires YAHOO.util.Anim (optional) to support animation
+ * @requires YAHOO.util.Motion (optional) to support animation
+ * @requires YAHOO.util.Easing (optional) to support animation
+ * @constructor Create a new Element directly.
+ * @param {String/HTMLElement} element
+ * @param {Boolean} forceNew (optional) By default the constructor checks to see if there is already an instance of this element in the cache and if there is it returns the same instance. This will skip that check (useful for extending this class).
+ */
+YAHOO.ext.Element = function(element, forceNew){
+ var dom = typeof element == 'string' ?
+ document.getElementById(element) : element;
+ if(!dom){ // invalid id/element
+ return null;
+ }
+ if(!forceNew && YAHOO.ext.Element.cache[dom.id]){ // element object already exists
+ return YAHOO.ext.Element.cache[dom.id];
+ }
+ /**
+ * The DOM element
+ * @type HTMLElement
+ */
+ this.dom = dom;
+
+ /**
+ * The DOM element ID
+ * @type String
+ */
+ this.id = dom.id;
+
+ /**
+ * The element's default display mode @type String
+ */
+ this.originalDisplay = YAHOO.util.Dom.getStyle(dom, 'display') || '';
+ if(this.autoDisplayMode){
+ if(this.originalDisplay == 'none'){
+ this.setVisibilityMode(YAHOO.ext.Element.DISPLAY);
+ }
+ }
+ if(this.originalDisplay == 'none'){
+ this.originalDisplay = '';
+ }
+}
+
+YAHOO.ext.Element.prototype = {
+ visibilityMode : 1,
+ /**
+ * The default unit to append to CSS values where a unit isn't provided (Defaults to px).
+ * @type String
+ */
+ defaultUnit : 'px',
+ /**
+ * Sets the elements visibility mode. When setVisible() is called it
+ * will use this to determine whether to set the visibility or the display property.
+ * @param visMode Element.VISIBILITY or Element.DISPLAY
+ * @return {YAHOO.ext.Element} this
+ */
+ setVisibilityMode : function(visMode){
+ this.visibilityMode = visMode;
+ return this;
+ },
+ /**
+ * Convenience method for setVisibilityMode(Element.DISPLAY)
+ * @param {String} display (optional) What to set display to when visible
+ * @return {YAHOO.ext.Element} this
+ */
+ enableDisplayMode : function(display){
+ this.setVisibilityMode(YAHOO.ext.Element.DISPLAY);
+ if(typeof display != 'undefined') this.originalDisplay = display;
+ return this;
+ },
+
+ /**
+ * Perform Yahoo UI animation on this element.
+ * @param {Object} args The YUI animation control args
+ * @param {Float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeBoth)
+ * @param {Function} animType (optional) YAHOO.util.Anim subclass to use. For example: YAHOO.util.Motion
+ * @return {YAHOO.ext.Element} this
+ */
+ animate : function(args, duration, onComplete, easing, animType, stopAnims){
+ this.anim(args, duration, onComplete, easing, animType);
+ return this;
+ },
+
+ /**
+ * @private Internal animation call
+ */
+ anim : function(args, duration, onComplete, easing, animType){
+ animType = animType || YAHOO.util.Anim;
+ var anim = new animType(this.dom, args, duration || .35,
+ easing || YAHOO.util.Easing.easeBoth);
+ if(onComplete){
+ anim.onComplete.subscribe(function(){
+ if(typeof onComplete == 'function'){
+ onComplete.call(this);
+ }else if(onComplete instanceof Array){
+ for(var i = 0; i < onComplete.length; i++){
+ var fn = onComplete[i];
+ if(fn) fn.call(this);
+ }
+ }
+ }, this, true);
+ }
+ anim.animate();
+ return anim;
+ },
+
+ /**
+ * Scrolls this element into view within the passed container.
+ * @param {String/HTMLElement/Element} container (optional) The container element to scroll (defaults to document.body)
+ * @return {YAHOO.ext.Element} this
+ */
+ scrollIntoView : function(container){
+ var c = getEl(container || document.body, true);
+ var cp = c.getStyle('position');
+ var restorePos = false;
+ if(cp != 'relative' && cp != 'absolute'){
+ c.setStyle('position', 'relative');
+ restorePos = true;
+ }
+ var el = this.dom;
+ var childTop = parseInt(el.offsetTop, 10);
+ var childBottom = childTop + el.offsetHeight;
+ var containerTop = parseInt(c.dom.scrollTop, 10); // parseInt for safari bug
+ var containerBottom = containerTop + c.dom.clientHeight;
+ if(childTop < containerTop){
+ c.dom.scrollTop = childTop;
+ }else if(childBottom > containerBottom){
+ c.dom.scrollTop = childBottom-c.dom.clientHeight;
+ }
+ if(restorePos){
+ c.setStyle('position', cp);
+ }
+ return this;
+ },
+
+ /**
+ * Measures the elements content height and updates height to match. Note, this function uses setTimeout and
+ * the new height may not be available immediately.
+ * @param {Boolean} animate (optional) Animate the transition (Default is false)
+ * @param {Float} duration (optional) Length of the animation. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeOut for hiding or YAHOO.util.Easing.easeIn for showing)
+ * @return {YAHOO.ext.Element} this
+ */
+ autoHeight : function(animate, duration, onComplete, easing){
+ var oldHeight = this.getHeight();
+ this.clip();
+ this.setHeight(1); // force clipping
+ setTimeout(function(){
+ var height = parseInt(this.dom.scrollHeight, 10); // parseInt for Safari
+ if(!animate){
+ this.setHeight(height);
+ this.unclip();
+ if(typeof onComplete == 'function'){
+ onComplete();
+ }
+ }else{
+ this.setHeight(oldHeight); // restore original height
+ this.setHeight(height, animate, duration, function(){
+ this.unclip();
+ if(typeof onComplete == 'function') onComplete();
+ }.createDelegate(this), easing);
+ }
+ }.createDelegate(this), 0);
+ return this;
+ },
+
+ contains : function(el){
+ if(!el){return false;}
+ return YAHOO.util.Dom.isAncestor(this.dom, el.dom ? el.dom : el);
+ },
+
+ /**
+ * Checks whether the element is currently visible using both visibility and display properties.
+ * @param {Boolean} deep True to walk the dom and see if parent elements are hidden.
+ * @return {Boolean} true if the element is currently visible
+ */
+ isVisible : function(deep) {
+ var vis = YAHOO.util.Dom.getStyle(this.dom, 'visibility') != 'hidden'
+ && YAHOO.util.Dom.getStyle(this.dom, 'display') != 'none';
+ if(!deep || !vis){
+ return vis;
+ }
+ var p = this.dom.parentNode;
+ while(p && p.tagName.toLowerCase() != 'body'){
+ if(YAHOO.util.Dom.getStyle(p, 'visibility') == 'hidden' || YAHOO.util.Dom.getStyle(p, 'display') == 'none'){
+ return false;
+ }
+ p = p.parentNode;
+ }
+ return true;
+ },
+
+ /**
+ * Selects child nodes based on the passed CSS selector (the selector should not contain an id)
+ * @param {String} selector The CSS selector
+ * @param {Boolean} unique true to create a unique YAHOO.ext.Element for each child (defaults to a shared flyweight object)
+ * @return {CompositeElement/CompositeElementLite} The composite element
+ */
+ select : function(selector, unique){
+ return YAHOO.ext.Element.select('#' + this.dom.id + ' ' + selector, unique);
+ },
+
+ /**
+ * Initializes a YAHOO.util.DD object for this element.
+ * @param {String} group The group the DD object is member of
+ * @param {Object} config The DD config object
+ * @param {Object} overrides An object containing methods to override/implement on the DD object
+ * @return {YAHOO.util.DD} The DD object
+ */
+ initDD : function(group, config, overrides){
+ var dd = new YAHOO.util.DD(YAHOO.util.Dom.generateId(this.dom), group, config);
+ return YAHOO.ext.util.Config.apply(dd, overrides);
+ },
+
+ /**
+ * Initializes a YAHOO.util.DDProxy object for this element.
+ * @param {String} group The group the DDProxy object is member of
+ * @param {Object} config The DDProxy config object
+ * @param {Object} overrides An object containing methods to override/implement on the DDProxy object
+ * @return {YAHOO.util.DDProxy} The DDProxy object
+ */
+ initDDProxy : function(group, config, overrides){
+ var dd = new YAHOO.util.DDProxy(YAHOO.util.Dom.generateId(this.dom), group, config);
+ return YAHOO.ext.util.Config.apply(dd, overrides);
+ },
+
+ /**
+ * Initializes a YAHOO.util.DDTarget object for this element.
+ * @param {String} group The group the DDTarget object is member of
+ * @param {Object} config The DDTarget config object
+ * @param {Object} overrides An object containing methods to override/implement on the DDTarget object
+ * @return {YAHOO.util.DDTarget} The DDTarget object
+ */
+ initDDTarget : function(group, config, overrides){
+ var dd = new YAHOO.util.DDTarget(YAHOO.util.Dom.generateId(this.dom), group, config);
+ return YAHOO.ext.util.Config.apply(dd, overrides);
+ },
+
+ /**
+ * Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use
+ * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property.
+ * @param {Boolean} visible Whether the element is visible
+ * @param {Boolean} animate (optional) Fade the element in or out (Default is false)
+ * @param {Float} duration (optional) How long the fade effect lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeOut for hiding or YAHOO.util.Easing.easeIn for showing)
+ * @return {YAHOO.ext.Element} this
+ */
+ setVisible : function(visible, animate, duration, onComplete, easing){
+ //if(this.isVisible() == visible) return; // nothing to do
+ if(!animate || !YAHOO.util.Anim){
+ if(this.visibilityMode == YAHOO.ext.Element.DISPLAY){
+ this.setDisplayed(visible);
+ }else{
+ YAHOO.util.Dom.setStyle(this.dom, 'visibility', visible ? 'visible' : 'hidden');
+ }
+ }else{
+ // make sure they can see the transition
+ this.setOpacity(visible?0:1);
+ YAHOO.util.Dom.setStyle(this.dom, 'visibility', 'visible');
+ if(this.visibilityMode == YAHOO.ext.Element.DISPLAY){
+ this.setDisplayed(true);
+ }
+ var args = {opacity: { from: (visible?0:1), to: (visible?1:0) }};
+ var anim = new YAHOO.util.Anim(this.dom, args, duration || .35,
+ easing || (visible ? YAHOO.util.Easing.easeIn : YAHOO.util.Easing.easeOut));
+ anim.onComplete.subscribe((function(){
+ if(this.visibilityMode == YAHOO.ext.Element.DISPLAY){
+ this.setDisplayed(visible);
+ }else{
+ YAHOO.util.Dom.setStyle(this.dom, 'visibility', visible ? 'visible' : 'hidden');
+ }
+ }).createDelegate(this));
+ if(onComplete){
+ anim.onComplete.subscribe(onComplete);
+ }
+ anim.animate();
+ }
+ return this;
+ },
+
+ /**
+ * Returns true if display is not "none"
+ * @return {Boolean}
+ */
+ isDisplayed : function() {
+ return YAHOO.util.Dom.getStyle(this.dom, 'display') != 'none';
+ },
+
+ /**
+ * Toggles the elements visibility or display, depending on visibility mode.
+ * @param {Boolean} animate (optional) Fade the element in or out (Default is false)
+ * @param {float} duration (optional) How long the fade effect lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeOut for hiding or YAHOO.util.Easing.easeIn for showing)
+ * @return {YAHOO.ext.Element} this
+ */
+ toggle : function(animate, duration, onComplete, easing){
+ this.setVisible(!this.isVisible(), animate, duration, onComplete, easing);
+ return this;
+ },
+
+ /**
+ * Sets the css display. Uses originalDisplay if value is a boolean true.
+ * @param {Boolean} value Boolean to display the element using it's default display or a string to set the display directly
+ * @return {YAHOO.ext.Element} this
+ */
+ setDisplayed : function(value) {
+ if(typeof value == 'boolean'){
+ value = value ? this.originalDisplay : 'none';
+ }
+ YAHOO.util.Dom.setStyle(this.dom, 'display', value);
+ return this;
+ },
+
+ /**
+ * Tries to focus the element. Any exceptions are caught.
+ * @return {YAHOO.ext.Element} this
+ */
+ focus : function() {
+ try{
+ this.dom.focus();
+ }catch(e){}
+ return this;
+ },
+
+ /**
+ * Tries to blur the element. Any exceptions are caught.
+ * @return {YAHOO.ext.Element} this
+ */
+ blur : function() {
+ try{
+ this.dom.blur();
+ }catch(e){}
+ return this;
+ },
+
+ /**
+ * Add a CSS class to the element.
+ * @param {String/Array} className The CSS class to add or an array of classes
+ * @return {YAHOO.ext.Element} this
+ */
+ addClass : function(className){
+ if(className instanceof Array){
+ for(var i = 0, len = className.length; i < len; i++) {
+ this.addClass(className[i]);
+ }
+ }else{
+ if(!this.hasClass(className)){
+ this.dom.className = this.dom.className + ' ' + className;
+ }
+ }
+ return this;
+ },
+
+ /**
+ * Adds the passed className to this element and removes the class from all siblings
+ * @param {String} className The className to add
+ * @return {YAHOO.ext.Element} this
+ */
+ radioClass : function(className){
+ var siblings = this.dom.parentNode.childNodes;
+ for(var i = 0; i < siblings.length; i++) {
+ var s = siblings[i];
+ if(s.nodeType == 1){
+ YAHOO.util.Dom.removeClass(s, className);
+ }
+ }
+ this.addClass(className);
+ return this;
+ },
+ /**
+ * Removes a CSS class from the element.
+ * @param {String/Array} className The CSS class to remove or an array of classes
+ * @return {YAHOO.ext.Element} this
+ */
+ removeClass : function(className){
+ if(!className){
+ return this;
+ }
+ if(className instanceof Array){
+ for(var i = 0, len = className.length; i < len; i++) {
+ this.removeClass(className[i]);
+ }
+ }else{
+ var re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)', 'g');
+ var c = this.dom.className;
+ if(re.test(c)){
+ this.dom.className = c.replace(re, ' ');
+ }
+ }
+ return this;
+ },
+
+ /**
+ * Toggles (adds or removes) the passed class.
+ * @param {String} className
+ * @return {YAHOO.ext.Element} this
+ */
+ toggleClass : function(className){
+ if(this.hasClass(className)){
+ this.removeClass(className);
+ }else{
+ this.addClass(className);
+ }
+ return this;
+ },
+
+ /**
+ * Checks if a CSS class is in use by the element.
+ * @param {String} className The CSS class to check
+ * @return {Boolean} true or false
+ */
+ hasClass : function(className){
+ var re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
+ return re.test(this.dom.className);
+ },
+
+ /**
+ * Replaces a CSS class on the element with another.
+ * @param {String} oldClassName The CSS class to replace
+ * @param {String} newClassName The replacement CSS class
+ * @return {YAHOO.ext.Element} this
+ */
+ replaceClass : function(oldClassName, newClassName){
+ this.removeClass(oldClassName);
+ this.addClass(newClassName);
+ return this;
+ },
+
+ /**
+ * Normalizes currentStyle and ComputedStyle.
+ * @param {String} property The style property whose value is returned.
+ * @return {String} The current value of the style property for this element.
+ */
+ getStyle : function(name){
+ return YAHOO.util.Dom.getStyle(this.dom, name);
+ },
+
+ /**
+ * Wrapper for setting style properties, also takes single object parameter of multiple styles
+ * @param {String/Object} property The style property to be set or an object of multiple styles.
+ * @param {String} val (optional) The value to apply to the given property or null if an object was passed.
+ * @return {YAHOO.ext.Element} this
+ */
+ setStyle : function(name, value){
+ if(typeof name == 'string'){
+ YAHOO.util.Dom.setStyle(this.dom, name, value);
+ }else{
+ var D = YAHOO.util.Dom;
+ for(var style in name){
+ if(typeof name[style] != 'function'){
+ D.setStyle(this.dom, style, name[style]);
+ }
+ }
+ }
+ return this;
+ },
+
+ /**
+ * More flexible version of {@link #setStyle} for setting style properties.
+ * @param {String/Object/Function} styles A style specification string eg "width:100px", or object in the form {width:"100px"}, or
+ * a function which returns such a specification.
+ * @return {YAHOO.ext.Element} this
+ */
+ applyStyles : function(style){
+ YAHOO.ext.DomHelper.applyStyles(this.dom, style);
+ },
+
+ /**
+ * Gets the current X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ @ return {Number} The X position of the element
+ */
+ getX : function(){
+ return YAHOO.util.Dom.getX(this.dom);
+ },
+
+ /**
+ * Gets the current Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ @ return {Number} The Y position of the element
+ */
+ getY : function(){
+ return YAHOO.util.Dom.getY(this.dom);
+ },
+
+ /**
+ * Gets the current position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ @ return {Array} The XY position of the element
+ */
+ getXY : function(){
+ return YAHOO.util.Dom.getXY(this.dom);
+ },
+
+ /**
+ * Sets the X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ @param {Number} The X position of the element
+ * @param {Boolean} animate (optional) Animate the transition (Default is false)
+ * @param {float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeBoth)
+ * @return {YAHOO.ext.Element} this
+ */
+ setX : function(x, animate, duration, onComplete, easing){
+ if(!animate || !YAHOO.util.Anim){
+ YAHOO.util.Dom.setX(this.dom, x);
+ }else{
+ this.setXY([x, this.getY()], animate, duration, onComplete, easing);
+ }
+ return this;
+ },
+
+ /**
+ * Sets the Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ @param {Number} The Y position of the element
+ * @param {Boolean} animate (optional) Animate the transition (Default is false)
+ * @param {float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeBoth)
+ * @return {YAHOO.ext.Element} this
+ */
+ setY : function(y, animate, duration, onComplete, easing){
+ if(!animate || !YAHOO.util.Anim){
+ YAHOO.util.Dom.setY(this.dom, y);
+ }else{
+ this.setXY([this.getX(), y], animate, duration, onComplete, easing);
+ }
+ return this;
+ },
+
+ /**
+ * Set the element's left position directly using CSS style (instead of setX())
+ * @param {String} left The left CSS property value
+ * @return {YAHOO.ext.Element} this
+ */
+ setLeft : function(left){
+ YAHOO.util.Dom.setStyle(this.dom, 'left', this.addUnits(left));
+ return this;
+ },
+
+ /**
+ * Set the element's top position directly using CSS style (instead of setY())
+ * @param {String} top The top CSS property value
+ * @return {YAHOO.ext.Element} this
+ */
+ setTop : function(top){
+ YAHOO.util.Dom.setStyle(this.dom, 'top', this.addUnits(top));
+ return this;
+ },
+
+ /**
+ * Set the element's css right style
+ * @param {String} right The right CSS property value
+ * @return {YAHOO.ext.Element} this
+ */
+ setRight : function(right){
+ YAHOO.util.Dom.setStyle(this.dom, 'right', this.addUnits(right));
+ return this;
+ },
+
+ /**
+ * Set the element's css bottom style
+ * @param {String} bottom The bottom CSS property value
+ * @return {YAHOO.ext.Element} this
+ */
+ setBottom : function(bottom){
+ YAHOO.util.Dom.setStyle(this.dom, 'bottom', this.addUnits(bottom));
+ return this;
+ },
+
+ /**
+ * Set the position of the element in page coordinates, regardless of how the element is positioned.
+ * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @param {Array} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
+ * @param {Boolean} animate (optional) Animate the transition (Default is false)
+ * @param {float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeBoth)
+ * @return {YAHOO.ext.Element} this
+ */
+ setXY : function(pos, animate, duration, onComplete, easing){
+ if(!animate || !YAHOO.util.Anim){
+ YAHOO.util.Dom.setXY(this.dom, pos);
+ }else{
+ this.anim({points: {to: pos}}, duration, onComplete, easing, YAHOO.util.Motion);
+ }
+ return this;
+ },
+
+ /**
+ * Set the position of the element in page coordinates, regardless of how the element is positioned.
+ * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @param {Number} x X value for new position (coordinates are page-based)
+ * @param {Number} y Y value for new position (coordinates are page-based)
+ * @param {Boolean} animate (optional) Animate the transition (Default is false)
+ * @param {float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeBoth)
+ * @return {YAHOO.ext.Element} this
+ */
+ setLocation : function(x, y, animate, duration, onComplete, easing){
+ this.setXY([x, y], animate, duration, onComplete, easing);
+ return this;
+ },
+
+ /**
+ * Set the position of the element in page coordinates, regardless of how the element is positioned.
+ * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @param {Number} x X value for new position (coordinates are page-based)
+ * @param {Number} y Y value for new position (coordinates are page-based)
+ * @param {Boolean} animate (optional) Animate the transition (Default is false)
+ * @param {float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeBoth)
+ * @return {YAHOO.ext.Element} this
+ */
+ moveTo : function(x, y, animate, duration, onComplete, easing){
+ //YAHOO.util.Dom.setStyle(this.dom, 'left', this.addUnits(x));
+ //YAHOO.util.Dom.setStyle(this.dom, 'top', this.addUnits(y));
+ this.setXY([x, y], animate, duration, onComplete, easing);
+ return this;
+ },
+
+ /**
+ * Returns the region of the given element.
+ * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
+ * @return {Region} A YAHOO.util.Region containing "top, left, bottom, right" member data.
+ */
+ getRegion : function(){
+ return YAHOO.util.Dom.getRegion(this.dom);
+ },
+
+ /**
+ * Returns the offset height of the element
+ * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
+ * @return {Number} The element's height
+ */
+ getHeight : function(contentHeight){
+ var h = this.dom.offsetHeight;
+ return contentHeight !== true ? h : h-this.getBorderWidth('tb')-this.getPadding('tb');
+ },
+
+ /**
+ * Returns the offset width of the element
+ * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
+ * @return {Number} The element's width
+ */
+ getWidth : function(contentWidth){
+ var w = this.dom.offsetWidth;
+ return contentWidth !== true ? w : w-this.getBorderWidth('lr')-this.getPadding('lr');
+ },
+
+ /**
+ * Returns the size of the element
+ * @param {Boolean} contentSize (optional) true to get the width/size minus borders and padding
+ * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
+ */
+ getSize : function(contentSize){
+ return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
+ },
+
+ /** @private */
+ adjustWidth : function(width){
+ if(typeof width == 'number'){
+ if(this.autoBoxAdjust && !this.isBorderBox()){
+ width -= (this.getBorderWidth('lr') + this.getPadding('lr'));
+ }
+ if(width < 0){
+ width = 0;
+ }
+ }
+ return width;
+ },
+
+ /** @private */
+ adjustHeight : function(height){
+ if(typeof height == 'number'){
+ if(this.autoBoxAdjust && !this.isBorderBox()){
+ height -= (this.getBorderWidth('tb') + this.getPadding('tb'));
+ }
+ if(height < 0){
+ height = 0;
+ }
+ }
+ return height;
+ },
+
+ /**
+ * Set the width of the element
+ * @param {Number} width The new width
+ * @param {Boolean} animate (optional) Animate the transition (Default is false)
+ * @param {float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeOut if width is larger or YAHOO.util.Easing.easeIn if it is smaller)
+ * @return {YAHOO.ext.Element} this
+ */
+ setWidth : function(width, animate, duration, onComplete, easing){
+ width = this.adjustWidth(width);
+ if(!animate || !YAHOO.util.Anim){
+ this.dom.style.width = this.addUnits(width);
+ //YAHOO.util.Dom.setStyle(this.dom, 'width', this.addUnits(width));
+ }else{
+ this.anim({width: {to: width}}, duration, onComplete,
+ easing || (width > this.getWidth() ? YAHOO.util.Easing.easeOut : YAHOO.util.Easing.easeIn));
+ }
+ return this;
+ },
+
+ /**
+ * Set the height of the element
+ * @param {Number} height The new height
+ * @param {Boolean} animate (optional) Animate the transition (Default is false)
+ * @param {float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeOut if height is larger or YAHOO.util.Easing.easeIn if it is smaller)
+ * @return {YAHOO.ext.Element} this
+ */
+ setHeight : function(height, animate, duration, onComplete, easing){
+ height = this.adjustHeight(height);
+ if(!animate || !YAHOO.util.Anim){
+ this.dom.style.height = this.addUnits(height);
+ //YAHOO.util.Dom.setStyle(this.dom, 'height', this.addUnits(height));
+ }else{
+ this.anim({height: {to: height}}, duration, onComplete,
+ easing || (height > this.getHeight() ? YAHOO.util.Easing.easeOut : YAHOO.util.Easing.easeIn));
+ }
+ return this;
+ },
+
+ /**
+ * Set the size of the element. If animation is true, both width an height will be animated concurrently.
+ * @param {Number} width The new width
+ * @param {Number} height The new height
+ * @param {Boolean} animate (optional) Animate the transition (Default is false)
+ * @param {float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeBoth)
+ * @return {YAHOO.ext.Element} this
+ */
+ setSize : function(width, height, animate, duration, onComplete, easing){
+ width = this.adjustWidth(width); height = this.adjustHeight(height);
+ if(!animate || !YAHOO.util.Anim){
+ this.dom.style.width = this.addUnits(width);
+ this.dom.style.height = this.addUnits(height);
+ }else{
+ this.anim({width: {to: width}, height: {to: height}}, duration, onComplete, easing);
+ }
+ return this;
+ },
+
+ /**
+ * Sets the element's position and size in one shot. If animation is true then width, height, x and y will be animated concurrently.
+ * @param {Number} x X value for new position (coordinates are page-based)
+ * @param {Number} y Y value for new position (coordinates are page-based)
+ * @param {Number} width The new width
+ * @param {Number} height The new height
+ * @param {Boolean} animate (optional) Animate the transition (Default is false)
+ * @param {float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeBoth)
+ * @return {YAHOO.ext.Element} this
+ */
+ setBounds : function(x, y, width, height, animate, duration, onComplete, easing){
+ if(!animate || !YAHOO.util.Anim){
+ this.setSize(width, height);
+ this.setLocation(x, y);
+ }else{
+ width = this.adjustWidth(width); height = this.adjustHeight(height);
+ this.anim({points: {to: [x, y]}, width: {to: width}, height: {to: height}}, duration, onComplete, easing, YAHOO.util.Motion);
+ }
+ return this;
+ },
+
+ /**
+ * Sets the element's position and size the the specified region. If animation is true then width, height, x and y will be animated concurrently.
+ * @param {YAHOO.util.Region} region The region to fill
+ * @param {Boolean} animate (optional) Animate the transition (Default is false)
+ * @param {float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeBoth)
+ * @return {YAHOO.ext.Element} this
+ */
+ setRegion : function(region, animate, duration, onComplete, easing){
+ this.setBounds(region.left, region.top, region.right-region.left, region.bottom-region.top, animate, duration, onComplete, easing);
+ return this;
+ },
+
+ /**
+ * Appends an event handler to this element
+ * @param {String} eventName The type of event to listen for
+ * @param {Function} handler The method the event invokes
+ * @param {Object} scope (optional) An arbitrary object that will be
+ * passed as a parameter to the handler
+ * @param {boolean} override (optional) If true, the obj passed in becomes
+ * the execution scope of the listener
+ * @return {YAHOO.ext.Element} this
+ */
+ addListener : function(eventName, handler, scope, override){
+ YAHOO.util.Event.addListener(this.dom, eventName, handler, scope || this, true);
+ return this;
+ },
+ /**
+ * Appends an event handler to this element that is buffered. If the event is triggered more than once
+ * in the specified time-frame, only the last one actually fires.
+ * @param {String} eventName The type of event to listen for
+ * @param {Function} handler The method the event invokes
+ * @param {Object} scope (optional) The scope (this object) for the handler
+ * @param {Number} millis (optional) The number of milliseconds to buffer (defaults to 250)
+ * @return {Function} The wrapped function that was created (can be used to remove the listener)
+ */
+ bufferedListener : function(eventName, fn, scope, millis){
+ var task = new YAHOO.ext.util.DelayedTask();
+ scope = scope || this;
+ var newFn = function(e){
+ task.delay(millis || 250, fn, scope, Array.prototype.slice.call(arguments, 0));
+ }
+ this.addListener(eventName, newFn);
+ return newFn;
+ },
+
+
+ /**
+ * Appends an event handler to this element. The difference between this function and addListener is this
+ * function prevents the default action, and if set stops propagation (bubbling) as well
+ * @param {String} eventName The type of event to listen for
+ * @param {Boolean} stopPropagation Whether to also stopPropagation (bubbling)
+ * @param {Function} handler The method the event invokes
+ * @param {Object} scope (optional) An arbitrary object that will be
+ * passed as a parameter to the handler
+ * @param {boolean} override (optional) If true, the obj passed in becomes
+ * the execution scope of the listener
+ * @return {YAHOO.ext.Element} this
+ */
+ addHandler : function(eventName, stopPropagation, handler, scope, override){
+ var fn = YAHOO.ext.Element.createStopHandler(stopPropagation, handler, scope || this, true);
+ YAHOO.util.Event.addListener(this.dom, eventName, fn);
+ return fn;
+ },
+
+ /**
+ * Appends an event handler to this element (Same as addListener)
+ * @param {String} eventName The type of event to listen for
+ * @param {Function} handler The method the event invokes
+ * @param {Object} scope (optional) An arbitrary object that will be
+ * passed as a parameter to the handler
+ * @param {boolean} override (optional) If true, the obj passed in becomes
+ * the execution scope of the listener
+ * @return {YAHOO.ext.Element} this
+ */
+ on : function(eventName, handler, scope, override){
+ YAHOO.util.Event.addListener(this.dom, eventName, handler, scope || this, true);
+ return this;
+ },
+
+ /**
+ * Append a managed listener - See {@link YAHOO.ext.EventObject} for more details. Use mon() for a shorter version.
+ * @param {String} eventName The type of event to listen for
+ * @param {Function} fn The method the event invokes
+ * @param {Object} scope (optional) An arbitrary object that will be
+ * passed as a parameter to the handler
+ * @param {boolean} override (optional) If true, the obj passed in becomes
+ * the execution scope of the listener
+ * @return {Function} The EventManager wrapped function that can be used to remove the listener
+ */
+ addManagedListener : function(eventName, fn, scope, override){
+ return YAHOO.ext.EventManager.on(this.dom, eventName, fn, scope || this, true);
+ },
+
+ /**
+ * Append a managed listener (shorthanded for {@link #addManagedListener})
+ * @param {String} eventName The type of event to listen for
+ * @param {Function} fn The method the event invokes
+ * @param {Object} scope (optional) An arbitrary object that will be
+ * passed as a parameter to the handler
+ * @param {boolean} override (optional) If true, the obj passed in becomes
+ * the execution scope of the listener
+ * @return {Function} The EventManager wrapped function that can be used to remove the listener
+ */
+ mon : function(eventName, fn, scope, override){
+ return YAHOO.ext.EventManager.on(this.dom, eventName, fn, scope || this, true);
+ },
+ /**
+ * Removes an event handler from this element
+ * @param {String} sType the type of event to remove
+ * @param {Function} fn the method the event invokes
+ * @param {Object} scope
+ * @return {YAHOO.ext.Element} this
+ */
+ removeListener : function(eventName, handler, scope){
+ YAHOO.util.Event.removeListener(this.dom, eventName, handler);
+ return this;
+ },
+
+ /**
+ * Removes all previous added listeners from this element
+ * @return {YAHOO.ext.Element} this
+ */
+ removeAllListeners : function(){
+ YAHOO.util.Event.purgeElement(this.dom);
+ return this;
+ },
+
+
+ /**
+ * Set the opacity of the element
+ * @param {Float} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
+ * @param {Boolean} animate (optional) Animate (fade) the transition (Default is false)
+ * @param {Float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeOut if height is larger or YAHOO.util.Easing.easeIn if it is smaller)
+ * @return {YAHOO.ext.Element} this
+ */
+ setOpacity : function(opacity, animate, duration, onComplete, easing){
+ if(!animate || !YAHOO.util.Anim){
+ YAHOO.util.Dom.setStyle(this.dom, 'opacity', opacity);
+ }else{
+ this.anim({opacity: {to: opacity}}, duration, onComplete, easing);
+ }
+ return this;
+ },
+
+ /**
+ * Gets the left X coordinate
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
+ */
+ getLeft : function(local){
+ if(!local){
+ return this.getX();
+ }else{
+ return parseInt(this.getStyle('left'), 10) || 0;
+ }
+ },
+
+ /**
+ * Gets the right X coordinate of the element (element X position + element width)
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
+ */
+ getRight : function(local){
+ if(!local){
+ return this.getX() + this.getWidth();
+ }else{
+ return (this.getLeft(true) + this.getWidth()) || 0;
+ }
+ },
+
+ /**
+ * Gets the top Y coordinate
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
+ */
+ getTop : function(local) {
+ if(!local){
+ return this.getY();
+ }else{
+ return parseInt(this.getStyle('top'), 10) || 0;
+ }
+ },
+
+ /**
+ * Gets the bottom Y coordinate of the element (element Y position + element height)
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
+ */
+ getBottom : function(local){
+ if(!local){
+ return this.getY() + this.getHeight();
+ }else{
+ return (this.getTop(true) + this.getHeight()) || 0;
+ }
+ },
+
+ /**
+ * Set the element as absolute positioned with the specified z-index
+ * @param {Number} zIndex (optional)
+ * @return {YAHOO.ext.Element} this
+ */
+ setAbsolutePositioned : function(zIndex){
+ this.setStyle('position', 'absolute');
+ if(zIndex){
+ this.setStyle('z-index', zIndex);
+ }
+ return this;
+ },
+
+ /**
+ * Set the element as relative positioned with the specified z-index
+ * @param {Number} zIndex (optional)
+ * @return {YAHOO.ext.Element} this
+ */
+ setRelativePositioned : function(zIndex){
+ this.setStyle('position', 'relative');
+ if(zIndex){
+ this.setStyle('z-index', zIndex);
+ }
+ return this;
+ },
+
+ /**
+ * Clear positioning back to the default when the document was loaded
+ * @return {YAHOO.ext.Element} this
+ */
+ clearPositioning : function(){
+ this.setStyle('position', '');
+ this.setStyle('left', '');
+ this.setStyle('right', '');
+ this.setStyle('top', '');
+ this.setStyle('bottom', '');
+ return this;
+ },
+
+ /**
+ * Gets an object with all CSS positioning properties. Useful along with setPostioning to get
+ * snapshot before performing an update and then restoring the element.
+ * @return {Object}
+ */
+ getPositioning : function(){
+ return {
+ 'position' : this.getStyle('position'),
+ 'left' : this.getStyle('left'),
+ 'right' : this.getStyle('right'),
+ 'top' : this.getStyle('top'),
+ 'bottom' : this.getStyle('bottom')
+ };
+ },
+
+ /**
+ * Gets the width of the border(s) for the specified side(s)
+ * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
+ * passing lr would get the border (l)eft width + the border (r)ight width.
+ * @return {Number} The width of the sides passed added together
+ */
+ getBorderWidth : function(side){
+ return this.addStyles(side, YAHOO.ext.Element.borders);
+ },
+
+ /**
+ * Gets the width of the padding(s) for the specified side(s)
+ * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
+ * passing lr would get the padding (l)eft + the padding (r)ight.
+ * @return {Number} The padding of the sides passed added together
+ */
+ getPadding : function(side){
+ return this.addStyles(side, YAHOO.ext.Element.paddings);
+ },
+
+ /**
+ * Set positioning with an object returned by getPositioning().
+ * @param {Object} posCfg
+ * @return {YAHOO.ext.Element} this
+ */
+ setPositioning : function(positionCfg){
+ if(positionCfg.position)this.setStyle('position', positionCfg.position);
+ if(positionCfg.left)this.setLeft(positionCfg.left);
+ if(positionCfg.right)this.setRight(positionCfg.right);
+ if(positionCfg.top)this.setTop(positionCfg.top);
+ if(positionCfg.bottom)this.setBottom(positionCfg.bottom);
+ return this;
+ },
+
+
+ /**
+ * Quick set left and top adding default units
+ * @return {YAHOO.ext.Element} this
+ */
+ setLeftTop : function(left, top){
+ this.dom.style.left = this.addUnits(left);
+ this.dom.style.top = this.addUnits(top);
+ return this;
+ },
+
+ /**
+ * Move this element relative to it's current position.
+ * @param {String} direction Possible values are: 'l','left' - 'r','right' - 't','top','up' - 'b','bottom','down'.
+ * @param {Number} distance How far to move the element in pixels
+ * @param {Boolean} animate (optional) Animate the movement (Default is false)
+ * @param {Float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use.
+ * @return {YAHOO.ext.Element} this
+ */
+ move : function(direction, distance, animate, duration, onComplete, easing){
+ var xy = this.getXY();
+ direction = direction.toLowerCase();
+ switch(direction){
+ case 'l':
+ case 'left':
+ this.moveTo(xy[0]-distance, xy[1], animate, duration, onComplete, easing);
+ break;
+ case 'r':
+ case 'right':
+ this.moveTo(xy[0]+distance, xy[1], animate, duration, onComplete, easing);
+ break;
+ case 't':
+ case 'top':
+ case 'up':
+ this.moveTo(xy[0], xy[1]-distance, animate, duration, onComplete, easing);
+ break;
+ case 'b':
+ case 'bottom':
+ case 'down':
+ this.moveTo(xy[0], xy[1]+distance, animate, duration, onComplete, easing);
+ break;
+ }
+ return this;
+ },
+
+ /**
+ * Store the current overflow setting and clip overflow on the element - use {@link #unclip} to remove
+ * @return {YAHOO.ext.Element} this
+ */
+ clip : function(){
+ if(!this.isClipped){
+ this.isClipped = true;
+ this.originalClip = {
+ 'o': this.getStyle('overflow'),
+ 'x': this.getStyle('overflow-x'),
+ 'y': this.getStyle('overflow-y')
+ };
+ this.setStyle('overflow', 'hidden');
+ this.setStyle('overflow-x', 'hidden');
+ this.setStyle('overflow-y', 'hidden');
+ }
+ return this;
+ },
+
+ /**
+ * Return clipping (overflow) to original clipping before clip() was called
+ * @return {YAHOO.ext.Element} this
+ */
+ unclip : function(){
+ if(this.isClipped){
+ this.isClipped = false;
+ var o = this.originalClip;
+ if(o.o){this.setStyle('overflow', o.o);}
+ if(o.x){this.setStyle('overflow-x', o.x);}
+ if(o.y){this.setStyle('overflow-y', o.y);}
+ }
+ return this;
+ },
+
+ /**
+ * Align this element with another element.
+ * @param {String/HTMLElement/YAHOO.ext.Element} element The element to align to.
+ * @param {String} position The position to align to. Possible values are 'tl' - top left, 'tr' - top right, 'bl' - bottom left, and 'br' - bottom right.
+ * @param {Array} offsets (optional) Offset the positioning by [x, y]
+ * @param {Boolean} animate (optional) Animate the movement (Default is false)
+ * @param {Float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use.
+ * @return {YAHOO.ext.Element} this
+ */
+ alignTo : function(element, position, offsets, animate, duration, onComplete, easing){
+ var otherEl = getEl(element);
+ if(!otherEl){
+ return this; // must not exist
+ }
+ offsets = offsets || [0, 0];
+ var r = otherEl.getRegion();
+ position = position.toLowerCase();
+ switch(position){
+ case 'bl':
+ this.moveTo(r.left + offsets[0], r.bottom + offsets[1],
+ animate, duration, onComplete, easing);
+ break;
+ case 'br':
+ this.moveTo(r.right + offsets[0], r.bottom + offsets[1],
+ animate, duration, onComplete, easing);
+ break;
+ case 'tl':
+ this.moveTo(r.left + offsets[0], r.top + offsets[1],
+ animate, duration, onComplete, easing);
+ break;
+ case 'tr':
+ this.moveTo(r.right + offsets[0], r.top + offsets[1],
+ animate, duration, onComplete, easing);
+ break;
+ }
+ return this;
+ },
+
+ /**
+ * Clears any opacity settings from this element. Required in some cases for IE.
+ * @return {YAHOO.ext.Element} this
+ */
+ clearOpacity : function(){
+ if (window.ActiveXObject) {
+ this.dom.style.filter = '';
+ } else {
+ this.dom.style.opacity = '';
+ this.dom.style['-moz-opacity'] = '';
+ this.dom.style['-khtml-opacity'] = '';
+ }
+ return this;
+ },
+
+ /**
+ * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
+ * @param {Boolean} animate (optional) Animate (fade) the transition (Default is false)
+ * @param {Float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeBoth)
+ * @return {YAHOO.ext.Element} this
+ */
+ hide : function(animate, duration, onComplete, easing){
+ this.setVisible(false, animate, duration, onComplete, easing);
+ return this;
+ },
+
+ /**
+ * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
+ * @param {Boolean} animate (optional) Animate (fade in) the transition (Default is false)
+ * @param {Float} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {Function} onComplete (optional) Function to call when animation completes.
+ * @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeBoth)
+ * @return {YAHOO.ext.Element} this
+ */
+ show : function(animate, duration, onComplete, easing){
+ this.setVisible(true, animate, duration, onComplete, easing);
+ return this;
+ },
+
+ /**
+ * @private Test if size has a unit, otherwise appends the default
+ */
+ addUnits : function(size){
+ if(size === '' || size == 'auto' || typeof size == 'undefined'){
+ return size;
+ }
+ if(typeof size == 'number' || !YAHOO.ext.Element.unitPattern.test(size)){
+ return size + this.defaultUnit;
+ }
+ return size;
+ },
+
+ /**
+ * Temporarily enables offsets (width,height,x,y) for an element with display:none, use endMeasure() when done.
+ * @return {YAHOO.ext.Element} this
+ */
+ beginMeasure : function(){
+ var el = this.dom;
+ if(el.offsetWidth || el.offsetHeight){
+ return this; // offsets work already
+ }
+ var changed = [];
+ var p = this.dom; // start with this element
+ while((!el.offsetWidth && !el.offsetHeight) && p && p.tagName && p.tagName.toLowerCase() != 'body'){
+ if(YAHOO.util.Dom.getStyle(p, 'display') == 'none'){
+ changed.push({el: p, visibility: YAHOO.util.Dom.getStyle(p, 'visibility')});
+ p.style.visibility = 'hidden';
+ p.style.display = 'block';
+ }
+ p = p.parentNode;
+ }
+ this._measureChanged = changed;
+ return this;
+
+ },
+
+ /**
+ * Restores displays to before beginMeasure was called
+ * @return {YAHOO.ext.Element} this
+ */
+ endMeasure : function(){
+ var changed = this._measureChanged;
+ if(changed){
+ for(var i = 0, len = changed.length; i < len; i++) {
+ var r = changed[i];
+ r.el.style.visibility = r.visibility;
+ r.el.style.display = 'none';
+ }
+ this._measureChanged = null;
+ }
+ return this;
+ },
+
+ /**
+ * Update the innerHTML of this element, optionally searching for and processing scripts
+ * @param {String} html The new HTML
+ * @param {Boolean} loadScripts (optional) true to look for and process scripts
+ * @param {Function} callback For async script loading you can be noticed when the update completes
+ * @return {YAHOO.ext.Element} this
+ */
+ update : function(html, loadScripts, callback){
+ if(typeof html == 'undefined'){
+ html = '';
+ }
+ if(loadScripts !== true){
+ this.dom.innerHTML = html;
+ if(typeof callback == 'function'){
+ callback();
+ }
+ return this;
+ }
+ var id = YAHOO.util.Dom.generateId();
+ var dom = this.dom;
+
+ html += '';
+
+ YAHOO.util.Event.onAvailable(id, function(){
+ var hd = document.getElementsByTagName("head")[0];
+ var re = /(?:
+
+ * @param {String} color (optional) The highlight color. Should be a 6 char hex color (no #). (defaults to ffff9c)
+ * @param {Object} options (optional) Object literal with any of the options listed above
+ */
+ highlight : function(color, options){
+ color = color || 'ffff9c';
+ options = options || {};
+ attr = options.attr || 'background-color';
+ var origColor = this.getColor(attr);
+ endColor = (options.endColor || origColor) || 'ffffff';
+ var dom = this.dom;
+ var cb = function(){
+ YAHOO.util.Dom.setStyle(dom, attr, origColor || '');
+ if(options.callback){
+ options.callback.call(options.scope || window);
+ }
+ };
+ var o = {};
+ o[attr] = {from: color, to: endColor};
+ this.anim(o, options.duration || .75, cb, options.easing || YAHOO.util.Easing.easeNone, YAHOO.util.ColorAnim);
+ return this;
+ }
+};
+
+/**
+ * true to automatically adjust width and height settings for box-model issues (default to true)
+ */
+YAHOO.ext.Element.prototype.autoBoxAdjust = true;
+/**
+ * true to automatically detect display mode and use display instead of visibility with show()/hide() (defaults to false).
+ * To enable this globally:
+el.highlight('ff0000', {
+ attr: 'background-color',
+ endColor: (current color) or 'ffffff'
+ callback: yourFunction,
+ scope: yourObject,
+ easing: YAHOO.util.Easing.easeNone,
+ duration: .75
+});
+
+ */
+YAHOO.ext.Element.prototype.autoDisplayMode = true;
+
+YAHOO.ext.Element.unitPattern = /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i;
+/**
+ * Visibility mode constant - Use visibility to hide element
+ * @static
+ * @type Number
+ */
+YAHOO.ext.Element.VISIBILITY = 1;
+/**
+ * Visibility mode constant - Use display to hide element
+ * @static
+ * @type Number
+ */
+YAHOO.ext.Element.DISPLAY = 2;
+
+YAHOO.ext.Element.blockElements = /^(?:address|blockquote|center|dir|div|dl|fieldset|form|h\d|hr|isindex|menu|ol|ul|p|pre|table|dd|dt|li|tbody|tr|td|thead|tfoot|iframe)$/i;
+YAHOO.ext.Element.borders = {l: 'border-left-width', r: 'border-right-width', t: 'border-top-width', b: 'border-bottom-width'};
+YAHOO.ext.Element.paddings = {l: 'padding-left', r: 'padding-right', t: 'padding-top', b: 'padding-bottom'};
+YAHOO.ext.Element.margins = {l: 'margin-left', r: 'margin-right', t: 'margin-top', b: 'margin-bottom'};
+
+/**
+ * @private Call out to here so we make minimal closure
+ */
+YAHOO.ext.Element.createStopHandler = function(stopPropagation, handler, scope, override){
+ return function(e){
+ if(e){
+ if(stopPropagation){
+ YAHOO.util.Event.stopEvent(e);
+ }else {
+ YAHOO.util.Event.preventDefault(e);
+ }
+ }
+ handler.call(override && scope ? scope : window, e, scope);
+ };
+};
+
+/**
+ * @private
+ */
+YAHOO.ext.Element.cache = {};
+
+/**
+ * Static method to retreive Element objects. Uses simple caching to consistently return the same object.
+ * Automatically fixes if an object was recreated with the same id via AJAX or DOM.
+ * @param {String/HTMLElement/Element} el The id of the element or the element to wrap (must have an id). If you pass in an element, it is returned
+ * @return {Element} The element object
+ * @static
+ */
+YAHOO.ext.Element.get = function(){
+ var doc = document; // prevent IE dom lookup on every call to getEl
+ var docEl;
+ var E = YAHOO.ext.Element;
+ var D = YAHOO.util.Dom;
+
+ return function(el){
+ if(!el){ return null; }
+ if(el instanceof E){
+ if(el != docEl){
+ el.dom = doc.getElementById(el.id); // refresh dom element in case no longer valid
+ E.cache[el.id] = el; // in case it was created directly with Element(), let's cache it
+ }
+ return el;
+ }else if(el.isComposite){
+ return el;
+ }else if(el instanceof Array){
+ return E.select(el);
+ }else if(el == doc){
+ // create a bogus element object representing the document object
+ if(!docEl){
+ var f = function(){};
+ f.prototype = E.prototype;
+ docEl = new f();
+ docEl.dom = doc;
+ }
+ return docEl;
+ }
+ var key = el;
+ if(typeof el != 'string'){ // must be an element
+ D.generateId(el, 'elgen-');
+ key = el.id;
+ }
+ var element = E.cache[key];
+ if(!element){
+ element = new E(key);
+ if(!element.dom) return null;
+ E.cache[key] = element;
+ }else{
+ element.dom = doc.getElementById(key);
+ }
+ return element;
+ };
+}();
+
+/*
+ * Gets the globally shared flyweight Element. Use sparingly for
+ * bulk operations where a unique instance isn't needed.
+ * Do not store a reference to this element - the dom node
+ * can be overwritten by other code.
+ */
+YAHOO.ext.Element.fly = function(el){
+ var E = YAHOO.ext.Element;
+ if(typeof el == 'string'){
+ el = document.getElementById(el);
+ }
+ if(!E._flyweight){
+ var f = function(){};
+ f.prototype = E.prototype;
+ E._flyweight = new f();
+ }
+ E._flyweight.dom = el;
+ return E._flyweight;
+}
+
+/*
+ * Shorthand function for YAHOO.ext.Element.get()
+ */
+getEl = YAHOO.ext.Element.get;
+
+YAHOO.util.Event.addListener(window, 'unload', function(){
+ YAHOO.ext.Element.cache = null;
+});
+
diff --git a/frontend/beta/js/YUI-extensions/EventManager.js b/frontend/beta/js/YUI-extensions/EventManager.js
new file mode 100644
index 0000000..f9db759
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/EventManager.js
@@ -0,0 +1,456 @@
+
+/**
+ * @class YAHOO.ext.EventManager
+ * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
+ * several useful events directly.
+ * See {@link YAHOO.ext.EventObject} for more details on normalized event objects.
+ * @singleton
+ */
+YAHOO.ext.EventManager = new function(){
+ var docReadyEvent;
+ var docReadyProcId;
+ var docReadyState = false;
+ this.ieDeferSrc = false;
+ var resizeEvent;
+ var resizeTask;
+
+ var fireDocReady = function(){
+ if(!docReadyState){
+ docReadyState = true;
+ if(docReadyProcId){
+ clearInterval(docReadyProcId);
+ }
+ if(docReadyEvent){
+ docReadyEvent.fire();
+ }
+ }
+ };
+
+ var initDocReady = function(){
+ docReadyEvent = new YAHOO.util.CustomEvent('documentready');
+ if(document.addEventListener) {
+ YAHOO.util.Event.on(document, "DOMContentLoaded", fireDocReady);
+ }else if(YAHOO.ext.util.Browser.isIE){
+ // inspired by http://www.thefutureoftheweb.com/blog/2006/6/adddomloadevent
+ document.write('YAHOO.ext.Element.prototype.autoDisplayMode = true;
');
+ YAHOO.util.Event.on('ie-deferred-loader', 'readystatechange', function(){
+ if(this.readyState == 'complete'){
+ fireDocReady();
+ }
+ });
+ }else if(YAHOO.ext.util.Browser.isSafari){
+ docReadyProcId = setInterval(function(){
+ var rs = document.readyState;
+ if(rs == 'loaded' || rs == 'complete') {
+ fireDocReady();
+ }
+ }, 10);
+ }
+ // no matter what, make sure it fires on load
+ YAHOO.util.Event.on(window, 'load', fireDocReady);
+ };
+ /**
+ * Places a simple wrapper around an event handler to override the browser event
+ * object with a YAHOO.ext.EventObject
+ * @param {Function} fn The method the event invokes
+ * @param {Object} scope An object that becomes the scope of the handler
+ * @param {boolean} override If true, the obj passed in becomes
+ * the execution scope of the listener
+ * @return {Function} The wrapped function
+ */
+ this.wrap = function(fn, scope, override){
+ var wrappedFn = function(e){
+ YAHOO.ext.EventObject.setEvent(e);
+ fn.call(override ? scope || window : window, YAHOO.ext.EventObject, scope);
+ };
+ return wrappedFn;
+ };
+
+ /**
+ * Appends an event handler
+ *
+ * @param {Object} element The html element to assign the
+ * event to
+ * @param {String} eventName The type of event to append
+ * @param {Function} fn The method the event invokes
+ * @param {Object} scope An object that becomes the scope of the handler
+ * @param {boolean} override If true, the obj passed in becomes
+ * the execution scope of the listener
+ * @return {Function} The wrapper function created (to be used to remove the listener if necessary)
+ */
+ this.addListener = function(element, eventName, fn, scope, override){
+ var wrappedFn = this.wrap(fn, scope, override);
+ YAHOO.util.Event.addListener(element, eventName, wrappedFn);
+ return wrappedFn;
+ };
+
+ /**
+ * Removes an event handler
+ *
+ * @param {Object} element The html element to remove the
+ * event from
+ * @param {String} eventName The type of event to append
+ * @param {Function} wrappedFn The wrapper method returned when adding the listener
+ * @return {Boolean} True if a listener was actually removed
+ */
+ this.removeListener = function(element, eventName, wrappedFn){
+ return YAHOO.util.Event.removeListener(element, eventName, wrappedFn);
+ };
+
+ /**
+ * Appends an event handler (shorthand for addListener)
+ *
+ * @param {Object} element The html element to assign the
+ * event to
+ * @param {String} eventName The type of event to append
+ * @param {Function} fn The method the event invokes
+ * @param {Object} scope An arbitrary object that will be
+ * passed as a parameter to the handler
+ * @param {boolean} override If true, the obj passed in becomes
+ * the execution scope of the listener
+ * @return {Function} The wrapper function created (to be used to remove the listener if necessary)
+ * @method
+ */
+ this.on = this.addListener;
+
+ /**
+ * Fires when the document is ready (before onload and before images are loaded). Can be
+ * accessed shorthanded Ext.onReady().
+ * @param {Function} fn The method the event invokes
+ * @param {Object} scope An object that becomes the scope of the handler
+ * @param {boolean} override If true, the obj passed in becomes
+ * the execution scope of the listener
+ */
+ this.onDocumentReady = function(fn, scope, override){
+ if(docReadyState){ // if it already fired
+ fn.call(override? scope || window : window, scope);
+ return;
+ }
+ if(!docReadyEvent){
+ initDocReady();
+ }
+ docReadyEvent.subscribe(fn, scope, override);
+ }
+
+ /**
+ * Fires when the window is resized and provides resize event buffering (50 milliseconds), passes new viewport width and height to handlers.
+ * @param {Function} fn The method the event invokes
+ * @param {Object} scope An object that becomes the scope of the handler
+ * @param {boolean} override If true, the obj passed in becomes
+ * the execution scope of the listener
+ */
+ this.onWindowResize = function(fn, scope, override){
+ if(!resizeEvent){
+ resizeEvent = new YAHOO.util.CustomEvent('windowresize');
+ resizeTask = new YAHOO.ext.util.DelayedTask(function(){
+ resizeEvent.fireDirect(YAHOO.util.Dom.getViewportWidth(), YAHOO.util.Dom.getViewportHeight());
+ });
+ YAHOO.util.Event.on(window, 'resize', function(){
+ resizeTask.delay(50);
+ });
+ }
+ resizeEvent.subscribe(fn, scope, override);
+ };
+
+ /**
+ * Removes the passed window resize listener.
+ * @param {Function} fn The method the event invokes
+ * @param {Object} scope The scope of handler
+ */
+ this.removeResizeListener = function(fn, scope){
+ if(resizeEvent){
+ resizeEvent.unsubscribe(fn, scope);
+ }
+ };
+
+ this.fireResize = function(){
+ if(resizeEvent){
+ resizeEvent.fireDirect(YAHOO.util.Dom.getViewportWidth(), YAHOO.util.Dom.getViewportHeight());
+ }
+ };
+};
+
+YAHOO.ext.onReady = YAHOO.ext.EventManager.onDocumentReady;
+
+/**
+ * @class YAHOO.ext.EventObject
+ * EventObject exposes the Yahoo! UI Event functionality directly on the object
+ * passed to your event handler. It exists mostly for convenience. It also fixes the annoying null checks automatically to cleanup your code
+ * (All the YAHOO.util.Event methods throw javascript errors if the passed event is null).
+ * To get an EventObject instead of the standard browser event,
+ * your must register your listener thru the {@link YAHOO.ext.EventManager} or directly on an Element
+ * with {@link YAHOO.ext.Element#addManagedListener} or the shorthanded equivalent {@link YAHOO.ext.Element#mon}.
+ * Example:
+ *
+ * @singleton
+ */
+YAHOO.ext.EventObject = new function(){
+ /** The normal browser event */
+ this.browserEvent = null;
+ /** The button pressed in a mouse event */
+ this.button = -1;
+ /** True if the shift key was down during the event */
+ this.shiftKey = false;
+ /** True if the control key was down during the event */
+ this.ctrlKey = false;
+ /** True if the alt key was down during the event */
+ this.altKey = false;
+
+ /** Key constant @type Number */
+ this.BACKSPACE = 8;
+ /** Key constant @type Number */
+ this.TAB = 9;
+ /** Key constant @type Number */
+ this.RETURN = 13;
+ /** Key constant @type Number */
+ this.ESC = 27;
+ /** Key constant @type Number */
+ this.SPACE = 32;
+ /** Key constant @type Number */
+ this.PAGEUP = 33;
+ /** Key constant @type Number */
+ this.PAGEDOWN = 34;
+ /** Key constant @type Number */
+ this.END = 35;
+ /** Key constant @type Number */
+ this.HOME = 36;
+ /** Key constant @type Number */
+ this.LEFT = 37;
+ /** Key constant @type Number */
+ this.UP = 38;
+ /** Key constant @type Number */
+ this.RIGHT = 39;
+ /** Key constant @type Number */
+ this.DOWN = 40;
+ /** Key constant @type Number */
+ this.DELETE = 46;
+ /** Key constant @type Number */
+ this.F5 = 116;
+
+ /** @private */
+ this.setEvent = function(e){
+ if(e == this){ // already wrapped
+ return this;
+ }
+ this.browserEvent = e;
+ if(e){
+ this.button = e.button;
+ this.shiftKey = e.shiftKey;
+ this.ctrlKey = e.ctrlKey;
+ this.altKey = e.altKey;
+ }else{
+ this.button = -1;
+ this.shiftKey = false;
+ this.ctrlKey = false;
+ this.altKey = false;
+ }
+ return this;
+ };
+
+ /**
+ * Stop the event. Calls YAHOO.util.Event.stopEvent() if the event is not null.
+ */
+ this.stopEvent = function(){
+ if(this.browserEvent){
+ YAHOO.util.Event.stopEvent(this.browserEvent);
+ }
+ };
+
+ /**
+ * Prevents the browsers default handling of the event. Calls YAHOO.util.Event.preventDefault() if the event is not null.
+ */
+ this.preventDefault = function(){
+ if(this.browserEvent){
+ YAHOO.util.Event.preventDefault(this.browserEvent);
+ }
+ };
+
+ /** @private */
+ this.isNavKeyPress = function(){
+ return (this.browserEvent.keyCode && this.browserEvent.keyCode >= 33 && this.browserEvent.keyCode <= 40);
+ };
+
+ /**
+ * Cancels bubbling of the event. Calls YAHOO.util.Event.stopPropagation() if the event is not null.
+ */
+ this.stopPropagation = function(){
+ if(this.browserEvent){
+ YAHOO.util.Event.stopPropagation(this.browserEvent);
+ }
+ };
+
+ /**
+ * Gets the key code for the event. Returns value from YAHOO.util.Event.getCharCode() if the event is not null.
+ * @return {Number}
+ */
+ this.getCharCode = function(){
+ if(this.browserEvent){
+ return YAHOO.util.Event.getCharCode(this.browserEvent);
+ }
+ return null;
+ };
+
+ /**
+ * Returns a browsers key for a keydown event
+ * @return {Number} The key code
+ */
+ this.getKey = function(){
+ if(this.browserEvent){
+ return this.browserEvent.keyCode || this.browserEvent.charCode;
+ }
+ return null;
+ };
+
+ /**
+ * Gets the x coordinate of the event. Returns value from YAHOO.util.Event.getPageX() if the event is not null.
+ * @return {Number}
+ */
+ this.getPageX = function(){
+ if(this.browserEvent){
+ return YAHOO.util.Event.getPageX(this.browserEvent);
+ }
+ return null;
+ };
+
+ /**
+ * Gets the y coordinate of the event. Returns value from YAHOO.util.Event.getPageY() if the event is not null.
+ * @return {Number}
+ */
+ this.getPageY = function(){
+ if(this.browserEvent){
+ return YAHOO.util.Event.getPageY(this.browserEvent);
+ }
+ return null;
+ };
+
+ /**
+ * Gets the time of the event. Returns value from YAHOO.util.Event.getTime() if the event is not null.
+ * @return {Number}
+ */
+ this.getTime = function(){
+ if(this.browserEvent){
+ return YAHOO.util.Event.getTime(this.browserEvent);
+ }
+ return null;
+ };
+
+ /**
+ * Gets the page coordinates of the event. Returns value from YAHOO.util.Event.getXY() if the event is not null.
+ * @return {Array} The xy values like [x, y]
+ */
+ this.getXY = function(){
+ if(this.browserEvent){
+ return YAHOO.util.Event.getXY(this.browserEvent);
+ }
+ return [];
+ };
+
+ /**
+ * Gets the target for the event. Returns value from YAHOO.util.Event.getTarget() if the event is not null.
+ * @return {HTMLelement}
+ */
+ this.getTarget = function(){
+ if(this.browserEvent){
+ return YAHOO.util.Event.getTarget(this.browserEvent);
+ }
+ return null;
+ };
+
+ /**
+ * Walk up the DOM looking for a particular target - if the default target matches, it is returned.
+ * @param {String} className The class name to look for or null
+ * @param {String} tagName (optional) The tag name to look for
+ * @return {HTMLelement}
+ */
+ this.findTarget = function(className, tagName){
+ if(tagName) tagName = tagName.toLowerCase();
+ if(this.browserEvent){
+ function isMatch(el){
+ if(!el){
+ return false;
+ }
+ if(className && !YAHOO.util.Dom.hasClass(el, className)){
+ return false;
+ }
+ if(tagName && el.tagName.toLowerCase() != tagName){
+ return false;
+ }
+ return true;
+ };
+
+ var t = this.getTarget();
+ if(!t || isMatch(t)){
+ return t;
+ }
+ var p = t.parentNode;
+ var b = document.body;
+ while(p && p != b){
+ if(isMatch(p)){
+ return p;
+ }
+ p = p.parentNode;
+ }
+ }
+ return null;
+ };
+ /**
+ * Gets the related target. Returns value from YAHOO.util.Event.getRelatedTarget() if the event is not null.
+ * @return {HTMLElement}
+ */
+ this.getRelatedTarget = function(){
+ if(this.browserEvent){
+ return YAHOO.util.Event.getRelatedTarget(this.browserEvent);
+ }
+ return null;
+ };
+
+ /**
+ * Normalizes mouse wheel delta across browsers
+ * @return {Number} The delta
+ */
+ this.getWheelDelta = function(){
+ var e = this.browserEvent;
+ var delta = 0;
+ if(e.wheelDelta){ /* IE/Opera. */
+ delta = e.wheelDelta/120;
+ /* In Opera 9, delta differs in sign as compared to IE. */
+ if(window.opera) delta = -delta;
+ }else if(e.detail){ /* Mozilla case. */
+ delta = -e.detail/3;
+ }
+ return delta;
+ };
+
+ /**
+ * Returns true if the control, shift or alt key was pressed during this event.
+ * @return {Boolean}
+ */
+ this.hasModifier = function(){
+ return this.ctrlKey || this.altKey || this.shiftKey;
+ };
+
+ /**
+ * Returns true if the target of this event equals el or is a child of el
+ * @param {String/HTMLElement/Element} el
+ * @return {Boolean}
+ */
+ this.within = function(el){
+ el = getEl(el);
+ var t = this.getTarget();
+ return t && el && (el.dom == t || YAHOO.util.Dom.isAncestor(el.dom, t));
+ }
+}();
+
+
diff --git a/frontend/beta/js/YUI-extensions/JSON.js b/frontend/beta/js/YUI-extensions/JSON.js
new file mode 100644
index 0000000..ffc9573
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/JSON.js
@@ -0,0 +1,132 @@
+/**
+ * @class YAHOO.ext.util.JSON
+ * Modified version of Douglas Crockford's json.js that doesn't
+ * mess with the Object prototype
+ * http://www.json.org/js.html
+ * @singleton
+ */
+YAHOO.ext.util.JSON = new function(){
+ var useHasOwn = {}.hasOwnProperty ? true : false;
+
+ // crashes Safari in some instances
+ //var validRE = /^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/;
+
+ var pad = function(n) {
+ return n < 10 ? '0' + n : n;
+ };
+
+ var m = {
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ };
+
+ var encodeString = function(s){
+ if (/["\\\x00-\x1f]/.test(s)) {
+ return '"' + s.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+ var c = m[b];
+ if(c){
+ return c;
+ }
+ c = b.charCodeAt();
+ return '\\u00' +
+ Math.floor(c / 16).toString(16) +
+ (c % 16).toString(16);
+ }) + '"';
+ }
+ return '"' + s + '"';
+ };
+
+ var encodeArray = function(o){
+ var a = ['['], b, i, l = o.length, v;
+ for (i = 0; i < l; i += 1) {
+ v = o[i];
+ switch (typeof v) {
+ case 'undefined':
+ case 'function':
+ case 'unknown':
+ break;
+ default:
+ if (b) {
+ a.push(',');
+ }
+ a.push(v === null ? "null" : YAHOO.ext.util.JSON.encode(v));
+ b = true;
+ }
+ }
+ a.push(']');
+ return a.join('');
+ };
+
+ var encodeDate = function(o){
+ return '"' + o.getFullYear() + '-' +
+ pad(o.getMonth() + 1) + '-' +
+ pad(o.getDate()) + 'T' +
+ pad(o.getHours()) + ':' +
+ pad(o.getMinutes()) + ':' +
+ pad(o.getSeconds()) + '"';
+ };
+
+ /**
+ * Encodes an Object, Array or other value
+ * @param {Mixed} o The variable to encode
+ * @return {String} The JSON string
+ */
+ this.encode = function(o){
+ if(typeof o == 'undefined' || o === null){
+ return 'null';
+ }else if(o instanceof Array){
+ return encodeArray(o);
+ }else if(o instanceof Date){
+ return encodeDate(o);
+ }else if(typeof o == 'string'){
+ return encodeString(o);
+ }else if(typeof o == 'number'){
+ return isFinite(o) ? String(o) : "null";
+ }else if(typeof o == 'boolean'){
+ return String(o);
+ }else {
+ var a = ['{'], b, i, v;
+ for (var i in o) {
+ if(!useHasOwn || o.hasOwnProperty(i)) {
+ v = o[i];
+ switch (typeof v) {
+ case 'undefined':
+ case 'function':
+ case 'unknown':
+ break;
+ default:
+ if(b){
+ a.push(',');
+ }
+ a.push(this.encode(i), ':',
+ v === null ? "null" : this.encode(v));
+ b = true;
+ }
+ }
+ }
+ a.push('}');
+ return a.join('');
+ }
+ };
+
+ /**
+ * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a SyntaxError.
+ * @param {String} json The JSON string
+ * @return {Object} The resulting object
+ */
+ this.decode = function(json){
+ // although crockford had a good idea, this line crashes safari in some instances
+ //try{
+ //if(validRE.test(json)) {
+ return eval('(' + json + ')');
+ // }
+ // }catch(e){
+ // }
+ // throw new SyntaxError("parseJSON");
+ };
+}();
diff --git a/frontend/beta/js/YUI-extensions/KeyMap.js b/frontend/beta/js/YUI-extensions/KeyMap.js
new file mode 100644
index 0000000..c5af567
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/KeyMap.js
@@ -0,0 +1,135 @@
+/**
+ * @class YAHOO.ext.KeyMap
+ * Handles mapping keys to actions for an element. One key map can be used for multiple actions.
+ * A KeyMap can also handle a string representation of keys.
+ fu<>nction handleClick(e){ // e is not a standard event object, it is a YAHOO.ext.EventObject
+ e.preventDefault();
+ var target = e.getTarget();
+ ...
+ }
+ var myDiv = getEl('myDiv');
+ myDiv.mon('click', handleClick);
+ //or
+ YAHOO.ext.EventManager.on('myDiv', 'click', handleClick);
+ YAHOO.ext.EventManager.addListener('myDiv', 'click', handleClick);
+
+ * Usage:
+
+* Note: A KepMap starts enabled
+* @constructor
+* @param {String/HTMLElement/YAHOO.ext.Element} el The element to bind to
+* @param {Object} config The config
+* @param {String} eventName (optional) The event to bind to (Defaults to "keydown").
+ */
+YAHOO.ext.KeyMap = function(el, config, eventName){
+ this.el = getEl(el);
+ this.eventName = eventName || 'keydown';
+ this.bindings = [];
+ if(config instanceof Array){
+ for(var i = 0, len = config.length; i < len; i++){
+ this.addBinding(config[i]);
+ }
+ }else{
+ this.addBinding(config);
+ }
+ this.keyDownDelegate = YAHOO.ext.EventManager.wrap(this.handleKeyDown, this, true);
+ this.enable();
+}
+
+YAHOO.ext.KeyMap.prototype = {
+ /**
+ * Add a new binding to this KeyMap
+ * @param {Object} config A single KeyMap config
+ */
+ addBinding : function(config){
+ var keyCode = config.key,
+ shift = config.shift,
+ ctrl = config.ctrl,
+ alt = config.alt,
+ fn = config.fn,
+ scope = config.scope;
+ if(typeof keyCode == 'string'){
+ var ks = [];
+ var keyString = keyCode.toUpperCase();
+ for(var j = 0, len = keyString.length; j < len; j++){
+ ks.push(keyString.charCodeAt(j));
+ }
+ keyCode = ks;
+ }
+ var keyArray = keyCode instanceof Array;
+ var handler = function(e){
+ if((!shift || e.shiftKey) && (!ctrl || e.ctrlKey) && (!alt || e.altKey)){
+ var k = e.getKey();
+ if(keyArray){
+ for(var i = 0, len = keyCode.length; i < len; i++){
+ if(keyCode[i] == k){
+ fn.call(scope || window, k, e);
+ return;
+ }
+ }
+ }else{
+ if(k == keyCode){
+ fn.call(scope || window, k, e);
+ }
+ }
+ }
+ };
+ this.bindings.push(handler);
+ },
+
+ handleKeyDown : function(e){
+ if(this.enabled){ //just in case
+ var b = this.bindings;
+ for(var i = 0, len = b.length; i < len; i++){
+ b[i](e);
+ }
+ }
+ },
+
+ /**
+ * Returns true if this KepMap is enabled
+ * @return {Boolean}
+ */
+ isEnabled : function(){
+ return this.enabled;
+ },
+
+ /**
+ * Enable this KeyMap
+ */
+ enable: function(){
+ if(!this.enabled){
+ this.el.on(this.eventName, this.keyDownDelegate);
+ this.enabled = true;
+ }
+ },
+
+ /**
+ * Disable this KeyMap
+ */
+ disable: function(){
+ if(this.enabled){
+ this.el.removeListener(this.eventName, this.keyDownDelegate);
+ this.enabled = false;
+ }
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/Layer.js b/frontend/beta/js/YUI-extensions/Layer.js
new file mode 100644
index 0000000..9eeaf7c
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/Layer.js
@@ -0,0 +1,246 @@
+/**
+ * @class YAHOO.ext.Layer
+ * @extends YAHOO.ext.Element
+ * An extended Element object that supports a shadow and shim, constrain to viewport and
+ * automatic maintaining of shadow/shim positions.
+ * @cfg {Boolean} shim False to disable the iframe shim in browsers which need one (defaults to true)
+ * @cfg {String/Boolean} shadow True to create a shadow element with default class "ylayer-shadow" or
+ * you can pass a string with a css class name. False turns off the shadow.
+ * @cfg {Object} dh DomHelper object config to create element with (defaults to {tag: 'div', cls: 'ylayer'}).
+ * @cfg {Boolean} constrain False to disable constrain to viewport (defaults to true)
+ * @cfg {String} cls CSS class to add to the element
+ * @cfg {Number} zindex Starting z-index (defaults to 11000!)
+ * @cfg {Number} shadowOffset Offset for the shadow (defaults to 3)
+ * @constructor
+ * @param {Object} config
+ * @param {String/HTMLElement} existingEl (optional) Uses an existing dom element. If the element is not found it creates it.
+ */
+YAHOO.ext.Layer = function(config, existingEl){
+ config = config || {};
+ var dh = YAHOO.ext.DomHelper;
+ if(existingEl){
+ this.dom = YAHOO.util.Dom.get(existingEl);
+ }
+ if(!this.dom){
+ var o = config.dh || {tag: 'div', cls: 'ylayer'};
+ this.dom = dh.insertBefore(document.body.firstChild, o);
+ }
+ if(config.cls){
+ this.addClass(config.cls);
+ }
+ this.constrain = config.constrain !== false;
+ this.visibilityMode = YAHOO.ext.Element.VISIBILITY;
+ this.id = YAHOO.util.Dom.generateId(this.dom);
+ var zindex = (config.zindex || parseInt(this.getStyle('z-index'), 10)) || 11000;
+ this.setAbsolutePositioned(zindex);
+ if(config.shadow){
+ var cls = (typeof config.shadow == 'string' ? config.shadow : 'ylayer-shadow');
+ this.shadow = dh.insertBefore(this.dom,
+ {tag: 'div', cls: cls}, true);
+ this.shadowOffset = config.shadowOffset || 3;
+ this.shadow.setAbsolutePositioned(zindex-1);
+ }else{
+ this.shadowOffset = 0;
+ }
+ var b = YAHOO.ext.util.Browser;
+ if(config.shim !== false && (b.isIE || (b.isGecko && b.isMac))){
+ this.shim = this.createShim();
+ this.shim.setOpacity(0);
+ this.shim.setAbsolutePositioned(zindex-2);
+ }
+ this.hide();
+};
+YAHOO.extendX(YAHOO.ext.Layer, YAHOO.ext.Element, {
+ sync : function(doShow){
+ if(this.isVisible() && (this.shadow || this.shim)){
+ var b = this.getBox();
+ if(this.shim){
+ if(doShow){
+ this.shim.show();
+ }
+ this.shim.setBox(b);
+ }
+ if(this.shadow){
+ if(doShow){
+ this.shadow.show();
+ }
+ b.x += this.shadowOffset;
+ b.y += this.shadowOffset;
+ this.shadow.setBox(b);
+ }
+ }
+ },
+
+ syncLocalXY : function(){
+ var l = this.getLeft(true);
+ var t = this.getTop(true);
+ if(this.shim){
+ this.shim.setLeftTop(l, t);
+ }
+ if(this.shadow){
+ this.shadow.setLeftTop(l + this.shadowOffset,
+ t + this.shadowOffset);
+ }
+ },
+
+ hideUnders : function(negOffset){
+ if(this.shadow){
+ this.shadow.hide();
+ if(negOffset){
+ this.shadow.setLeftTop(-10000,-10000);
+ }
+ }
+ if(this.shim){
+ this.shim.hide();
+ if(negOffset){
+ this.shim.setLeftTop(-10000,-10000);
+ }
+ }
+ },
+
+ constrainXY : function(){
+ if(this.constrain){
+ var vw = YAHOO.util.Dom.getViewportWidth(),
+ vh = YAHOO.util.Dom.getViewportHeight();
+ var xy = this.getXY();
+ var x = xy[0], y = xy[1];
+ var w = this.dom.offsetWidth+this.shadowOffset, h = this.dom.offsetHeight+this.shadowOffset;
+ // only move it if it needs it
+ var moved = false;
+ // first validate right/bottom
+ if(x + w > vw){
+ x = vw - w;
+ moved = true;
+ }
+ if(y + h > vh){
+ y = vh - h;
+ moved = true;
+ }
+ // then make sure top/left isn't negative
+ if(x < 0){
+ x = 0;
+ moved = true;
+ }
+ if(y < 0){
+ y = 0;
+ moved = true;
+ }
+ if(moved){
+ xy = [x, y];
+ this.lastXY = xy;
+ this.beforeAction();
+ YAHOO.ext.Layer.superclass.setXY.call(this, xy);
+ this.sync(true);
+ }
+ }
+ },
+
+ setVisible : function(v, a, d, c, e){
+ if(this.lastXY){
+ YAHOO.ext.Layer.superclass.setXY.call(this, this.lastXY);
+ }
+ if(a && v){
+ var cb = function(){
+ this.sync(true);
+ if(c){
+ c();
+ }
+ }.createDelegate(this);
+ YAHOO.ext.Layer.superclass.setVisible.call(this, true, true, d, cb, e);
+ }else{
+ if(!v){
+ this.hideUnders(true);
+ }
+ var cb = c;
+ if(a){
+ cb = function(){
+ this.setLeftTop(-10000,-10000);
+ if(c){
+ c();
+ }
+ }.createDelegate(this);
+ }
+ YAHOO.ext.Layer.superclass.setVisible.call(this, v, a, d, cb, e);
+ if(v){
+ this.sync(true);
+ }else if(!a){
+ this.setLeftTop(-10000,-10000);
+ }
+ }
+ },
+
+ beforeAction : function(){
+ if(this.shadow){
+ this.shadow.hide();
+ }
+ },
+
+ setXY : function(xy, a, d, c, e){
+ this.lastXY = xy;
+ this.beforeAction();
+ var cb = this.createCB(c);
+ YAHOO.ext.Layer.superclass.setXY.call(this, xy, a, d, cb, e);
+ if(!a){
+ cb();
+ }
+ },
+
+ createCB : function(c){
+ var el = this;
+ return function(){
+ el.constrainXY();
+ el.sync(true);
+ if(c){
+ c();
+ }
+ };
+ },
+
+ setX : function(x, a, d, c, e){
+ this.setXY([x, this.getY()], a, d, c, e);
+ },
+
+ setY : function(y, a, d, c, e){
+ this.setXY([this.getX(), y], a, d, c, e);
+ },
+
+ setSize : function(w, h, a, d, c, e){
+ this.beforeAction();
+ var cb = this.createCB(c);
+ YAHOO.ext.Layer.superclass.setSize.call(this, w, h, a, d, cb, e);
+ if(!a){
+ cb();
+ }
+ },
+
+ setWidth : function(w, a, d, c, e){
+ this.beforeAction();
+ var cb = this.createCB(c);
+ YAHOO.ext.Layer.superclass.setWidth.call(this, w, a, d, cb, e);
+ if(!a){
+ cb();
+ }
+ },
+
+ setHeight : function(h, a, d, c, e){
+ this.beforeAction();
+ var cb = this.createCB(c);
+ YAHOO.ext.Layer.superclass.setHeight.call(this, h, a, d, cb, e);
+ if(!a){
+ cb();
+ }
+ },
+
+ setBounds : function(x, y, w, h, a, d, c, e){
+ this.beforeAction();
+ var cb = this.createCB(c);
+ if(!a){
+ YAHOO.ext.Layer.superclass.setXY.call(this, [x, y]);
+ YAHOO.ext.Layer.superclass.setSize.call(this, w, h, a, d, cb, e);
+ cb();
+ }else{
+ YAHOO.ext.Layer.superclass.setBounds.call(this, x, y, w, h, a, d, cb, e);
+ }
+ return this;
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/MixedCollection.js b/frontend/beta/js/YUI-extensions/MixedCollection.js
new file mode 100644
index 0000000..2e3d88a
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/MixedCollection.js
@@ -0,0 +1,344 @@
+/**
+ * @class YAHOO.ext.util.MixedCollection
+ * A Collection class that maintains both numeric indexes and keys and exposes events.
+ // map one key by key code
+ var map = new YAHOO.ext.KeyMap('my-element', {
+ key: 13,
+ fn: myHandler,
+ scope: myObject
+ });
+
+ // map multiple keys to one action by string
+ var map = new YAHOO.ext.KeyMap('my-element', {
+ key: "a\r\n\t",
+ fn: myHandler,
+ scope: myObject
+ });
+
+ // map multiple keys to multiple actions by strings and array of codes
+ var map = new YAHOO.ext.KeyMap('my-element', [
+ {
+ key: [10,13],
+ fn: function(){ alert('Return was pressed'); }
+ }, {
+ key: "abc",
+ fn: function(){ alert('a, b or c was pressed'); }
+ }, {
+ key: "\t",
+ ctrl:true,
+ shift:true,
+ fn: function(){ alert('Control + shift + tab was pressed.'); }
+ }
+]);
+
+ * @constructor
+ * @param {Boolean} allowFunctions True if the addAll function should add function references
+ * to the collection.
+ */
+YAHOO.ext.util.MixedCollection = function(allowFunctions){
+ this.items = [];
+ this.keys = [];
+ this.events = {
+ /**
+ * @event clear
+ * Fires when the collection is cleared.
+ */
+ 'clear' : new YAHOO.util.CustomEvent('clear'),
+ /**
+ * @event add
+ * Fires when an item is added to the collection.
+ * @param {Number} index The index at which the item was added.
+ * @param {Object} o The item added.
+ * @param {String} key The key associated with the added item.
+ */
+ 'add' : new YAHOO.util.CustomEvent('add'),
+ /**
+ * @event replace
+ * Fires when an item is replaced in the collection.
+ * @param {String} key he key associated with the new added.
+ * @param {Object} old The item being replaced.
+ * @param {Object} new The new item.
+ */
+ 'replace' : new YAHOO.util.CustomEvent('replace'),
+ /**
+ * @event remove
+ * Fires when an item is removed from the collection.
+ * @param {Object} o The item being removed.
+ * @param {String} key (optional) The key associated with the removed item.
+ */
+ 'remove' : new YAHOO.util.CustomEvent('remove')
+ }
+ this.allowFunctions = allowFunctions === true;
+};
+
+YAHOO.extendX(YAHOO.ext.util.MixedCollection, YAHOO.ext.util.Observable, {
+ allowFunctions : false,
+
+/**
+ * Adds an item to the collection.
+ * @param {String} key The key to associate with the item
+ * @param {Object} o The item to add.
+ * @return {Object} The item added.
+ */
+ add : function(key, o){
+ if(arguments.length == 1){
+ o = arguments[0];
+ key = this.getKey(o);
+ }
+ this.items.push(o);
+ if(typeof key != 'undefined' && key != null){
+ this.items[key] = o;
+ this.keys.push(key);
+ }
+ this.fireEvent('add', this.items.length-1, o, key);
+ return o;
+ },
+
+/**
+ * MixedCollection has a generic way to fetch keys if you implement getKey.
+
+ // normal way
+ var mc = new YAHOO.ext.util.MixedCollection();
+ mc.add(someEl.dom.id, someEl);
+ mc.add(otherEl.dom.id, otherEl);
+ //and so on
+
+ // using getKey
+ var mc = new YAHOO.ext.util.MixedCollection();
+ mc.getKey = function(el){
+ return el.dom.id;
+ }
+ mc.add(someEl);
+ mc.add(otherEl);
+ // etc
+
+ * @param o {Object} The item for which to find the key.
+ * @return {Object} The key for the passed item.
+ */
+ getKey : function(o){
+ return null;
+ },
+
+/**
+ * Replaces an item in the collection.
+ * @param {String} key The key associated with the item to replace, or the item to replace.
+ * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate with that key.
+ * @return {Object} The new item.
+ */
+ replace : function(key, o){
+ if(arguments.length == 1){
+ o = arguments[0];
+ key = this.getKey(o);
+ }
+ if(typeof this.items[key] == 'undefined'){
+ return this.add(key, o);
+ }
+ var old = this.items[key];
+ if(typeof key == 'number'){ // array index key
+ this.items[key] = o;
+ }else{
+ var index = this.indexOfKey(key);
+ this.items[index] = o;
+ this.items[key] = o;
+ }
+ this.fireEvent('replace', key, old, o);
+ return o;
+ },
+
+/**
+ * Adds all elements of an Array or an Object to the collection.
+ * @param {Object/Array} objs An Object containing properties which will be added to the collection, or
+ * an Array of values, each of which are added to the collection.
+ */
+ addAll : function(objs){
+ if(arguments.length > 1 || objs instanceof Array){
+ var args = arguments.length > 1 ? arguments : objs;
+ for(var i = 0, len = args.length; i < len; i++){
+ this.add(args[i]);
+ }
+ }else{
+ for(var key in objs){
+ if(this.allowFunctions || typeof objs[key] != 'function'){
+ this.add(objs[key], key);
+ }
+ }
+ }
+ },
+
+/**
+ * Executes the specified function once for every item in the collection, passing each
+ * item as the first and only parameter.
+ * @param {Function} fn The function to execute for each item.
+ * @param {Object} scope (optional) The scope in which to execute the function.
+ */
+ each : function(fn, scope){
+ for(var i = 0, len = this.items.length; i < len; i++){
+ fn.call(scope || window, this.items[i]);
+ }
+ },
+
+/**
+ * Executes the specified function once for every key in the collection, passing each
+ * key, and its associated item as the first two parameters.
+ * @param {Function} fn The function to execute for each item.
+ * @param {Object} scope (optional) The scope in which to execute the function.
+ */
+ eachKey : function(fn, scope){
+ for(var i = 0, len = this.keys.length; i < len; i++){
+ fn.call(scope || window, this.keys[i], this.items[i]);
+ }
+ },
+
+/**
+ * Returns the first item in the collection which elicits a true return value from the
+ * passed selection function.
+ * @param {Function} fn The selection function to execute for each item.
+ * @param {Object} scope (optional) The scope in which to execute the function.
+ * @return {Object} The first item in the collection which returned true from the selection function.
+ */
+ find : function(fn, scope){
+ for(var i = 0, len = this.items.length; i < len; i++){
+ if(fn.call(scope || window, this.items[i])){
+ return this.items[i];
+ }
+ }
+ return null;
+ },
+
+/**
+ * Inserts an item at the specified index in the collection.
+ * @param {Number} index The index to insert the item at.
+ * @param {String} key The key to associate with the new item, or the item itself.
+ * @param {Object} o (optional) If the second parameter was a key, the new item.
+ * @return {Object} The item inserted.
+ */
+ insert : function(index, key, o){
+ if(arguments.length == 2){
+ o = arguments[1];
+ key = this.getKey(o);
+ }
+ if(index >= this.items.length){
+ return this.add(o, key);
+ }
+ this.items.splice(index, 0, o);
+ if(typeof key != 'undefined' && key != null){
+ this.items[key] = o;
+ this.keys.splice(index, 0, key);
+ }
+ this.fireEvent('add', index, o, key);
+ return o;
+ },
+
+/**
+ * Removed an item from the collection.
+ * @param {Object} o The item to remove.
+ * @return {Object} The item removed.
+ */
+ remove : function(o){
+ var index = this.indexOf(o);
+ this.items.splice(index, 1);
+ if(typeof this.keys[index] != 'undefined'){
+ var key = this.keys[index];
+ this.keys.splice(index, 1);
+ delete this.items[key];
+ }
+ this.fireEvent('remove', o);
+ return o;
+ },
+
+/**
+ * Remove an item from a specified index in the collection.
+ * @param {Number} index The index within the collection of the item to remove.
+ */
+ removeAt : function(index){
+ this.items.splice(index, 1);
+ var key = this.keys[index];
+ if(typeof key != 'undefined'){
+ this.keys.splice(index, 1);
+ delete this.items[key];
+ }
+ this.fireEvent('remove', o, key);
+ },
+
+/**
+ * Removed an item associated with the passed key fom the collection.
+ * @param {String} key The key of the item to remove.
+ */
+ removeKey : function(key){
+ var o = this.items[key];
+ var index = this.indexOf(o);
+ this.items.splice(index, 1);
+ this.keys.splice(index, 1);
+ delete this.items[key];
+ this.fireEvent('remove', o, key);
+ },
+
+/**
+ * Returns the number of items in the collection.
+ * @return {Number} the number of items in the collection.
+ */
+ getCount : function(){
+ return this.items.length;
+ },
+
+/**
+ * Returns index within the collection of the passed Object.
+ * @param {Object} o The item to find the index of.
+ * @return {Number} index of the item.
+ */
+ indexOf : function(o){
+ if(!this.items.indexOf){
+ for(var i = 0, len = this.items.length; i < len; i++){
+ if(this.items[i] == o) return i;
+ }
+ return -1;
+ }else{
+ return this.items.indexOf(o);
+ }
+ },
+
+/**
+ * Returns index within the collection of the passed key.
+ * @param {String} key The key to find the index of.
+ * @return {Number} index of the key.
+ */
+ indexOfKey : function(key){
+ if(!this.keys.indexOf){
+ for(var i = 0, len = this.keys.length; i < len; i++){
+ if(this.keys[i] == key) return i;
+ }
+ return -1;
+ }else{
+ return this.keys.indexOf(key);
+ }
+ },
+
+/**
+ * Returns the item associated with the passed key.
+ * @param {String/Number} key The key or index of the item.
+ * @return {Object} The item associated with the passed key.
+ */
+ item : function(key){
+ return this.items[key];
+ },
+
+/**
+ * Returns true if the collection contains the passed Object as an item.
+ * @param {Object} o The Object to look for in the collection.
+ * @return {Boolean} True if the collection contains the Object as an item.
+ */
+ contains : function(o){
+ return this.indexOf(o) != -1;
+ },
+
+/**
+ * Returns true if the collection contains the passed Object as a key.
+ * @param {String} key The key to look for in the collection.
+ * @return {Boolean} True if the collection contains the Object as a key.
+ */
+ containsKey : function(key){
+ return typeof this.items[key] != 'undefined';
+ },
+
+/**
+ * Removes all items from the collection.
+ */
+ clear : function(o){
+ this.items = [];
+ this.keys = [];
+ this.fireEvent('clear');
+ },
+
+/**
+ * Returns the first item in the collection.
+ * @return {Object} the first item in the collection..
+ */
+ first : function(){
+ return this.items[0];
+ },
+
+/**
+ * Returns the last item in the collection.
+ * @return {Object} the last item in the collection..
+ */
+ last : function(){
+ return this.items[this.items.length];
+ }
+});
+/**
+ * Returns the item associated with the passed key or index.
+ * @method
+ * @param {String/Number} key The key or index of the item.
+ * @return {Object} The item associated with the passed key.
+ */
+YAHOO.ext.util.MixedCollection.prototype.get = YAHOO.ext.util.MixedCollection.prototype.item;
diff --git a/frontend/beta/js/YUI-extensions/State.js b/frontend/beta/js/YUI-extensions/State.js
new file mode 100644
index 0000000..76a9618
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/State.js
@@ -0,0 +1,264 @@
+YAHOO.namespace('ext.state');
+/**
+ * @class YAHOO.ext.state.Provider
+ * Abstract base class for provider implementations. This class provides methods
+ * for encoding and decoding typed variables including dates and defines the
+ * Provider interface.
+ */
+YAHOO.ext.state.Provider = function(){
+ YAHOO.ext.state.Provider.superclass.constructor.call(this);
+ /**
+ * @event statechange
+ * Fires when a state change occurs.
+ * @param {Provider} this
+ * @param {String} key The state key which was changed
+ * @param {String} value The encoded value for the state
+ */
+ this.events = {
+ 'statechange': new YAHOO.util.CustomEvent('statechange')
+ };
+ this.state = {};
+};
+YAHOO.extendX(YAHOO.ext.state.Provider, YAHOO.ext.util.Observable, {
+ /**
+ * Get the current value for a key.
+ * @param {String} name
+ * @param {Mixed} defaultValue
+ * @return {Mixed}
+ */
+ get : function(name, defaultValue){
+ return typeof this.state[name] == 'undefined' ?
+ defaultValue : this.state[name];
+ },
+
+ /**
+ * Clear a value from the state.
+ */
+ clear : function(name){
+ delete this.state[name];
+ this.fireEvent('statechange', this, name, null);
+ },
+
+ /**
+ * Set the value for a key.
+ * @param {String} name
+ * @param {Mixed} value
+ */
+ set : function(name, value){
+ this.state[name] = value;
+ this.fireEvent('statechange', this, name, value);
+ },
+
+ /**
+ * Decodes a string previously encoded with {@link #encodeValue}.
+ * @param {String} value
+ * @return {Mixed} The value
+ */
+ decodeValue : function(cookie){
+ var re = /^(a|n|d|b|s|o)\:(.*)$/;
+ var matches = re.exec(unescape(cookie));
+ if(!matches || !matches[1]) return; // non state cookie
+ var type = matches[1];
+ var v = matches[2];
+ switch(type){
+ case 'n':
+ return parseFloat(v);
+ case 'd':
+ return new Date(Date.parse(v));
+ case 'b':
+ return (v == '1');
+ case 'a':
+ var all = [];
+ var values = v.split('^');
+ for(var i = 0, len = values.length; i < len; i++){
+ all.push(this.decodeValue(values[i]))
+ }
+ return all;
+ case 'o':
+ var all = {};
+ var values = v.split('^');
+ for(var i = 0, len = values.length; i < len; i++){
+ var kv = values[i].split('=');
+ all[kv[0]] = this.decodeValue(kv[1]);
+ }
+ return all;
+ default:
+ return v;
+ }
+ },
+
+ /**
+ * Encode a value including type information.
+ * @param {Mixed} value
+ * @return {String}
+ */
+ encodeValue : function(v){
+ var enc;
+ if(typeof v == 'number'){
+ enc = 'n:' + v;
+ }else if(typeof v == 'boolean'){
+ enc = 'b:' + (v ? '1' : '0');
+ }else if(v instanceof Date){
+ enc = 'd:' + v.toGMTString();
+ }else if(v instanceof Array){
+ var flat = '';
+ for(var i = 0, len = v.length; i < len; i++){
+ flat += this.encodeValue(v[i]);
+ if(i != len-1) flat += '^';
+ }
+ enc = 'a:' + flat;
+ }else if(typeof v == 'object'){
+ var flat = '';
+ for(var key in v){
+ if(typeof v[key] != 'function'){
+ flat += key + '=' + this.encodeValue(v[key]) + '^';
+ }
+ }
+ enc = 'o:' + flat.substring(0, flat.length-1);
+ }else{
+ enc = 's:' + v;
+ }
+ return escape(enc);
+ }
+});
+
+/**
+ * @class YAHOO.ext.state.Manager
+ * This is the global state manager. By default all components that are "state aware" check this class
+ * for state information if you don't pass them a custom state provider. In order for this class
+ * to be useful, it must be initialized with a provider when your application initializes.
+
+ * @singleton
+ */
+YAHOO.ext.state.Manager = new function(){
+ var provider = new YAHOO.ext.state.Provider();
+
+ return {
+ /**
+ * Configures the default provider for your application.
+ * @param {Provider} stateProvider
+ */
+ setProvider : function(stateProvider){
+ provider = stateProvider;
+ },
+
+ /**
+ * Get the current value for a key.
+ * @param {String} name
+ * @param {Mixed} defaultValue
+ * @return {Mixed}
+ */
+ get : function(key, defaultValue){
+ return provider.get(key, defaultValue);
+ },
+
+ /**
+ * Set the value for a key.
+ * @param {String} name
+ * @param {Mixed} value
+ */
+ set : function(key, value){
+ provider.set(key, value);
+ },
+
+ /**
+ * Clear a value from the state.
+ */
+ clear : function(key){
+ provider.clear(key);
+ },
+
+ /**
+ * Gets the currently configured provider.
+ * @return {Provider}
+ */
+ getProvider : function(){
+ return provider;
+ }
+ };
+}();
+
+/**
+ * @class YAHOO.ext.state.CookieProvider
+ * @extends YAHOO.ext.state.Provider
+ * The default Provider implementation. The example below includes all valid configuration options and their
+ * default values.
+
+// in your initialization function
+init : function(){
+ YAHOO.ext.state.Manager.setProvider(new YAHOO.ext.state.CookieProvider());
+ ...
+ // supposed you have a {@link YAHOO.ext.BorderLayout}
+ var layout = new YAHOO.ext.BorderLayout(...);
+ layout.restoreState();
+ // or a {YAHOO.ext.BasicDialog}
+ var dialog = new YAHOO.ext.BasicDialog(...);
+ dialog.restoreState();
+
+ * @constructor
+ * Create a new CookieProvider
+ * @param {Object} config The configuration object
+ */
+YAHOO.ext.state.CookieProvider = function(config){
+ YAHOO.ext.state.CookieProvider.superclass.constructor.call(this);
+ this.path = '/';
+ this.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
+ this.domain = null;
+ this.secure = false;
+ YAHOO.ext.util.Config.apply(this, config);
+ this.state = this.readCookies();
+};
+
+YAHOO.extendX(YAHOO.ext.state.CookieProvider, YAHOO.ext.state.Provider, {
+ set : function(name, value){
+ if(typeof value == 'undefined' || value === null){
+ this.clear(name);
+ return;
+ }
+ this.setCookie(name, value);
+ YAHOO.ext.state.CookieProvider.superclass.set.call(this, name, value);
+ },
+
+ clear : function(name){
+ this.clearCookie(name);
+ YAHOO.ext.state.CookieProvider.superclass.clear.call(this, name);
+ },
+
+ readCookies : function(){
+ var cookies = {};
+ var c = document.cookie + ';';
+ var re = /\s?(.*?)=(.*?);/g;
+ var matches;
+ while((matches = re.exec(c)) != null){
+ var name = matches[1];
+ var value = matches[2];
+ if(name && name.substring(0,3) == 'ys-'){
+ cookies[name.substr(3)] = this.decodeValue(value);
+ }
+ }
+ return cookies;
+ },
+
+ setCookie : function(name, value){
+ document.cookie = "ys-"+ name + "=" + this.encodeValue(value) +
+ ((this.expires == null) ? "" : ("; expires=" + this.expires.toGMTString())) +
+ ((this.path == null) ? "" : ("; path=" + this.path)) +
+ ((this.domain == null) ? "" : ("; domain=" + this.domain)) +
+ ((this.secure == true) ? "; secure" : "");
+ },
+
+ clearCookie : function(name){
+ document.cookie = "ys-" + name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" +
+ ((this.path == null) ? "" : ("; path=" + this.path)) +
+ ((this.domain == null) ? "" : ("; domain=" + this.domain)) +
+ ((this.secure == true) ? "; secure" : "");
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/UpdateManager.js b/frontend/beta/js/YUI-extensions/UpdateManager.js
new file mode 100644
index 0000000..c2eb23f
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/UpdateManager.js
@@ -0,0 +1,484 @@
+/**
+ * @class YAHOO.ext.UpdateManager
+ * @extends YAHOO.ext.util.Observable
+ * Provides AJAX-style update for Element object using Yahoo
+ * UI library YAHOO.util.Connect functionality.
+ var cp = new YAHOO.ext.state.CookieProvider({
+ path: '/',
+ expires: new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
+ domain: null,
+ secure: false
+ })
+ YAHOO.ext.state.Manager.setProvider(cp);
+
+ * Usage:
+ *
+ * @requires YAHOO.ext.Element
+ * @requires YAHOO.util.Dom
+ * @requires YAHOO.util.Event
+ * @requires YAHOO.util.CustomEvent
+ * @requires YAHOO.util.Connect
+ * @constructor
+ * Create new UpdateManager directly.
+ * @param {String/HTMLElement/YAHOO.ext.Element} el The element to update
+ * @param {Boolean} forceNew (optional) By default the constructor checks to see if the passed element already has an UpdateManager and if it does it returns the same instance. This will skip that check (useful for extending this class).
+ */
+YAHOO.ext.UpdateManager = function(el, forceNew){
+ el = YAHOO.ext.Element.get(el);
+ if(!forceNew && el.updateManager){
+ return el.updateManager;
+ }
+ /**
+ * The Element object
+ * @type YAHOO.ext.Element
+ */
+ this.el = el;
+ /**
+ * Cached url to use for refreshes. Overwritten every time update() is called unless 'discardUrl' param is set to true.
+ * @type String
+ */
+ this.defaultUrl = null;
+ this.beforeUpdate = new YAHOO.util.CustomEvent('UpdateManager.beforeUpdate');
+ this.onUpdate = new YAHOO.util.CustomEvent('UpdateManager.onUpdate');
+ this.onFailure = new YAHOO.util.CustomEvent('UpdateManager.onFailure');
+
+ this.events = {
+ /**
+ * @event beforeupdate
+ * Fired before an update is made, return false from your handler and the update is cancelled.
+ * @param {YAHOO.ext.Element} el
+ * @param {String/Object/Function} url
+ * @param {String/Object} params
+ */
+ 'beforeupdate': this.beforeUpdate,
+ /**
+ * @event update
+ * Fired after successful update is made.
+ * @param {YAHOO.ext.Element} el
+ * @param {Object} oResponseObject The YAHOO.util.Connect response Object
+ */
+ 'update': this.onUpdate,
+ /**
+ * @event failure
+ * Fired on update failure. Uses fireDirect with signature: (oElement, oResponseObject)
+ * @param {YAHOO.ext.Element} el
+ * @param {Object} oResponseObject The YAHOO.util.Connect response Object
+ */
+ 'failure': this.onFailure
+ };
+
+ /**
+ * Blank page URL to use with SSL file uploads (Defaults to YAHOO.ext.UpdateManager.defaults.sslBlankUrl or 'about:blank').
+ * @type String
+ */
+ this.sslBlankUrl = YAHOO.ext.UpdateManager.defaults.sslBlankUrl;
+ /**
+ * Whether to append unique parameter on get request to disable caching (Defaults to YAHOO.ext.UpdateManager.defaults.disableCaching or false).
+ * @type Boolean
+ */
+ this.disableCaching = YAHOO.ext.UpdateManager.defaults.disableCaching;
+ /**
+ * Text for loading indicator (Defaults to YAHOO.ext.UpdateManager.defaults.indicatorText or '<div class="loading-indicator">Loading...</div>').
+ * @type String
+ */
+ this.indicatorText = YAHOO.ext.UpdateManager.defaults.indicatorText;
+ /**
+ * Whether to show indicatorText when loading (Defaults to YAHOO.ext.UpdateManager.defaults.showLoadIndicator or true).
+ * @type String
+ */
+ this.showLoadIndicator = YAHOO.ext.UpdateManager.defaults.showLoadIndicator;
+ /**
+ * Timeout for requests or form posts in seconds (Defaults to YAHOO.ext.UpdateManager.defaults.timeout or 30 seconds).
+ * @type Number
+ */
+ this.timeout = YAHOO.ext.UpdateManager.defaults.timeout;
+
+ /**
+ * True to process scripts in the output (Defaults to YAHOO.ext.UpdateManager.defaults.loadScripts (false)).
+ * @type Boolean
+ */
+ this.loadScripts = YAHOO.ext.UpdateManager.defaults.loadScripts;
+
+ /**
+ * YAHOO.util.Connect transaction object of current executing transaction
+ */
+ this.transaction = null;
+
+ /**
+ * @private
+ */
+ this.autoRefreshProcId = null;
+ /**
+ * Delegate for refresh() prebound to 'this', use myUpdater.refreshDelegate.createCallback(arg1, arg2) to bind arguments
+ * @type Function
+ */
+ this.refreshDelegate = this.refresh.createDelegate(this);
+ /**
+ * Delegate for update() prebound to 'this', use myUpdater.updateDelegate.createCallback(arg1, arg2) to bind arguments
+ * @type Function
+ */
+ this.updateDelegate = this.update.createDelegate(this);
+ /**
+ * Delegate for formUpdate() prebound to 'this', use myUpdater.formUpdateDelegate.createCallback(arg1, arg2) to bind arguments
+ * @type Function
+ */
+ this.formUpdateDelegate = this.formUpdate.createDelegate(this);
+ /**
+ * @private
+ */
+ this.successDelegate = this.processSuccess.createDelegate(this);
+ /**
+ * @private
+ */
+ this.failureDelegate = this.processFailure.createDelegate(this);
+
+ /**
+ * The renderer for this UpdateManager. Defaults to {@link YAHOO.ext.UpdateManager.BasicRenderer}.
+ */
+ this.renderer = new YAHOO.ext.UpdateManager.BasicRenderer();
+};
+
+YAHOO.ext.UpdateManager.prototype = {
+ fireEvent : YAHOO.ext.util.Observable.prototype.fireEvent,
+ on : YAHOO.ext.util.Observable.prototype.on,
+ addListener : YAHOO.ext.util.Observable.prototype.addListener,
+ delayedListener : YAHOO.ext.util.Observable.prototype.delayedListener,
+ removeListener : YAHOO.ext.util.Observable.prototype.removeListener,
+ purgeListeners : YAHOO.ext.util.Observable.prototype.purgeListeners,
+ bufferedListener : YAHOO.ext.util.Observable.prototype.bufferedListener,
+ /**
+ * Get the Element this UpdateManager is bound to
+ * @return {YAHOO.ext.Element} The element
+ */
+ getEl : function(){
+ return this.el;
+ },
+
+ /**
+ * Performs an async request, updating this element with the response. If params are specified it uses POST, otherwise it uses GET.
+ * @param {Object/String/Function} url The url for this request or a function to call to get the url or a config object containing any of the following options:
+
+ * // Get it from a YAHOO.ext.Element object
+ * var el = getEl('foo');
+ * var mgr = el.getUpdateManager();
+ * mgr.update('http://myserver.com/index.php', 'param1=1¶m2=2');
+ * ...
+ * mgr.formUpdate('myFormId', 'http://myserver.com/index.php');
+ *
+ * // or directly (returns the same UpdateManager instance)
+ * var mgr = new YAHOO.ext.UpdateManager('myElementId');
+ * mgr.startAutoRefresh(60, 'http://myserver.com/index.php');
+ * mgr.on('update', myFcnNeedsToKnow);
+ *
+ *
+ * The only required property is url. The optional properties nocache, text and scripts
+ * are shorthand for disableCaching, indicatorText and loadScripts and are used to set their associated property on this UpdateManager instance.
+ * @param {String/Object} params (optional) The parameters to pass as either a url encoded string "param1=1¶m2=2" or an object {param1: 1, param2: 2}
+ * @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess, oResponse)
+ * @param {Boolean} discardUrl (optional) By default when you execute an update the defaultUrl is changed to the last used url. If true, it will not store the url.
+ */
+ update : function(url, params, callback, discardUrl){
+ if(this.beforeUpdate.fireDirect(this.el, url, params) !== false){
+ if(typeof url == 'object'){ // must be config object
+ var cfg = url;
+ url = cfg.url;
+ params = params || cfg.params;
+ callback = callback || cfg.callback;
+ discardUrl = discardUrl || cfg.discardUrl;
+ if(callback && cfg.scope){
+ callback = callback.createDelegate(cfg.scope);
+ }
+ if(typeof cfg.nocache != 'undefined'){this.disableCaching = cfg.nocache};
+ if(typeof cfg.text != 'undefined'){this.indicatorText = '
+um.update({
+ url: 'your-url.php',
+ params: {param1: 'foo', param2: 'bar'}, // or a URL encoded string
+ callback: yourFunction,
+ scope: yourObject, //(optional scope)
+ discardUrl: false,
+ nocache: false,
+ text: 'Loading...',
+ timeout: 30,
+ scripts: false
+});
+
+ * @param {String/HTMLElement/YAHOO.ext.Element} el The element to update
+ * @param {String} url The url
+ * @param {String/Object} params (optional) Url encoded param string or an object of name/value pairs
+ * @param {Object} options (optional) A config object with any of the UpdateManager properties you want to set - for example: {disableCaching:true, indicatorText: 'Loading data...'}
+ * @static
+ */
+YAHOO.ext.UpdateManager.updateElement = function(el, url, params, options){
+ var um = getEl(el, true).getUpdateManager();
+ YAHOO.ext.util.Config.apply(um, options);
+ um.update(url, params, options ? options.callback : null);
+};
+// alias for backwards compat
+YAHOO.ext.UpdateManager.update = YAHOO.ext.UpdateManager.updateElement;
+/**
+ * @class YAHOO.ext.UpdateManager.BasicRenderer
+ * Default Content renderer. Updates the elements innerHTML with the responseText.
+ */
+YAHOO.ext.UpdateManager.BasicRenderer = function(){};
+
+YAHOO.ext.UpdateManager.BasicRenderer.prototype = {
+ /**
+ * This is called when the transaction is completed and it's time to update the element - The BasicRenderer
+ * updates the elements innerHTML with the responseText - To perform a custom render (i.e. XML or JSON processing),
+ * create an object with a "render(el, response)" method and pass it to setRenderer on the UpdateManager.
+ * @param {YAHOO.ext.Element} el The element being rendered
+ * @param {Object} response The YUI Connect response object
+ * @param {UpdateManager} updateManager The calling update manager
+ * @param {Function} callback A callback that will need to be called if loadScripts is true on the UpdateManager
+ */
+ render : function(el, response, updateManager, callback){
+ el.update(response.responseText, updateManager.loadScripts, callback);
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/anim/Actor.js b/frontend/beta/js/YUI-extensions/anim/Actor.js
new file mode 100644
index 0000000..f5574e6
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/anim/Actor.js
@@ -0,0 +1,759 @@
+
+/**
+ * @class YAHOO.ext.Actor
+ * Provides support for syncing and chaining of Element Yahoo! UI based animation and some common effects. Actors support "self-play" without an Animator.YAHOO.ext.UpdateManager.updateElement('my-div', 'stuff.php');
+ * Note: Along with the animation methods defined below, this class inherits and captures all of the "set" or animation methods of {@link YAHOO.ext.Element}. "get" methods are not captured and execute immediately.
+ *
Usage:
+ *
+ * @extends YAHOO.ext.Element
+ * @requires YAHOO.ext.Element
+ * @requires YAHOO.util.Dom
+ * @requires YAHOO.util.Event
+ * @requires YAHOO.util.CustomEvent
+ * @requires YAHOO.util.Anim
+ * @requires YAHOO.util.ColorAnim
+ * @requires YAHOO.util.Motion
+ * @className YAHOO.ext.Actor
+ * @constructor
+ * Create new Actor.
+ * @param {String/HTMLElement} el The dom element or element id
+ * @param {YAHOO.ext.Animator} animator (optional) The Animator that will capture this Actor's actions
+ * @param {Boolean} selfCapture (optional) Whether this actor should capture it's own actions to support self playback without an animator (defaults to false)
+ */
+YAHOO.ext.Actor = function(element, animator, selfCapture){
+ this.el = YAHOO.ext.Element.get(element, true); // cache el object for playback
+ YAHOO.ext.Actor.superclass.constructor.call(this, element, true);
+ this.onCapture = new YAHOO.util.CustomEvent('Actor.onCapture');
+ if(animator){
+ /**
+ * The animator used to sync this actor with other actors
+ * @member YAHOO.ext.Actor
+ */
+ animator.addActor(this);
+ }
+ /**
+ * Whether this actor is currently capturing
+ * @member YAHOO.ext.Actor
+ */
+ this.capturing = selfCapture;
+ this.playlist = selfCapture ? new YAHOO.ext.Animator.AnimSequence() : null;
+};
+
+YAHOO.extendX(YAHOO.ext.Actor, YAHOO.ext.Element);
+
+/**
+ * Captures an action for this actor. Generally called internally but can be called directly.
+ * @param {YAHOO.ext.Actor.Action} action
+ */
+YAHOO.ext.Actor.prototype.capture = function(action){
+ if(this.playlist != null){
+ this.playlist.add(action);
+ }
+ this.onCapture.fireDirect(this, action);
+ return action;
+};
+
+/** @ignore */
+YAHOO.ext.Actor.overrideAnimation = function(method, animParam, onParam){
+ return function(){
+ if(!this.capturing){
+ return method.apply(this, arguments);
+ }
+ var args = Array.prototype.slice.call(arguments, 0);
+ if(args[animParam] === true){
+ return this.capture(new YAHOO.ext.Actor.AsyncAction(this, method, args, onParam));
+ }else{
+ return this.capture(new YAHOO.ext.Actor.Action(this, method, args));
+ }
+ };
+}
+
+/** @ignore */
+YAHOO.ext.Actor.overrideBasic = function(method){
+ return function(){
+ if(!this.capturing){
+ return method.apply(this, arguments);
+ }
+ var args = Array.prototype.slice.call(arguments, 0);
+ return this.capture(new YAHOO.ext.Actor.Action(this, method, args));
+ };
+}
+
+// All of these methods below are marked "ignore" because JSDoc treats them as fields, not function. How brilliant. The Element methods are documented anyway though.
+/** Capturing override - See {@link YAHOO.ext.Element#setVisibilityMode} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setVisibilityMode = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.setVisibilityMode);
+/** Capturing override - See {@link YAHOO.ext.Element#enableDisplayMode} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.enableDisplayMode = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.enableDisplayMode);
+/** Capturing override - See {@link YAHOO.ext.Element#focus} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.focus = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.focus);
+/** Capturing override - See {@link YAHOO.ext.Element#addClass} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.addClass = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.addClass);
+/** Capturing override - See {@link YAHOO.ext.Element#removeClass} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.removeClass = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.removeClass);
+/** Capturing override - See {@link YAHOO.ext.Element#replaceClass} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.replaceClass = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.replaceClass);
+/** Capturing override - See {@link YAHOO.ext.Element#setStyle} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setStyle = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.setStyle);
+/** Capturing override - See {@link YAHOO.ext.Element#setLeft} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setLeft = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.setLeft);
+/** Capturing override - See {@link YAHOO.ext.Element#setTop} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setTop = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.setTop);
+/** Capturing override - See {@link YAHOO.ext.Element#setAbsolutePositioned} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setAbsolutePositioned = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.setAbsolutePositioned);
+/** Capturing override - See {@link YAHOO.ext.Element#setRelativePositioned} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setRelativePositioned = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.setRelativePositioned);
+/** Capturing override - See {@link YAHOO.ext.Element#clearPositioning} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.clearPositioning = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.clearPositioning);
+/** Capturing override - See {@link YAHOO.ext.Element#setPositioning} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setPositioning = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.setPositioning);
+/** Capturing override - See {@link YAHOO.ext.Element#clip} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.clip = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.clip);
+/** Capturing override - See {@link YAHOO.ext.Element#unclip} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.unclip = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.unclip);
+/** Capturing override - See {@link YAHOO.ext.Element#clearOpacity} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.clearOpacity = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.clearOpacity);
+/** Capturing override - See {@link YAHOO.ext.Element#update} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.update = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.update);
+/** Capturing override - See {@link YAHOO.ext.Element#remove} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.remove = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.remove);
+YAHOO.ext.Actor.prototype.fitToParent = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.fitToParent);
+YAHOO.ext.Actor.prototype.appendChild = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.appendChild);
+YAHOO.ext.Actor.prototype.createChild = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.createChild);
+YAHOO.ext.Actor.prototype.appendTo = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.appendTo);
+YAHOO.ext.Actor.prototype.insertBefore = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.insertBefore);
+YAHOO.ext.Actor.prototype.insertAfter = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.insertAfter);
+YAHOO.ext.Actor.prototype.wrap = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.wrap);
+YAHOO.ext.Actor.prototype.replace = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.replace);
+YAHOO.ext.Actor.prototype.insertHtml = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.insertHtml);
+YAHOO.ext.Actor.prototype.set = YAHOO.ext.Actor.overrideBasic(YAHOO.ext.Actor.superclass.set);
+
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#load} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.load = function(){
+ if(!this.capturing){
+ return YAHOO.ext.Actor.superclass.load.apply(this, arguments);
+ }
+ var args = Array.prototype.slice.call(arguments, 0);
+ return this.capture(new YAHOO.ext.Actor.AsyncAction(this, YAHOO.ext.Actor.superclass.load,
+ args, 2));
+};
+
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#animate} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.animate = function(args, duration, onComplete, easing, animType){
+ if(!this.capturing){
+ return YAHOO.ext.Actor.superclass.animate.apply(this, arguments);
+ }
+ return this.capture(new YAHOO.ext.Actor.AsyncAction(this, YAHOO.ext.Actor.superclass.animate,
+ [args, duration, onComplete, easing, animType], 2));
+};
+
+/** Capturing and animation syncing override - See {@link YAHOO.ext.Element#setVisible} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setVisible = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.setVisible, 1, 3);
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#toggle} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.toggle = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.toggle, 0, 2);
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#setXY} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setXY = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.setXY, 1, 3);
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#setLocation} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setLocation = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.setLocation, 2, 4);
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#setWidth} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setWidth = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.setWidth, 1, 3);
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#setHeight} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setHeight = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.setHeight, 1, 3);
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#setSize} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setSize = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.setSize, 2, 4);
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#setBounds} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setBounds = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.setBounds, 4, 6);
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#setOpacity} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setOpacity = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.setOpacity, 1, 3);
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#moveTo} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.moveTo = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.moveTo, 2, 4);
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#move} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.move = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.move, 2, 4);
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#alignTo} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.alignTo = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.alignTo, 3, 5);
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#hide} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.hide = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.hide, 0, 2);
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#show} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.show = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.show, 0, 2);
+
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#setBox} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setBox = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.setBox, 2, 4);
+
+/**Capturing and animation syncing override - See {@link YAHOO.ext.Element#autoHeight} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.autoHeight = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.autoHeight, 0, 2);
+/** Capturing override - See {@link YAHOO.ext.Element#setX} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setX = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.setX, 1, 3);
+/** Capturing override - See {@link YAHOO.ext.Element#setY} for method details.
+ * @method */
+YAHOO.ext.Actor.prototype.setY = YAHOO.ext.Actor.overrideAnimation(YAHOO.ext.Actor.superclass.setY, 1, 3);
+
+/**
+ * Start self capturing calls on this Actor. All subsequent calls are captured and executed when play() is called.
+ */
+YAHOO.ext.Actor.prototype.startCapture = function(){
+ this.capturing = true;
+ this.playlist = new YAHOO.ext.Animator.AnimSequence();
+ };
+
+ /**
+ * Stop self capturing calls on this Actor.
+ */
+ YAHOO.ext.Actor.prototype.stopCapture = function(){
+ this.capturing = false;
+ };
+
+/**
+ * Clears any calls that have been self captured.
+ */
+YAHOO.ext.Actor.prototype.clear = function(){
+ this.playlist = new YAHOO.ext.Animator.AnimSequence();
+};
+
+/**
+ * Starts playback of self captured calls.
+ * @param {Function} oncomplete (optional) Callback to execute when playback has completed
+ */
+YAHOO.ext.Actor.prototype.play = function(oncomplete){
+ this.capturing = false;
+ if(this.playlist){
+ this.playlist.play(oncomplete);
+ }
+ };
+
+/**
+ * Capture a function call.
+ * @param {Function} fcn The function to call
+ * @param {Array} args (optional) The arguments to call the function with
+ * @param {Object} scope (optional) The scope of the function
+ */
+YAHOO.ext.Actor.prototype.addCall = function(fcn, args, scope){
+ if(!this.capturing){
+ fcn.apply(scope || this, args || []);
+ }else{
+ this.capture(new YAHOO.ext.Actor.Action(scope, fcn, args || []));
+ }
+};
+
+/**
+ * Capture an async function call.
+ * @param {Function} fcn The function to call
+ * @param {Number} callbackIndex The index of the callback parameter on the passed function. A CALLBACK IS REQUIRED.
+ * @param {Array} args The arguments to call the function with
+ * @param {Object} scope (optional) The scope of the function
+ */
+YAHOO.ext.Actor.prototype.addAsyncCall = function(fcn, callbackIndex, args, scope){
+ if(!this.capturing){
+ fcn.apply(scope || this, args || []);
+ }else{
+ this.capture(new YAHOO.ext.Actor.AsyncAction(scope, fcn, args || [], callbackIndex));
+ }
+ },
+
+/**
+ * Capture a pause (in seconds).
+ * @param {Number} seconds The seconds to pause
+ */
+YAHOO.ext.Actor.prototype.pause = function(seconds){
+ this.capture(new YAHOO.ext.Actor.PauseAction(seconds));
+ };
+
+/**
+* Shake this element from side to side
+*/
+YAHOO.ext.Actor.prototype.shake = function(){
+ this.move('left', 20, true, .05);
+ this.move('right', 40, true, .05);
+ this.move('left', 40, true, .05);
+ this.move('right', 20, true, .05);
+};
+
+/**
+* Bounce this element from up and down
+*/
+YAHOO.ext.Actor.prototype.bounce = function(){
+ this.move('up', 20, true, .05);
+ this.move('down', 40, true, .05);
+ this.move('up', 40, true, .05);
+ this.move('down', 20, true, .05);
+};
+
+/**
+* Show the element using a "blinds" effect
+* @param {String} anchor The part of the element that it should appear to exapand from.
+ The short/long options currently are t/top, l/left
+* @param {Number} newSize (optional) The size to animate to. (Default to current size)
+* @param {Float} duration (optional) How long the effect lasts (in seconds)
+* @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeOut)
+*/
+YAHOO.ext.Actor.prototype.blindShow = function(anchor, newSize, duration, easing){
+ var size = this.getSize();
+ this.clip();
+ anchor = anchor.toLowerCase();
+ switch(anchor){
+ case 't':
+ case 'top':
+ this.setHeight(1);
+ this.setVisible(true);
+ this.setHeight(newSize || size.height, true, duration || .5, null, easing || YAHOO.util.Easing.easeOut);
+ break;
+ case 'l':
+ case 'left':
+ this.setWidth(1);
+ this.setVisible(true);
+ this.setWidth(newSize || size.width, true, duration || .5, null, easing || YAHOO.util.Easing.easeOut);
+ break;
+ }
+ this.unclip();
+ return size;
+};
+
+/**
+* Hide the element using a "blinds" effect
+* @param {String} anchor The part of the element that it should appear to collapse to.
+ The short/long options are t/top, l/left, b/bottom, r/right.
+* @param {Float} duration (optional) How long the effect lasts (in seconds)
+* @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeIn)
+*/
+YAHOO.ext.Actor.prototype.blindHide = function(anchor, duration, easing){
+ var size = this.getSize();
+ this.clip();
+ anchor = anchor.toLowerCase();
+ switch(anchor){
+ case 't':
+ case 'top':
+ this.setSize(size.width, 1, true, duration || .5, null, easing || YAHOO.util.Easing.easeIn);
+ this.setVisible(false);
+ break;
+ case 'l':
+ case 'left':
+ this.setSize(1, size.height, true, duration || .5, null, easing || YAHOO.util.Easing.easeIn);
+ this.setVisible(false);
+ break;
+ case 'r':
+ case 'right':
+ this.animate({width: {to: 1}, points: {by: [size.width, 0]}},
+ duration || .5, null, YAHOO.util.Easing.easeIn, YAHOO.util.Motion);
+ this.setVisible(false);
+ break;
+ case 'b':
+ case 'bottom':
+ this.animate({height: {to: 1}, points: {by: [0, size.height]}},
+ duration || .5, null, YAHOO.util.Easing.easeIn, YAHOO.util.Motion);
+ this.setVisible(false);
+ break;
+ }
+ return size;
+};
+
+/**
+* Show the element using a "slide in" effect - In order for this effect to work the element MUST have a child element container that can be "slid" otherwise a blindShow effect is rendered.
+* @param {String} anchor The part of the element that it should appear to slide from.
+ The short/long options currently are t/top, l/left
+* @param {Number} newSize (optional) The size to animate to. (Default to current size)
+* @param {Float} duration (optional) How long the effect lasts (in seconds)
+* @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeOuth)
+*/
+YAHOO.ext.Actor.prototype.slideShow = function(anchor, newSize, duration, easing, clearPositioning){
+ var size = this.getSize();
+ this.clip();
+ var firstChild = this.dom.firstChild;
+ if(!firstChild || (firstChild.nodeName && "#TEXT" == firstChild.nodeName.toUpperCase())) { // can't do a slide with only a textnode
+ this.blindShow(anchor, newSize, duration, easing);
+ return;
+ }
+ var child = YAHOO.ext.Element.get(firstChild, true);
+ var pos = child.getPositioning();
+ this.addCall(child.setAbsolutePositioned, null, child);
+ this.setVisible(true);
+ anchor = anchor.toLowerCase();
+ switch(anchor){
+ case 't':
+ case 'top':
+ this.addCall(child.setStyle, ['right', ''], child);
+ this.addCall(child.setStyle, ['top', ''], child);
+ this.addCall(child.setStyle, ['left', '0px'], child);
+ this.addCall(child.setStyle, ['bottom', '0px'], child);
+ this.setHeight(1);
+ this.setHeight(newSize || size.height, true, duration || .5, null, easing || YAHOO.util.Easing.easeOut);
+ break;
+ case 'l':
+ case 'left':
+ this.addCall(child.setStyle, ['left', ''], child);
+ this.addCall(child.setStyle, ['bottom', ''], child);
+ this.addCall(child.setStyle, ['right', '0px'], child);
+ this.addCall(child.setStyle, ['top', '0px'], child);
+ this.setWidth(1);
+ this.setWidth(newSize || size.width, true, duration || .5, null, easing || YAHOO.util.Easing.easeOut);
+ break;
+ case 'r':
+ case 'right':
+ this.addCall(child.setStyle, ['left', '0px'], child);
+ this.addCall(child.setStyle, ['top', '0px'], child);
+ this.addCall(child.setStyle, ['right', ''], child);
+ this.addCall(child.setStyle, ['bottom', ''], child);
+ this.setWidth(1);
+ this.setWidth(newSize || size.width, true, duration || .5, null, easing || YAHOO.util.Easing.easeOut);
+ break;
+ case 'b':
+ case 'bottom':
+ this.addCall(child.setStyle, ['right', ''], child);
+ this.addCall(child.setStyle, ['top', '0px'], child);
+ this.addCall(child.setStyle, ['left', '0px'], child);
+ this.addCall(child.setStyle, ['bottom', ''], child);
+ this.setHeight(1);
+ this.setHeight(newSize || size.height, true, duration || .5, null, easing || YAHOO.util.Easing.easeOut);
+ break;
+ }
+ if(clearPositioning !== false){
+ this.addCall(child.setPositioning, [pos], child);
+ }
+ this.unclip();
+ return size;
+};
+
+/**
+* Hide the element using a "slide in" effect - In order for this effect to work the element MUST have a child element container that can be "slid" otherwise a blindHide effect is rendered.
+* @param {String} anchor The part of the element that it should appear to slide to.
+ The short/long options are t/top, l/left, b/bottom, r/right.
+* @param {Float} duration (optional) How long the effect lasts (in seconds)
+* @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeIn)
+*/
+YAHOO.ext.Actor.prototype.slideHide = function(anchor, duration, easing){
+ var size = this.getSize();
+ this.clip();
+ var firstChild = this.dom.firstChild;
+ if(!firstChild || (firstChild.nodeName && "#TEXT" == firstChild.nodeName.toUpperCase())) { // can't do a slide with only a textnode
+ this.blindHide(anchor, duration, easing);
+ return;
+ }
+ var child = YAHOO.ext.Element.get(firstChild, true);
+ var pos = child.getPositioning();
+ this.addCall(child.setAbsolutePositioned, null, child);
+ anchor = anchor.toLowerCase();
+ switch(anchor){
+ case 't':
+ case 'top':
+ this.addCall(child.setStyle, ['right', ''], child);
+ this.addCall(child.setStyle, ['top', ''], child);
+ this.addCall(child.setStyle, ['left', '0px'], child);
+ this.addCall(child.setStyle, ['bottom', '0px'], child);
+ this.setSize(size.width, 1, true, duration || .5, null, easing || YAHOO.util.Easing.easeIn);
+ this.setVisible(false);
+ break;
+ case 'l':
+ case 'left':
+ this.addCall(child.setStyle, ['left', ''], child);
+ this.addCall(child.setStyle, ['bottom', ''], child);
+ this.addCall(child.setStyle, ['right', '0px'], child);
+ this.addCall(child.setStyle, ['top', '0px'], child);
+ this.setSize(1, size.height, true, duration || .5, null, easing || YAHOO.util.Easing.easeIn);
+ this.setVisible(false);
+ break;
+ case 'r':
+ case 'right':
+ this.addCall(child.setStyle, ['right', ''], child);
+ this.addCall(child.setStyle, ['bottom', ''], child);
+ this.addCall(child.setStyle, ['left', '0px'], child);
+ this.addCall(child.setStyle, ['top', '0px'], child);
+ this.setSize(1, size.height, true, duration || .5, null, easing || YAHOO.util.Easing.easeIn);
+ this.setVisible(false);
+ break;
+ case 'b':
+ case 'bottom':
+ this.addCall(child.setStyle, ['right', ''], child);
+ this.addCall(child.setStyle, ['top', '0px'], child);
+ this.addCall(child.setStyle, ['left', '0px'], child);
+ this.addCall(child.setStyle, ['bottom', ''], child);
+ this.setSize(size.width, 1, true, duration || .5, null, easing || YAHOO.util.Easing.easeIn);
+ this.setVisible(false);
+ break;
+ }
+ this.addCall(child.setPositioning, [pos], child);
+ return size;
+};
+
+/**
+* Hide the element by "squishing" it into the corner
+* @param {Float} duration (optional) How long the effect lasts (in seconds)
+*/
+YAHOO.ext.Actor.prototype.squish = function(duration){
+ var size = this.getSize();
+ this.clip();
+ this.setSize(1, 1, true, duration || .5);
+ this.setVisible(false);
+ return size;
+};
+
+/**
+* Fade an element in
+* @param {Float} duration (optional) How long the effect lasts (in seconds)
+*/
+YAHOO.ext.Actor.prototype.appear = function(duration){
+ this.setVisible(true, true, duration);
+};
+
+/**
+* Fade an element out
+* @param {Float} duration (optional) How long the effect lasts (in seconds)
+*/
+YAHOO.ext.Actor.prototype.fade = function(duration){
+ this.setVisible(false, true, duration);
+};
+
+/**
+* Blink the element as if it was clicked and then collapse on it's center
+* @param {Float} duration (optional) How long the effect lasts (in seconds)
+*/
+YAHOO.ext.Actor.prototype.switchOff = function(duration){
+ this.clip();
+ this.setVisible(false, true, .1);
+ this.clearOpacity();
+ this.setVisible(true);
+ this.animate({height: {to: 1}, points: {by: [0, this.getHeight()/2]}},
+ duration || .5, null, YAHOO.util.Easing.easeOut, YAHOO.util.Motion);
+ this.setVisible(false);
+};
+
+/**
+* Highlight the element using a background color (or passed attribute) animation
+* @param {String} color (optional) The color to use for the highlight
+* @param {String} fromColor (optional) If the element does not currently have a background color, you will need to pass in a color to animate from
+* @param {Float} duration (optional) How long the effect lasts (in seconds)
+* @param {String} attribute (optional) Specify a CSS attribute to use other than background color - camelCase
+*/
+YAHOO.ext.Actor.prototype.highlight = function(color, fromColor, duration, attribute){
+ attribute = attribute || 'background-color';
+ var original = this.getStyle(attribute);
+ fromColor = fromColor || ((original && original != '' && original != 'transparent') ? original : '#FFFFFF');
+ var cfg = {};
+ cfg[attribute] = {to: color, from: fromColor};
+ this.setVisible(true);
+ this.animate(cfg, duration || .5, null, YAHOO.util.Easing.bounceOut, YAHOO.util.ColorAnim);
+ this.setStyle(attribute, original);
+};
+
+/**
+* Fade the element in and out the specified amount of times
+* @param {Number} count (optional) How many times to pulse (Defaults to 3)
+* @param {Float} duration (optional) How long the effect lasts (in seconds)
+*/
+YAHOO.ext.Actor.prototype.pulsate = function(count, duration){
+ count = count || 3;
+ for(var i = 0; i < count; i++){
+ this.toggle(true, duration || .25);
+ this.toggle(true, duration || .25);
+ }
+};
+
+/**
+* Fade the element as it is falling from it's current position
+* @param {Float} duration (optional) How long the effect lasts (in seconds)
+*/
+YAHOO.ext.Actor.prototype.dropOut = function(duration){
+ this.animate({opacity: {to: 0}, points: {by: [0, this.getHeight()]}},
+ duration || .5, null, YAHOO.util.Easing.easeIn, YAHOO.util.Motion);
+ this.setVisible(false);
+};
+
+/**
+* Hide the element in a way that it appears as if it is flying off the screen
+* @param {String} anchor The part of the page that the element should appear to move to.
+ The short/long options are t/top, l/left, b/bottom, r/right, tl/top-left,
+ tr/top-right, bl/bottom-left or br/bottom-right.
+* @param {Float} duration (optional) How long the effect lasts (in seconds)
+* @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeIn)
+*/
+YAHOO.ext.Actor.prototype.moveOut = function(anchor, duration, easing){
+ var Y = YAHOO.util;
+ var vw = Y.Dom.getViewportWidth();
+ var vh = Y.Dom.getViewportHeight();
+ var cpoints = this.getCenterXY()
+ var centerX = cpoints[0];
+ var centerY = cpoints[1];
+ var anchor = anchor.toLowerCase();
+ var p;
+ switch(anchor){
+ case 't':
+ case 'top':
+ p = [centerX, -this.getHeight()];
+ break;
+ case 'l':
+ case 'left':
+ p = [-this.getWidth(), centerY];
+ break;
+ case 'r':
+ case 'right':
+ p = [vw+this.getWidth(), centerY];
+ break;
+ case 'b':
+ case 'bottom':
+ p = [centerX, vh+this.getHeight()];
+ break;
+ case 'tl':
+ case 'top-left':
+ p = [-this.getWidth(), -this.getHeight()];
+ break;
+ case 'bl':
+ case 'bottom-left':
+ p = [-this.getWidth(), vh+this.getHeight()];
+ break;
+ case 'br':
+ case 'bottom-right':
+ p = [vw+this.getWidth(), vh+this.getHeight()];
+ break;
+ case 'tr':
+ case 'top-right':
+ p = [vw+this.getWidth(), -this.getHeight()];
+ break;
+ }
+ this.moveTo(p[0], p[1], true, duration || .35, null, easing || Y.Easing.easeIn);
+ this.setVisible(false);
+};
+
+/**
+* Show the element in a way that it appears as if it is flying onto the screen
+* @param {String} anchor The part of the page that the element should appear to move from.
+ The short/long options are t/top, l/left, b/bottom, r/right, tl/top-left,
+ tr/top-right, bl/bottom-left or br/bottom-right.
+* @param {Array} to (optional) Array of x and y position to move to like [x, y] (Defaults to center screen)
+* @param {Float} duration (optional) How long the effect lasts (in seconds)
+* @param {Function} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeOut)
+*/
+YAHOO.ext.Actor.prototype.moveIn = function(anchor, to, duration, easing){
+ to = to || this.getCenterXY();
+ this.moveOut(anchor, .01);
+ this.setVisible(true);
+ this.setXY(to, true, duration || .35, null, easing || YAHOO.util.Easing.easeOut);
+};
+/**
+* Show a ripple of exploding, attenuating borders to draw attention to an Element.
+* @param {Number} color (optional) The color of the border.
+* @param {Number} count (optional) How many ripples.
+* @param {Float} duration (optional) How long each ripple takes to expire
+*/
+YAHOO.ext.Actor.prototype.frame = function(color, count, duration){
+ color = color || "red";
+ count = count || 3;
+ duration = duration || .5;
+ var frameFn = function(callback){
+ var box = this.getBox();
+ var animFn = function(){
+ var proxy = this.createProxy({
+ tag:"div",
+ style:{
+ visbility:"hidden",
+ position:"absolute",
+ 'z-index':"35000", // yee haw
+ border:"0px solid " + color
+ }
+ });
+ var scale = proxy.isBorderBox() ? 2 : 1;
+ proxy.animate({
+ top:{from:box.y, to:box.y - 20},
+ left:{from:box.x, to:box.x - 20},
+ borderWidth:{from:0, to:10},
+ opacity:{from:1, to:0},
+ height:{from:box.height, to:(box.height + (20*scale))},
+ width:{from:box.width, to:(box.width + (20*scale))}
+ }, duration, function(){
+ proxy.remove();
+ });
+ if(--count > 0){
+ animFn.defer((duration/2)*1000, this);
+ }else{
+ if(typeof callback == 'function'){
+ callback();
+ }
+ }
+ }
+ animFn.call(this);
+ }
+ this.addAsyncCall(frameFn, 0, null, this);
+};
+
+YAHOO.ext.Actor.Action = function(actor, method, args){
+ this.actor = actor;
+ this.method = method;
+ this.args = args;
+ }
+
+YAHOO.ext.Actor.Action.prototype = {
+ play : function(onComplete){
+ this.method.apply(this.actor || window, this.args);
+ onComplete();
+ }
+};
+
+
+YAHOO.ext.Actor.AsyncAction = function(actor, method, args, onIndex){
+ YAHOO.ext.Actor.AsyncAction.superclass.constructor.call(this, actor, method, args);
+ this.onIndex = onIndex;
+ this.originalCallback = this.args[onIndex];
+}
+YAHOO.extendX(YAHOO.ext.Actor.AsyncAction, YAHOO.ext.Actor.Action);
+
+YAHOO.ext.Actor.AsyncAction.prototype.play = function(onComplete){
+ var callbackArg = this.originalCallback ?
+ this.originalCallback.createSequence(onComplete) : onComplete;
+ this.args[this.onIndex] = callbackArg;
+ this.method.apply(this.actor, this.args);
+};
+
+
+YAHOO.ext.Actor.PauseAction = function(seconds){
+ this.seconds = seconds;
+};
+YAHOO.ext.Actor.PauseAction.prototype = {
+ play : function(onComplete){
+ setTimeout(onComplete, this.seconds * 1000);
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/anim/Animator.js b/frontend/beta/js/YUI-extensions/anim/Animator.js
new file mode 100644
index 0000000..ed250fb
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/anim/Animator.js
@@ -0,0 +1,482 @@
+/**
+ * @class YAHOO.ext.Animator
+ * Provides support for syncing animations for multiple {@link YAHOO.ext.Actor}s.
+ * var actor = new YAHOO.ext.Actor('myElementId');
+ * actor.startCapture(true);
+ * actor.moveTo(100, 100, true);
+ * actor.squish();
+ * actor.play();
+ *
+ * // or to start capturing immediately, with no Animator (the null second param)
+ *
+ * var actor = new YAHOO.ext.Actor('myElementId', null, true);
+ * actor.moveTo(100, 100, true);
+ * actor.squish();
+ * actor.play();
+ *
+*
This example can be seen in action here
+* by clicking on "Click here and I will point it out" at the end of the first paragraph.
+ *
+ * @requires YAHOO.ext.Element
+ * @requires YAHOO.util.Dom
+ * @requires YAHOO.util.Event
+ * @requires YAHOO.util.CustomEvent
+ * @requires YAHOO.util.Anim
+ * @requires YAHOO.util.ColorAnim
+ * @requires YAHOO.util.Motion
+ * @constructor
+ * @param {String/HTMLElement} el The dom element or element id
+ * @param {YAHOO.ext.Animator} animator (optional) The Animator that will capture this Actor's actions
+ * @param {Boolean} selfCapture (optional) Whether this actor should capture it's own actions to support self playback without an animator (defaults to false)
+ */
+ YAHOO.ext.Animator = function(/*Actors...*/){
+ this.actors = [];
+ this.playlist = new YAHOO.ext.Animator.AnimSequence();
+ this.captureDelegate = this.capture.createDelegate(this);
+ this.playDelegate = this.play.createDelegate(this);
+ this.syncing = false;
+ this.stopping = false;
+ this.playing = false;
+ for(var i = 0; i < arguments.length; i++){
+ this.addActor(arguments[i]);
+ }
+ };
+
+ YAHOO.ext.Animator.prototype = {
+
+ capture : function(actor, action){
+ if(this.syncing){
+ if(!this.syncMap[actor.id]){
+ this.syncMap[actor.id] = new YAHOO.ext.Animator.AnimSequence();
+ }
+ this.syncMap[actor.id].add(action);
+ }else{
+ this.playlist.add(action);
+ }
+ },
+
+ /**
+ * Add an actor. The actor is also set to capturing = true.
+ * @param {YAHOO.ext.Actor} actor
+ */
+ addActor : function(actor){
+ actor.onCapture.subscribe(this.captureDelegate);
+ this.actors.push(actor);
+ },
+
+
+ /**
+ * Start capturing actions on the added actors.
+ * @param {Boolean} clearPlaylist Whether to also create a new playlist
+ */
+ startCapture : function(clearPlaylist){
+ for(var i = 0; i < this.actors.length; i++){
+ var a = this.actors[i];
+ if(!this.isCapturing(a)){
+ a.onCapture.subscribe(this.captureDelegate);
+ }
+ a.capturing = true;
+ }
+ if(clearPlaylist){
+ this.playlist = new YAHOO.ext.Animator.AnimSequence();
+ }
+ },
+
+ /**
+ * Checks whether this animator is listening to a specific actor.
+ * @param {YAHOO.ext.Actor} actor
+ */
+ isCapturing : function(actor){
+ var subscribers = actor.onCapture.subscribers;
+ if(subscribers){
+ for(var i = 0; i < subscribers.length; i++){
+ if(subscribers[i] && subscribers[i].contains(this.captureDelegate)){
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Stop capturing on all added actors.
+ */
+ stopCapture : function(){
+ for(var i = 0; i < this.actors.length; i++){
+ var a = this.actors[i];
+ a.onCapture.unsubscribe(this.captureDelegate);
+ a.capturing = false;
+ }
+ },
+
+ /**
+ * Start a multi-actor sync block. By default all animations are run in sequence. While in the sync block
+ * each actor's own animations will still be sequenced, but all actors will animate at the same time.
+ */
+ beginSync : function(){
+ this.syncing = true;
+ this.syncMap = {};
+ },
+
+ /**
+ * End the multi-actor sync block
+ */
+ endSync : function(){
+ this.syncing = false;
+ var composite = new YAHOO.ext.Animator.CompositeSequence();
+ for(key in this.syncMap){
+ if(typeof this.syncMap[key] != 'function'){
+ composite.add(this.syncMap[key]);
+ }
+ }
+ this.playlist.add(composite);
+ this.syncMap = null;
+ },
+
+ /**
+ * Starts playback of the playlist, also stops any capturing. To start capturing again call {@link #startCapture}.
+ * @param {Function} oncomplete (optional) Callback to execute when playback has completed
+ */
+ play : function(oncomplete){
+ if(this.playing) return; // can't play the same animation twice at once
+ this.stopCapture();
+ this.playlist.play(oncomplete);
+ },
+
+ /**
+ * Stop at the next available stopping point
+ */
+ stop : function(){
+ this.playlist.stop();
+ },
+
+ /**
+ * Check if this animator is currently playing
+ */
+ isPlaying : function(){
+ return this.playlist.isPlaying();
+ },
+ /**
+ * Clear the playlist
+ */
+ clear : function(){
+ this.playlist = new YAHOO.ext.Animator.AnimSequence();
+ },
+
+ /**
+ * Add a function call to the playlist.
+ * @param {Function} fcn The function to call
+ * @param {Array} args The arguments to call the function with
+ * @param {Object} scope (optional) The scope of the function
+ */
+ addCall : function(fcn, args, scope){
+ this.playlist.add(new YAHOO.ext.Actor.Action(scope, fcn, args || []));
+ },
+
+ /**
+ * Add an async function call to the playlist.
+ * @param {Function} fcn The function to call
+ * @param {Number} callbackIndex The index of the callback parameter on the passed function. A CALLBACK IS REQUIRED.
+ * @param {Array} args The arguments to call the function with
+ * @param {Object} scope (optional) The scope of the function
+ */
+ addAsyncCall : function(fcn, callbackIndex, args, scope){
+ this.playlist.add(new YAHOO.ext.Actor.AsyncAction(scope, fcn, args || [], callbackIndex));
+ },
+
+ /**
+ * Add a pause to the playlist (in seconds)
+ * @param {Number} seconds The number of seconds to pause.
+ */
+ pause : function(seconds){
+ this.playlist.add(new YAHOO.ext.Actor.PauseAction(seconds));
+ }
+
+ };
+/**
+ * Static function to build a AnimatorComposite from a css selector (requires YAHOO.ext.Element.selectorFunction be defined)
+ * @param {String/Array} selector The css selector or an array of nodes to animate
+ * @method @static
+ */
+YAHOO.ext.Animator.select = function(selector){
+ var els;
+ if(typeof selector == 'string'){
+ els = YAHOO.ext.Element.selectorFunction(selector);
+ }else if(selector instanceof Array){
+ els = selector;
+ }else{
+ throw 'Invalid selector';
+ }
+ return new YAHOO.ext.AnimatorComposite(els);
+};
+var getActors = YAHOO.ext.Animator.select;
+
+/**
+ * @class YAHOO.ext.AnimatorComposite
+ * Composite class with synchronized animations. This is the class returned by getActors(selector) or YAHOO.ext.Animator.select().
+ */
+YAHOO.ext.AnimatorComposite = function(els){
+ this.animator = new YAHOO.ext.Animator();
+ this.addElements(els);
+ this.syncAnims = true;
+};
+YAHOO.ext.AnimatorComposite.prototype = {
+ isComposite: true,
+ /**
+ * Adds elements to this composite.
+ * @param {Array} els An array of elements to add
+ * @return {AnimatorComposite} this
+ */
+ addElements : function(els){
+ if(!els) return this;
+ var anim = this.animator;
+ for(var i = 0, len = els.length; i < len; i++) {
+ anim.addActor(new YAHOO.ext.Actor(els[i]));
+ }
+ anim.startCapture();
+ return this;
+ },
+ /**
+ * Operations called after sequence() will be performed one by one on each element in this composite.
+ * @return {AnimatorComposite} this
+ */
+ sequence : function(){
+ this.syncAnims = false;
+ return this;
+ },
+ /**
+ * Operations called after sync() will be performed at the same time on each element in this composite.
+ * @return {AnimatorComposite} this
+ */
+ sync : function(){
+ this.syncAnims = true;
+ return this;
+ },
+ invoke : function(fn, args){
+ var els = this.animator.actors;
+ if(this.syncAnims) this.animator.beginSync();
+ for(var i = 0, len = els.length; i < len; i++) {
+ YAHOO.ext.Actor.prototype[fn].apply(els[i], args);
+ }
+ if(this.syncAnims) this.animator.endSync();
+ return this;
+ },
+ /**
+ * Play the actions queued in this composite.
+ * @param {Function} callback (optional) callback is called when all animations have compelted
+ * @return {AnimatorComposite} this
+ */
+ play : function(callback){
+ this.animator.play(callback);
+ return this;
+ },
+ /**
+ * Clear all actions in the queue.
+ * @param {Function} callback (optional) callback is called when all animations have compelted
+ * @return {AnimatorComposite} this
+ */
+ reset : function(callback){
+ this.animator.startCapture(true);
+ return this;
+ },
+ /**
+ * Add a pause
+ * @param {Number} seconds
+ * @return {AnimatorComposite} this
+ */
+ pause : function(seconds){
+ this.animator.pause(seconds);
+ return this;
+ },
+ /**
+ * Get the YAHOO.ext.Animator that controls the animations for this composite.
+ * @return {YAHOO.ext.Animator}
+ */
+ getAnimator : function(){
+ return this.animator;
+ },
+ /**
+ * Calls the passed function passing (el, this, index) for each element in this composite.
+ * @param {Function} fn The function to call
+ * @param {Object} scope (optional) The this object (defaults to the element)
+ * @return {AnimatorComposite} this
+ */
+ each : function(fn, scope){
+ var els = this.animator.actors;
+ if(this.syncAnims) this.animator.beginSync();
+ for(var i = 0, len = els.length; i < len; i++){
+ fn.call(scope || els[i], els[i], this, i);
+ }
+ if(this.syncAnims) this.animator.endSync();
+ return this;
+ },
+ /**
+ * Add a function call to the playlist.
+ * @param {Function} fcn The function to call
+ * @param {Array} args (optional) The arguments to call the function with
+ * @param {Object} scope (optional) The scope of the function
+ * @return {AnimatorComposite} this
+ */
+ addCall : function(fcn, args, scope){
+ this.animator.addCall(fcn, args, scope);
+ return this;
+ },
+ /**
+ * Add an async function call to the playlist.
+ * @param {Function} fcn The function to call
+ * @param {Number} callbackIndex The index of the callback parameter on the passed function. A CALLBACK IS REQUIRED.
+ * @param {Array} args (optional) The arguments to call the function with
+ * @param {Object} scope (optional) The scope of the function
+ * @return {AnimatorComposite} this
+ */
+ addAsyncCall : function(fcn, callbackIndex, args, scope){
+ this.animator.addAsyncCall(fcn, callbackIndex, args, scope);
+ return this;
+ }
+};
+for(var fnName in YAHOO.ext.Actor.prototype){
+ if(typeof YAHOO.ext.Actor.prototype[fnName] == 'function'){
+ YAHOO.ext.CompositeElement.createCall(YAHOO.ext.AnimatorComposite.prototype, fnName);
+ }
+}
+
+
+YAHOO.ext.Animator.AnimSequence = function(){
+ this.actions = [];
+ this.nextDelegate = this.next.createDelegate(this);
+ this.playDelegate = this.play.createDelegate(this);
+ this.oncomplete = null;
+ this.playing = false;
+ this.stopping = false;
+ this.actionIndex = -1;
+ };
+
+ YAHOO.ext.Animator.AnimSequence.prototype = {
+
+ add : function(action){
+ this.actions.push(action);
+ },
+
+ next : function(){
+ if(this.stopping){
+ this.playing = false;
+ if(this.oncomplete){
+ this.oncomplete(this, false);
+ }
+ return;
+ }
+ var nextAction = this.actions[++this.actionIndex];
+ if(nextAction){
+ nextAction.play(this.nextDelegate);
+ }else{
+ this.playing = false;
+ if(this.oncomplete){
+ this.oncomplete(this, true);
+ }
+ }
+ },
+
+ play : function(oncomplete){
+ if(this.playing) return; // can't play the same sequence twice at once
+ this.oncomplete = oncomplete;
+ this.stopping = false;
+ this.playing = true;
+ this.actionIndex = -1;
+ this.next();
+ },
+
+ stop : function(){
+ this.stopping = true;
+ },
+
+ isPlaying : function(){
+ return this.playing;
+ },
+
+ clear : function(){
+ this.actions = [];
+ },
+
+ addCall : function(fcn, args, scope){
+ this.actions.push(new YAHOO.ext.Actor.Action(scope, fcn, args || []));
+ },
+
+ addAsyncCall : function(fcn, callbackIndex, args, scope){
+ this.actions.push(new YAHOO.ext.Actor.AsyncAction(scope, fcn, args || [], callbackIndex));
+ },
+
+ pause : function(seconds){
+ this.actions.push(new YAHOO.ext.Actor.PauseAction(seconds));
+ }
+
+ };
+
+YAHOO.ext.Animator.CompositeSequence = function(){
+ this.sequences = [];
+ this.completed = 0;
+ this.trackDelegate = this.trackCompletion.createDelegate(this);
+}
+
+YAHOO.ext.Animator.CompositeSequence.prototype = {
+ add : function(sequence){
+ this.sequences.push(sequence);
+ },
+
+ play : function(onComplete){
+ this.completed = 0;
+ if(this.sequences.length < 1){
+ if(onComplete)onComplete();
+ return;
+ }
+ this.onComplete = onComplete;
+ for(var i = 0; i < this.sequences.length; i++){
+ this.sequences[i].play(this.trackDelegate);
+ }
+ },
+
+ trackCompletion : function(){
+ ++this.completed;
+ if(this.completed >= this.sequences.length && this.onComplete){
+ this.onComplete();
+ }
+ },
+
+ stop : function(){
+ for(var i = 0; i < this.sequences.length; i++){
+ this.sequences[i].stop();
+ }
+ },
+
+ isPlaying : function(){
+ for(var i = 0; i < this.sequences.length; i++){
+ if(this.sequences[i].isPlaying()){
+ return true;
+ }
+ }
+ return false;
+ }
+};
+
+
diff --git a/frontend/beta/js/YUI-extensions/data/AbstractDataModel.js b/frontend/beta/js/YUI-extensions/data/AbstractDataModel.js
new file mode 100644
index 0000000..092ea75
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/data/AbstractDataModel.js
@@ -0,0 +1,226 @@
+/**
+ * @class YAHOO.ext.grid.AbstractDataModel
+ * @extends YAHOO.ext.util.Observable
+ * This abstract class provides default implementations of the events required by the Grid.
+ It takes care of the creating the CustomEvents and provides some convenient methods for firing the events.
+var animator = new YAHOO.ext.Animator();
+var cursor = new YAHOO.ext.Actor('cursor-img', animator);
+var click = new YAHOO.ext.Actor('click-img', animator);
+var resize = new YAHOO.ext.Actor('resize-img', animator);
+
+// start capturing
+animator.startCapture();
+
+// these animations will be run in sequence
+cursor.show();
+cursor.moveTo(500,400);
+cursor.moveTo(20, getEl('navbar').getY()+10, true, .75);
+click.show();
+click.alignTo(cursor, 'tl', [-4, -4]);
+
+// Add an async function call, pass callback to argument 1
+animator.addAsyncCall(Blog.navbar.undockDelegate, 1);
+
+// pause .5 seconds
+animator.pause(.5);
+
+// again, these animations will be run in sequence
+click.hide(true, .7);
+cursor.alignTo('splitter', 'tr', [0, +100], true, 1);
+resize.alignTo('splitter', 'tr', [-12, +100]);
+
+// start sync block: these animations will run at the same time
+animator.beginSync();
+cursor.hide();
+resize.show();
+animator.endSync();
+
+// play the captured animation sequences, call myCallback when done
+animator.play(myCallback);
+ *
+ * @constructor
+*/
+YAHOO.ext.grid.AbstractDataModel = function(){
+ /** Fires when a cell is updated - fireDirect sig: (this, rowIndex, columnIndex)
+ * @private
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ */
+ this.onCellUpdated = new YAHOO.util.CustomEvent('onCellUpdated');
+ /** Fires when all data needs to be revalidated - fireDirect sig: (thisd)
+ * @private
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ */
+ this.onTableDataChanged = new YAHOO.util.CustomEvent('onTableDataChanged');
+ /** Fires when rows are deleted - fireDirect sig: (this, firstRowIndex, lastRowIndex)
+ * @private
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ */
+ this.onRowsDeleted = new YAHOO.util.CustomEvent('onRowsDeleted');
+ /** Fires when a rows are inserted - fireDirect sig: (this, firstRowIndex, lastRowIndex)
+ * @private
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ */
+ this.onRowsInserted = new YAHOO.util.CustomEvent('onRowsInserted');
+ /** Fires when a rows are updated - fireDirect sig: (this, firstRowIndex, lastRowIndex)
+ * @private
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ */
+ this.onRowsUpdated = new YAHOO.util.CustomEvent('onRowsUpdated');
+ /** Fires when a sort has reordered the rows - fireDirect sig: (this, sortColumnIndex,
+ * @private
+ * sortDirection = 'ASC' or 'DESC')
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ */
+ this.onRowsSorted = new YAHOO.util.CustomEvent('onRowsSorted');
+
+ this.events = {
+ /**
+ * @event cellupdated
+ * Fires when a cell is updated
+ * @param {DataModel} this
+ * @param {Number} rowIndex
+ * @param {Number} columnIndex
+ */
+ 'cellupdated' : this.onCellUpdated,
+ /**
+ * @event datachanged
+ * Fires when the entire data structure has changed
+ * @param {DataModel} this
+ */
+ 'datachanged' : this.onTableDataChanged,
+ /**
+ * @event rowsdeleted
+ * Fires when a range of rows have been deleted
+ * @param {DataModel} this
+ * @param {Number} firstRowIndex
+ * @param {Number} lastRowIndex
+ */
+ 'rowsdeleted' : this.onRowsDeleted,
+ /**
+ * @event rowsinserted
+ * Fires when a range of rows have been inserted
+ * @param {DataModel} this
+ * @param {Number} firstRowIndex
+ * @param {Number} lastRowIndex
+ */
+ 'rowsinserted' : this.onRowsInserted,
+ /**
+ * @event rowsupdated
+ * Fires when a range of rows have been updated
+ * @param {DataModel} this
+ * @param {Number} firstRowIndex
+ * @param {Number} lastRowIndex
+ */
+ 'rowsupdated' : this.onRowsUpdated,
+ /**
+ * @event rowssorted
+ * Fires when the data has been sorted
+ * @param {DataModel} this
+ */
+ 'rowssorted' : this.onRowsSorted
+ };
+};
+
+YAHOO.ext.grid.AbstractDataModel.prototype = {
+
+ fireEvent : YAHOO.ext.util.Observable.prototype.fireEvent,
+ on : YAHOO.ext.util.Observable.prototype.on,
+ addListener : YAHOO.ext.util.Observable.prototype.addListener,
+ delayedListener : YAHOO.ext.util.Observable.prototype.delayedListener,
+ removeListener : YAHOO.ext.util.Observable.prototype.removeListener,
+ purgeListeners : YAHOO.ext.util.Observable.prototype.purgeListeners,
+ bufferedListener : YAHOO.ext.util.Observable.prototype.bufferedListener,
+
+ /**
+ * Notifies listeners that the value of the cell at [row, col] has been updated
+ * @deprecated
+ * @private
+ */
+ fireCellUpdated : function(row, col){
+ this.onCellUpdated.fireDirect(this, row, col);
+ },
+
+ /**
+ * Notifies listeners that all data for the grid may have changed - use as a last resort. This
+ * also wipes out all selections a user might have made.
+ * @deprecated
+ * @private
+ */
+ fireTableDataChanged : function(){
+ this.onTableDataChanged.fireDirect(this);
+ },
+
+ /**
+ * Notifies listeners that rows in the range [firstRow, lastRow], inclusive, have been deleted
+ * @deprecated
+ * @private
+ */
+ fireRowsDeleted : function(firstRow, lastRow){
+ this.onRowsDeleted.fireDirect(this, firstRow, lastRow);
+ },
+
+ /**
+ * Notifies listeners that rows in the range [firstRow, lastRow], inclusive, have been inserted
+ * @deprecated
+ * @private
+ */
+ fireRowsInserted : function(firstRow, lastRow){
+ this.onRowsInserted.fireDirect(this, firstRow, lastRow);
+ },
+
+ /**
+ * Notifies listeners that rows in the range [firstRow, lastRow], inclusive, have been updated
+ * @deprecated
+ * @private
+ */
+ fireRowsUpdated : function(firstRow, lastRow){
+ this.onRowsUpdated.fireDirect(this, firstRow, lastRow);
+ },
+
+ /**
+ * Notifies listeners that rows have been sorted and any indexes may be invalid
+ * @deprecated
+ * @private
+ */
+ fireRowsSorted : function(sortColumnIndex, sortDir, noRefresh){
+ this.onRowsSorted.fireDirect(this, sortColumnIndex, sortDir, noRefresh);
+ },
+
+ /**
+ * Empty interface method - Classes which extend AbstractDataModel should implement this method.
+ * See {@link YAHOO.ext.DefaultDataModel#sort} for an example implementation.
+ * @private
+ */
+ sort : function(sortInfo, columnIndex, direction, suppressEvent){
+
+ },
+
+ /**
+ * Interface method to supply info regarding the Grid's current sort state - if overridden,
+ * this should return an object like this {column: this.sortColumn, direction: this.sortDir}.
+ * @return {Object}
+ */
+ getSortState : function(){
+ return {column: this.sortColumn, direction: this.sortDir};
+ },
+
+ /**
+ * Empty interface method - Classes which extend AbstractDataModel should implement this method.
+ * See {@link YAHOO.ext.DefaultDataModel} for an example implementation.
+ * @private
+ */
+ getRowCount : function(){
+
+ },
+
+ /**
+ * Empty interface method - Classes which extend AbstractDataModel should implement this method to support virtual row counts.
+ * @private
+ */
+ getTotalRowCount : function(){
+ return this.getRowCount();
+ },
+
+
+ /**
+ * Empty interface method - Classes which extend AbstractDataModel should implement this method.
+ * See {@link YAHOO.ext.DefaultDataModel} for an example implementation.
+ * @private
+ */
+ getRowId : function(rowIndex){
+
+ },
+
+ /**
+ * Empty interface method - Classes which extend AbstractDataModel should implement this method.
+ * See {@link YAHOO.ext.DefaultDataModel} for an example implementation.
+ * @private
+ */
+ getValueAt : function(rowIndex, colIndex){
+
+ },
+
+ /**
+ * Empty interface method - Classes which extend AbstractDataModel should implement this method.
+ * See {@link YAHOO.ext.DefaultDataModel} for an example implementation.
+ * @private
+ */
+ setValueAt : function(value, rowIndex, colIndex){
+
+ },
+
+ isPaged : function(){
+ return false;
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/data/DefaultDataModel.js b/frontend/beta/js/YUI-extensions/data/DefaultDataModel.js
new file mode 100644
index 0000000..57a022a
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/data/DefaultDataModel.js
@@ -0,0 +1,339 @@
+
+/**
+ * @class YAHOO.ext.grid.DefaultDataModel
+ * This is the default implementation of a DataModel used by the Grid. It works
+ * with multi-dimensional array based data. Using the event system in the base class
+ * {@link YAHOO.ext.grid.AbstractDataModel}, all updates to this DataModel are automatically
+ * reflected in the user interface.
+ *
Usage:
+ *
+ * @extends YAHOO.ext.grid.AbstractDataModel
+ * @constructor
+ * @param {Array} data
+*/
+YAHOO.ext.grid.DefaultDataModel = function(data){
+ YAHOO.ext.grid.DefaultDataModel.superclass.constructor.call(this);
+ /**@private*/
+ this.data = data;
+};
+YAHOO.extendX(YAHOO.ext.grid.DefaultDataModel, YAHOO.ext.grid.AbstractDataModel, {
+ /**
+ * Returns the number of rows in the dataset
+ * @return {Number}
+ */
+ getRowCount : function(){
+ return this.data.length;
+ },
+
+ /**
+ * Returns the ID of the specified row. By default it return the value of the first column.
+ * Override to provide more advanced ID handling.
+ * @return {Number}
+ */
+ getRowId : function(rowIndex){
+ return this.data[rowIndex][0];
+ },
+
+ /**
+ * Returns the column data for the specified row.
+ * @return {Array}
+ */
+ getRow : function(rowIndex){
+ return this.data[rowIndex];
+ },
+
+ /**
+ * Returns the column data for the specified rows as a
+ * multi-dimensional array: rows[3][0] would give you the value of row 4, column 0.
+ * @param {Array} indexes The row indexes to fetch
+ * @return {Array}
+ */
+ getRows : function(indexes){
+ var data = this.data;
+ var r = [];
+ for(var i = 0; i < indexes.length; i++){
+ r.push(data[indexes[i]]);
+ }
+ return r;
+ },
+
+ /**
+ * Returns the value at the specified data position
+ * @param {Number} rowIndex
+ * @param {Number} colIndex
+ * @return {Object}
+ */
+ getValueAt : function(rowIndex, colIndex){
+ return this.data[rowIndex][colIndex];
+ },
+
+ /**
+ * Sets the specified value at the specified data position
+ * @param {Object} value The new value
+ * @param {Number} rowIndex
+ * @param {Number} colIndex
+ */
+ setValueAt: function(value, rowIndex, colIndex){
+ this.data[rowIndex][colIndex] = value;
+ this.fireCellUpdated(rowIndex, colIndex);
+ },
+
+ /**
+ * @private
+ * Removes the specified range of rows.
+ * @param {Number} startIndex
+ * @param {Number} endIndex (optional) Defaults to startIndex
+ */
+ removeRows: function(startIndex, endIndex){
+ endIndex = endIndex || startIndex;
+ this.data.splice(startIndex, endIndex-startIndex+1);
+ this.fireRowsDeleted(startIndex, endIndex);
+ },
+
+ /**
+ * Remove a row.
+ * @param {Number} index
+ */
+ removeRow: function(index){
+ this.data.splice(index, 1);
+ this.fireRowsDeleted(index, index);
+ },
+
+ /**
+ * @private
+ * Removes all rows.
+ */
+ removeAll: function(){
+ var count = this.getRowCount();
+ if(count > 0){
+ this.removeRows(0, count-1);
+ }
+ },
+
+ /**
+ * Query the DataModel rows by the filters defined in spec, for example...
+ *
+ * var myData = [
+ ["MSFT","Microsoft Corporation", "314,571.156", "32,187.000", "55000"],
+ ["ORCL", "Oracle Corporation", "62,615.266", "9,519.000", "40650"]
+ * ];
+ * var dataModel = new YAHOO.ext.grid.DefaultDataModel(myData);
+ *
+ * @param {Object} spec The spec is generally an object literal consisting of
+ * column index and filter type. The filter type can be a string/number (exact match),
+ * a regular expression to test using String.search() or a function to call. If it's a function,
+ * it will be called with the value for the specified column and an array of the all column
+ * values for that row: yourFcn(value, columnData). If it returns anything other than true,
+ * the row is not a match. If you have modified Object.prototype this method may fail.
+ * @param {Boolean} returnUnmatched True to return rows which don't match the query instead
+ * of rows that do match
+ * @return {Array} An array of row indexes that match
+ */
+ query: function(spec, returnUnmatched){
+ var d = this.data;
+ var r = [];
+ for(var i = 0; i < d.length; i++){
+ var row = d[i];
+ var isMatch = true;
+ for(var col in spec){
+ //if(typeof spec[col] != 'function'){
+ if(!isMatch) continue;
+ var filter = spec[col];
+ switch(typeof filter){
+ case 'string':
+ case 'number':
+ case 'boolean':
+ if(row[col] != filter){
+ isMatch = false;
+ }
+ break;
+ case 'function':
+ if(!filter(row[col], row)){
+ isMatch = false;
+ }
+ break;
+ case 'object':
+ if(filter instanceof RegExp){
+ if(String(row[col]).search(filter) === -1){
+ isMatch = false;
+ }
+ }
+ break;
+ }
+ //}
+ }
+ if(isMatch && !returnUnmatched){
+ r.push(i);
+ }else if(!isMatch && returnUnmatched){
+ r.push(i);
+ }
+ }
+ return r;
+ },
+
+ /**
+ * Filter the DataModel rows by the query defined in spec, see {@link #query} for more details
+ * on the query spec.
+ * @param {Object} query The query spec {@link #query}
+ * @return {Number} The number of rows removed
+ */
+ filter: function(query){
+ var matches = this.query(query, true);
+ var data = this.data;
+ // go thru the data setting matches to deleted
+ // while not disturbing row indexes
+ for(var i = 0; i < matches.length; i++){
+ data[matches[i]]._deleted = true;
+ }
+ for(var i = 0; i < data.length; i++){
+ while(data[i] && data[i]._deleted === true){
+ this.removeRow(i);
+ }
+ }
+ return matches.length;
+ },
+
+ /**
+ * Adds a row to the dataset.
+ * @param {Array} cellValues The array of values for the new row
+ * @return {Number} The index of the added row
+ */
+ addRow: function(cellValues){
+ this.data.push(cellValues);
+ var newIndex = this.data.length-1;
+ this.fireRowsInserted(newIndex, newIndex);
+ this.applySort();
+ return newIndex;
+ },
+
+ /**
+ * @private
+ * Adds a set of rows.
+ * @param {Array} rowData This should be an array of arrays like the constructor takes
+ */
+ addRows: function(rowData){
+ this.data = this.data.concat(rowData);
+ var firstIndex = this.data.length-rowData.length;
+ this.fireRowsInserted(firstIndex, firstIndex+rowData.length-1);
+ this.applySort();
+ },
+
+ /**
+ * Inserts a row a the specified location in the dataset.
+ * @param {Number} index The index where the row should be inserted
+ * @param {Array} cellValues The array of values for the new row
+ * @return {Number} The index the row was inserted in
+ */
+ insertRow: function(index, cellValues){
+ this.data.splice(index, 0, cellValues);
+ this.fireRowsInserted(index, index);
+ this.applySort();
+ return index;
+ },
+
+ /**
+ * @private
+ * Inserts a set of rows.
+ * @param {Number} index The index where the rows should be inserted
+ * @param {Array} rowData This should be an array of arrays like the constructor takes
+ */
+ insertRows: function(index, rowData){
+ /*
+ if(index == this.data.length){ // try these two first since they are faster
+ this.data = this.data.concat(rowData);
+ }else if(index == 0){
+ this.data = rowData.concat(this.data);
+ }else{
+ var newData = this.data.slice(0, index);
+ newData.concat(rowData);
+ newData.concat(this.data.slice(index));
+ this.data = newData;
+ }*/
+ var args = rowData.concat();
+ args.splice(0, 0, index, 0);
+ this.data.splice.apply(this.data, args);
+ this.fireRowsInserted(index, index+rowData.length-1);
+ this.applySort();
+ },
+
+ /**
+ * Applies the last used sort to the current data.
+ */
+ applySort: function(suppressEvent){
+ if(typeof this.sortColumn != 'undefined'){
+ this.sort(this.sortInfo, this.sortColumn, this.sortDir, suppressEvent);
+ }
+ },
+
+ /**
+ * Sets the default sort info. Note: this function does not actually apply the sort.
+ * @param {Function/Object} sortInfo A sort comparison function or null to use the default or A object that has a method getSortType(index) that returns a function like
+ * a grid column model.
+ * @param {Number} columnIndex The column index to sort by
+ * @param {String} direction The direction of the sort ('DESC' or 'ASC')
+ */
+ setDefaultSort: function(sortInfo, columnIndex, direction){
+ this.sortInfo = sortInfo;
+ this.sortColumn = columnIndex;
+ this.sortDir = direction;
+ },
+ /**
+ * Sorts the data by the specified column - Uses the sortType specified for the column in the passed columnModel.
+ * @param {Function/Object} sortInfo A sort comparison function or null to use the default or A object that has a method getSortType(index) that returns a function like
+ * a grid column model.
+ * @param {Number} columnIndex The column index to sort by
+ * @param {String} direction The direction of the sort ('DESC' or 'ASC')
+ */
+ sort: function(sortInfo, columnIndex, direction, suppressEvent){
+ // store these so we can maintain sorting when we load new data
+ this.sortInfo = sortInfo;
+ this.sortColumn = columnIndex;
+ this.sortDir = direction;
+
+ var dsc = (direction && direction.toUpperCase() == 'DESC');
+ var sortType = null;
+ if(sortInfo != null){
+ if(typeof sortInfo == 'function'){
+ sortType = sortInfo;
+ }else if(typeof sortInfo == 'object'){
+ sortType = sortInfo.getSortType(columnIndex);;
+ }
+ }
+ var fn = function(cells, cells2){
+ var v1 = sortType ? sortType(cells[columnIndex], cells) : cells[columnIndex];
+ var v2 = sortType ? sortType(cells2[columnIndex], cells2) : cells2[columnIndex];
+ if(v1 < v2)
+ return dsc ? +1 : -1;
+ if(v1 > v2)
+ return dsc ? -1 : +1;
+ return 0;
+ };
+ this.data.sort(fn);
+ if(!suppressEvent){
+ this.fireRowsSorted(columnIndex, direction);
+ }
+ },
+
+ /**
+ * Calls passed function with each rows data - if the function returns false it stops.
+ * @param {Function} fn
+ * @param {Object} scope (optional)
+ */
+ each: function(fn, scope){
+ var d = this.data;
+ for(var i = 0, len = d.length; i < len; i++){
+ if(fn.call(scope || window, d[i], i) === false) break;
+ }
+ }
+});
+
+/**
+ * Alias to YAHOO.ext.grid.DefaultColumnModel.sortTypes
+ * @static
+ */
+if(YAHOO.ext.grid.DefaultColumnModel){
+ YAHOO.ext.grid.DefaultDataModel.sortTypes = YAHOO.ext.grid.DefaultColumnModel.sortTypes;
+}
diff --git a/frontend/beta/js/YUI-extensions/data/JSONDataModel.js b/frontend/beta/js/YUI-extensions/data/JSONDataModel.js
new file mode 100644
index 0000000..ca48dce
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/data/JSONDataModel.js
@@ -0,0 +1,81 @@
+
+/**
+ * @class YAHOO.ext.grid.JSONDataModel
+ * This is an implementation of a DataModel used by the Grid. It works
+ * with JSON data.
+ *
+ * // column 1 starts with Jack, column 2 filtered by myFcn, column 3 equals 'Fred'
+ * dataModel.filter({1: /^Jack.+/i}, 2: myFcn, 3: 'Fred'});
+ *
Example schema:
+ *
+ * @extends YAHOO.ext.grid.LoadableDataModel
+ * @constructor
+*/
+YAHOO.ext.grid.JSONDataModel = function(schema){
+ YAHOO.ext.grid.JSONDataModel.superclass.constructor.call(this, YAHOO.ext.grid.LoadableDataModel.JSON);
+ /**@private*/
+ this.schema = schema;
+};
+YAHOO.extendX(YAHOO.ext.grid.JSONDataModel, YAHOO.ext.grid.LoadableDataModel, {
+ /**
+ * Overrides loadData in LoadableDataModel to process JSON data
+ * @param {Object} data The JSON object to load
+ * @param {Function} callback
+ */
+ loadData : function(data, callback, keepExisting){
+ var idField = this.schema.id;
+ var fields = this.schema.fields;
+ try{
+ if(this.schema.totalProperty){
+ var v = parseInt(eval('data.' + this.schema.totalProperty), 10);
+ if(!isNaN(v)){
+ this.totalCount = v;
+ }
+ }
+ var rowData = [];
+ var root = eval('data.' + this.schema.root);
+ for(var i = 0; i < root.length; i++){
+ var node = root[i];
+ var colData = [];
+ colData.node = node;
+ colData.id = (typeof node[idField] != 'undefined' && node[idField] !== '' ? node[idField] : String(i));
+ for(var j = 0; j < fields.length; j++) {
+ var val = node[fields[j]];
+ if(typeof val == 'undefined'){
+ val = '';
+ }
+ if(this.preprocessors[j]){
+ val = this.preprocessors[j](val);
+ }
+ colData.push(val);
+ }
+ rowData.push(colData);
+ }
+ if(keepExisting !== true){
+ this.removeAll();
+ }
+ this.addRows(rowData);
+ if(typeof callback == 'function'){
+ callback(this, true);
+ }
+ this.fireLoadEvent();
+ }catch(e){
+ this.fireLoadException(e, null);
+ if(typeof callback == 'function'){
+ callback(this, false);
+ }
+ }
+ },
+
+ /**
+ * Overrides getRowId in DefaultDataModel to return the ID value of the specified node.
+ * @param {Number} rowIndex
+ * @return {Number}
+ */
+ getRowId : function(rowIndex){
+ return this.data[rowIndex].id;
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/data/LoadableDataModel.js b/frontend/beta/js/YUI-extensions/data/LoadableDataModel.js
new file mode 100644
index 0000000..07def44
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/data/LoadableDataModel.js
@@ -0,0 +1,330 @@
+/**
+ * @class YAHOO.ext.grid.LoadableDataModel
+ * This class extends DefaultDataModel and adds the core functionality to load data remotely. Generally you will want to use one of it's subclasses.
+ * var schema = {
+ * root: 'Results.Result',
+ * id: 'ASIN',
+ * fields: ['Author', 'Title', 'Manufacturer', 'ProductGroup']
+ * };
+ *
+ * @extends YAHOO.ext.grid.DefaultDataModel
+ * @constructor
+ * @param {String} dataType YAHOO.ext.grid.LoadableDataModel.XML, YAHOO.ext.grid.LoadableDataModel.TEXT or YAHOO.ext.grid.JSON
+*/
+YAHOO.ext.grid.LoadableDataModel = function(dataType){
+ YAHOO.ext.grid.LoadableDataModel.superclass.constructor.call(this, []);
+
+ /** Fires when a successful load is completed - fireDirect sig: (this)
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ * @private
+ */
+ this.onLoad = new YAHOO.util.CustomEvent('load');
+ /** Fires when a load fails - fireDirect sig: (this, errorMsg, responseObj)
+ * @type YAHOO.util.CustomEvent
+ * @deprecated Use addListener instead of accessing directly
+ * @private
+ */
+ this.onLoadException = new YAHOO.util.CustomEvent('loadException');
+ /**
+ * @event load
+ * Fires when new data has successfully been loaded
+ * @param {DataModel} this
+ */
+ this.events['load'] = this.onLoad;
+ /**
+ * @event beforeload
+ * Fires before a load takes place
+ * @param {DataModel} this
+ */
+ this.events['beforeload'] = new YAHOO.util.CustomEvent('beforeload');
+ /**
+ * @event loadexception
+ * Fires when there's an error loading data
+ * @param {DataModel} this
+ * @param {Exception} e The exception object or null
+ * @param {Object} response The Connect response object
+ */
+ this.events['loadexception'] = this.onLoadException;
+
+ /**@private*/
+ this.dataType = dataType;
+ /**@private*/
+ this.preprocessors = [];
+ /**@private*/
+ this.postprocessors = [];
+
+ // paging info
+ /** The active page @type Number*/
+ this.loadedPage = 1;
+ /** True to use remote sorting, initPaging automatically sets this to true @type Boolean */
+ this.remoteSort = false;
+ /** The number of records per page @type Number*/
+ this.pageSize = 0;
+ /** The script/page to call to provide paged/sorted data @type String*/
+ this.pageUrl = null;
+ /** An object of key/value pairs to be passed as parameters
+ * when loading pages/sorting @type Object*/
+ this.baseParams = {};
+ /** Maps named params to url parameters - Override to specify your own param names */
+ this.paramMap = {'page':'page', 'pageSize':'pageSize', 'sortColumn':'sortColumn', 'sortDir':'sortDir'};
+
+};
+YAHOO.extendX(YAHOO.ext.grid.LoadableDataModel, YAHOO.ext.grid.DefaultDataModel, {
+
+ /** @ignore */
+ setLoadedPage: function(pageNum, userCallback){
+ this.loadedPage = pageNum;
+ if(typeof userCallback == 'function'){
+ userCallback();
+ }
+ },
+
+ /** Returns true if this model uses paging @return Boolean */
+ isPaged: function(){
+ return this.pageSize > 0;
+ },
+
+ /** Returns the total number of records available, override if needed @return {Number} */
+ getTotalRowCount: function(){
+ return this.totalCount || this.getRowCount();
+ },
+
+ /** Returns the number of records per page @return Number */
+ getPageSize: function(){
+ return this.pageSize;
+ },
+
+ /** Returns the total number of pages available @return Number */
+ getTotalPages: function(){
+ if(this.getPageSize() == 0 || this.getTotalRowCount() == 0){
+ return 1;
+ }
+ return Math.ceil(this.getTotalRowCount()/this.getPageSize());
+ },
+
+ /** Initializes paging for this model.
+ * @param {String} url
+ * @param {Number} pageSize
+ * @param {Object} baseParams (optional) Object containing key/value pairs to add to all requests
+ */
+ initPaging: function(url, pageSize, baseParams){
+ this.pageUrl = url;
+ this.pageSize = pageSize;
+ this.remoteSort = true;
+ if(baseParams) this.baseParams = baseParams;
+ },
+
+ /** @ignore */
+ createParams: function(pageNum, sortColumn, sortDir){
+ var params = {}, map = this.paramMap;
+ for(var key in this.baseParams){
+ if(typeof this.baseParams[key] != 'function'){
+ params[key] = this.baseParams[key];
+ }
+ }
+ params[map['page']] = pageNum;
+ params[map['pageSize']] = this.getPageSize();
+ params[map['sortColumn']] = (typeof sortColumn == 'undefined' ? '' : sortColumn);
+ params[map['sortDir']] = sortDir || '';
+ return params;
+ },
+
+ /**
+ * Loads a page of data.
+ * @param {Number} pageNum Which page to load. The first page is 1.
+ * @param {Function} callback (optional) Optional callback when loading is complete
+ * @param {Boolean} keepExisting (optional) true to keep existing data and append the new data
+ */
+ loadPage: function(pageNum, callback, keepExisting){
+ var sort = this.getSortState();
+ var params = this.createParams(pageNum, sort.column, sort.direction);
+ this.load(this.pageUrl, params, this.setLoadedPage.createDelegate(this, [pageNum, callback]),
+ keepExisting ? (pageNum-1) * this.pageSize : null);
+ },
+
+ /** @ignore */
+ applySort: function(suppressEvent){
+ if(!this.remoteSort){
+ YAHOO.ext.grid.LoadableDataModel.superclass.applySort.apply(this, arguments);
+ }else if(!suppressEvent){
+ var sort = this.getSortState();
+ if(sort.column){
+ this.fireRowsSorted(sort.column, sort.direction, true);
+ }
+ }
+ },
+
+ /** @ignore */
+ resetPaging: function(){
+ this.loadedPage = 1;
+ },
+
+ /* Overridden sort method to use remote sorting if turned on */
+ sort: function(sortInfo, columnIndex, direction, suppressEvent){
+ if(!this.remoteSort){
+ YAHOO.ext.grid.LoadableDataModel.superclass.sort.apply(this, arguments);
+ }else{
+ this.sortInfo = sortInfo;
+ this.sortColumn = columnIndex;
+ this.sortDir = direction;
+ var params = this.createParams(this.loadedPage, columnIndex, direction);
+ this.load(this.pageUrl, params, this.fireRowsSorted.createDelegate(this, [columnIndex, direction, true]));
+ }
+ },
+
+ /**
+ * Initiates the loading of the data from the specified URL - Failed load attempts will
+ * fire the {@link #loadexception} event.
+ * @param {Object/String} url The url from which the data can be loaded
+ * @param {String/Object} params (optional) The parameters to pass as either a url encoded string "param1=1¶m2=2" or as an object {param1: 1, param2: 2}
+ * @param {Function} callback (optional) Callback when load is complete - called with signature (this, true for success, false for failure)
+ * @param {Number} insertIndex (optional) if present, loaded data is inserted at the specified index instead of overwriting existing data
+ */
+ load: function(url, params, callback, insertIndex){
+ this.fireEvent('beforeload', this);
+ if(params && typeof params != 'string'){ // must be object
+ var buf = [];
+ for(var key in params){
+ if(typeof params[key] != 'function'){
+ buf.push(encodeURIComponent(key), '=', encodeURIComponent(params[key]), '&');
+ }
+ }
+ delete buf[buf.length-1];
+ params = buf.join('');
+ }
+ var cb = {
+ success: this.processResponse,
+ failure: this.processException,
+ scope: this,
+ argument: {callback: callback, insertIndex: insertIndex}
+ };
+ var method = params ? 'POST' : 'GET';
+ this.transId = YAHOO.util.Connect.asyncRequest(method, url, cb, params);
+ },
+
+ /**@private*/
+ processResponse: function(response){
+ var cb = response.argument.callback;
+ var keepExisting = (typeof response.argument.insertIndex == 'number');
+ var insertIndex = response.argument.insertIndex;
+ switch(this.dataType){
+ case YAHOO.ext.grid.LoadableDataModel.XML:
+ this.loadData(response.responseXML, cb, keepExisting, insertIndex);
+ break;
+ case YAHOO.ext.grid.LoadableDataModel.JSON:
+ var rtext = response.responseText;
+ try { // this code is a modified version of Yahoo! UI DataSource JSON parsing
+ // Trim leading spaces
+ while(rtext.substring(0,1) == " ") {
+ rtext = rtext.substring(1, rtext.length);
+ }
+ // Invalid JSON response
+ if(rtext.indexOf("{") < 0) {
+ throw "Invalid JSON response";
+ }
+
+ // Empty (but not invalid) JSON response
+ if(rtext.indexOf("{}") === 0) {
+ this.loadData({}, response.argument.callback);
+ return;
+ }
+
+ // Turn the string into an object literal...
+ // ...eval is necessary here
+ var jsonObjRaw = eval("(" + rtext + ")");
+ if(!jsonObjRaw) {
+ throw "Error evaling JSON response";
+ }
+ this.loadData(jsonObjRaw, cb, keepExisting, insertIndex);
+ } catch(e) {
+ this.fireLoadException(e, response);
+ if(typeof cb == 'function'){
+ cb(this, false);
+ }
+ }
+ break;
+ case YAHOO.ext.grid.LoadableDataModel.TEXT:
+ this.loadData(response.responseText, cb, keepExisting, insertIndex);
+ break;
+ };
+ },
+
+ /**@private*/
+ processException: function(response){
+ this.fireLoadException(null, response);
+ if(typeof response.argument.callback == 'function'){
+ response.argument.callback(this, false);
+ }
+ },
+
+ fireLoadException: function(e, responseObj){
+ this.onLoadException.fireDirect(this, e, responseObj);
+ },
+
+ fireLoadEvent: function(){
+ this.fireEvent('load', this.loadedPage, this.getTotalPages());
+ },
+
+ /**
+ * Adds a preprocessor function to parse data before it is added to the Model - ie. Date.parse to parse dates.
+ * @param {Number} columnIndex
+ * @param {Function} fn
+ */
+ addPreprocessor: function(columnIndex, fn){
+ this.preprocessors[columnIndex] = fn;
+ },
+
+ /**
+ * Gets the preprocessor function for the specified column.
+ * @param {Number} columnIndex
+ * @return {Function}
+ */
+ getPreprocessor: function(columnIndex){
+ return this.preprocessors[columnIndex];
+ },
+
+ /**
+ * Removes a preprocessor function.
+ * @param {Number} columnIndex
+ */
+ removePreprocessor: function(columnIndex){
+ this.preprocessors[columnIndex] = null;
+ },
+
+ /**
+ * Adds a postprocessor function to format data before updating the underlying data source (ie. convert date to string before updating XML document).
+ * @param {Number} columnIndex
+ * @param {Function} fn
+ */
+ addPostprocessor: function(columnIndex, fn){
+ this.postprocessors[columnIndex] = fn;
+ },
+
+ /**
+ * Gets the postprocessor function for the specified column.
+ * @param {Number} columnIndex
+ * @return {Function}
+ */
+ getPostprocessor: function(columnIndex){
+ return this.postprocessors[columnIndex];
+ },
+
+ /**
+ * Removes a postprocessor function.
+ * @param {Number} columnIndex
+ */
+ removePostprocessor: function(columnIndex){
+ this.postprocessors[columnIndex] = null;
+ },
+ /**
+ * Empty interface method - Called to process the data returned by the XHR - Classes which extend LoadableDataModel should implement this method.
+ * See {@link YAHOO.ext.XMLDataModel} for an example implementation.
+ */
+ loadData: function(data, callback, keepExisting, insertIndex){
+
+ }
+});
+
+YAHOO.ext.grid.LoadableDataModel.XML = 'xml';
+YAHOO.ext.grid.LoadableDataModel.JSON = 'json';
+YAHOO.ext.grid.LoadableDataModel.TEXT = 'text';
+
+
+
+
+
diff --git a/frontend/beta/js/YUI-extensions/data/Tree.js b/frontend/beta/js/YUI-extensions/data/Tree.js
new file mode 100644
index 0000000..afa5b20
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/data/Tree.js
@@ -0,0 +1,412 @@
+YAHOO.namespace('ext.data');
+
+/**
+ * @class YAHOO.ext.data.Tree
+ * @extends YAHOO.ext.util.Observable
+ * The class represents a tree data structure and bubbles all the events for it's nodes. The nodes
+ * in the tree have most standard DOM functionality.
+ * @constructor
+ * @param {Node} root (optional) The root node
+ */
+YAHOO.ext.data.Tree = function(root){
+ this.nodeHash = {};
+ this.root = null;
+ if(root){
+ this.setRootNode(root);
+ }
+ this.events = {
+ 'append' : true,
+ 'remove' : true,
+ 'move' : true,
+ 'insert' : true,
+ 'beforeappend' : true,
+ 'beforeremove' : true,
+ 'beforemove' : true,
+ 'beforeinsert' : true
+ };
+};
+
+YAHOO.extendX(YAHOO.ext.data.Tree, YAHOO.ext.util.Observable, {
+ pathSeparator: '/',
+
+ getRootNode : function(){
+ return this.root;
+ },
+
+ setRootNode : function(node){
+ this.root = node;
+ node.ownerTree = this;
+ node.isRoot = true;
+ return node;
+ },
+
+ getNodeById : function(id){
+ return this.nodeHash[id];
+ },
+
+ registerNode : function(node){
+ this.nodeHash[node.id] = node;
+ },
+
+ unregisterNode : function(node){
+ delete this.nodeHash[node.id];
+ },
+
+ toString : function(){
+ return '[Tree'+(this.id?' '+this.id:'')+']';
+ }
+});
+
+/**
+ * @class YAHOO.ext.tree.Node
+ * @extends YAHOO.ext.util.Observable
+ * @cfg {String} text The text for this node
+ * @cfg {String} id The id for this node
+ * @constructor
+ * @param {Object} attributes The attributes/config for the node
+ */
+YAHOO.ext.data.Node = function(attributes){
+ this.attributes = attributes || {};
+ this.leaf = this.attributes.leaf;
+ this.id = this.attributes.id;
+ if(!this.id){
+ this.id = YAHOO.util.Dom.generateId(null, 'ynode-');
+ this.attributes.id = this.id;
+ }
+
+ this.childNodes = [];
+ if(!this.childNodes.indexOf){ // indexOf is a must
+ this.childNodes.indexOf = function(o){
+ for(var i = 0, len = this.length; i < len; i++){
+ if(this[i] == o) return i;
+ }
+ return -1;
+ };
+ }
+ this.parentNode = null;
+ this.firstChild = null;
+ this.lastChild = null;
+ this.previousSibling = null;
+ this.nextSibling = null;
+
+ this.events = {
+ 'append' : true,
+ 'remove' : true,
+ 'move' : true,
+ 'insert' : true,
+ 'beforeappend' : true,
+ 'beforeremove' : true,
+ 'beforemove' : true,
+ 'beforeinsert' : true
+ };
+};
+
+YAHOO.extendX(YAHOO.ext.data.Node, YAHOO.ext.util.Observable, {
+ fireEvent : function(evtName){
+ // first do standard event for this node
+ if(YAHOO.ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){
+ return false;
+ }
+ // then bubble it up to the tree if the event wasn't cancelled
+ if(this.ownerTree){
+ if(this.ownerTree.fireEvent.apply(this.ownerTree, arguments) === false){
+ return false;
+ }
+ }
+ return true;
+ },
+
+ isLeaf : function(){
+ return this.leaf === true;
+ },
+
+ setFirstChild : function(node){
+ this.firstChild = node;
+ },
+
+ setLastChild : function(node){
+ this.lastChild = node;
+ },
+
+ isLast : function(){
+ return (!this.parentNode ? true : this.parentNode.lastChild == this);
+ },
+
+ isFirst : function(){
+ return (!this.parentNode ? true : this.parentNode.firstChild == this);
+ },
+
+ hasChildNodes : function(){
+ return !this.isLeaf() && this.childNodes.length > 0;
+ },
+
+ appendChild : function(node){
+ var multi = false;
+ if(node instanceof Array){
+ multi = node;
+ }else if(arguments.length > 1){
+ multi = arguments;
+ }
+ // if passed an array or multiple args do them one by one
+ if(multi){
+ for(var i = 0, len = multi.length; i < len; i++) {
+ this.appendChild(multi[i]);
+ }
+ }else{
+ if(this.fireEvent('beforeappend', this.ownerTree, this, node) === false){
+ return false;
+ }
+ var index = this.childNodes.length;
+ var oldParent = node.parentNode;
+ // it's a move, make sure we move it cleanly
+ if(oldParent){
+ if(node.fireEvent('beforemove', node.getOwnerTree(), node, oldParent, this, index) === false){
+ return false;
+ }
+ oldParent.removeChild(node);
+ }
+ var index = this.childNodes.length;
+ if(index == 0){
+ this.setFirstChild(node);
+ }
+ this.childNodes.push(node);
+ node.parentNode = this;
+ var ps = this.childNodes[index-1];
+ if(ps){
+ node.previousSibling = ps;
+ ps.nextSibling = node;
+ }
+ this.setLastChild(node);
+ node.setOwnerTree(this.getOwnerTree());
+ this.fireEvent('append', this.ownerTree, this, node, index);
+ if(oldParent){
+ node.fireEvent('move', this.ownerTree, node, oldParent, this, index);
+ }
+ return node;
+ }
+ },
+
+ removeChild : function(node){
+ var index = this.childNodes.indexOf(node);
+ if(index == -1){
+ return false;
+ }
+ if(this.fireEvent('beforeremove', this.ownerTree, this, node) === false){
+ return false;
+ }
+
+ // remove it from childNodes collection
+ this.childNodes.splice(index, 1);
+
+ // update siblings
+ if(node.previousSibling){
+ node.previousSibling.nextSibling = node.nextSibling;
+ }
+ if(node.nextSibling){
+ node.nextSibling.previousSibling = node.previousSibling;
+ }
+
+ // update child refs
+ if(this.firstChild == node){
+ this.setFirstChild(node.nextSibling);
+ }
+ if(this.lastChild == node){
+ this.setLastChild(node.previousSibling);
+ }
+
+ node.setOwnerTree(null);
+ // clear any references from the node
+ node.parentNode = null;
+ node.previousSibling = null;
+ node.nextSibling = null;
+ this.fireEvent('remove', this.ownerTree, this, node);
+ return node;
+ },
+
+ insertBefore : function(node, refNode){
+ if(!refNode){ // like standard Dom, refNode can be null for append
+ return this.appendChild(node);
+ }
+ // nothing to do
+ if(node == refNode){
+ return false;
+ }
+
+ if(this.fireEvent('beforeinsert', this.ownerTree, this, node, refNode) === false){
+ return false;
+ }
+ var index = this.childNodes.indexOf(refNode);
+ var oldParent = node.parentNode;
+ var refIndex = index;
+
+ // when moving internally, indexes will change after remove
+ if(oldParent == this && this.childNodes.indexOf(node) < index){
+ refIndex--;
+ }
+
+ // it's a move, make sure we move it cleanly
+ if(oldParent){
+ if(node.fireEvent('beforemove', node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
+ return false;
+ }
+ oldParent.removeChild(node);
+ }
+ if(refIndex == 0){
+ this.setFirstChild(node);
+ }
+ this.childNodes.splice(refIndex, 0, node);
+ node.parentNode = this;
+ var ps = this.childNodes[refIndex-1];
+ if(ps){
+ node.previousSibling = ps;
+ ps.nextSibling = node;
+ }
+ node.nextSibling = refNode;
+ node.setOwnerTree(this.getOwnerTree());
+ this.fireEvent('insert', this.ownerTree, this, node, refNode);
+ if(oldParent){
+ node.fireEvent('move', this.ownerTree, node, oldParent, this, refIndex, refNode);
+ }
+ return node;
+ },
+
+ item : function(index){
+ return this.childNodes[index];
+ },
+
+ replaceChild : function(newChild, oldChild){
+ this.insertBefore(newChild, oldChild);
+ this.removeChild(oldChild);
+ return oldChild;
+ },
+
+ indexOf : function(child){
+ return this.childNodes.indexOf(child);
+ },
+
+ getOwnerTree : function(){
+ // if it doesn't have one, look for one
+ if(!this.ownerTree){
+ var p = this;
+ while(p){
+ if(p.ownerTree){
+ this.ownerTree = p.ownerTree;
+ break;
+ }
+ p = p.parentNode;
+ }
+ }
+ return this.ownerTree;
+ },
+
+ setOwnerTree : function(tree){
+ // if it's move, we need to update everyone
+ if(tree != this.ownerTree){
+ if(this.ownerTree){
+ this.ownerTree.unregisterNode(this);
+ }
+ this.ownerTree = tree;
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ cs[i].setOwnerTree(tree);
+ }
+ if(tree){
+ tree.registerNode(this);
+ }
+ }
+ },
+
+ getPath : function(attr){
+ attr = attr || 'id';
+ var p = this.parentNode;
+ var b = [this.attributes[attr]];
+ while(p){
+ b.unshift(p.attributes[attr]);
+ p = p.parentNode;
+ }
+ var sep = this.getOwnerTree().pathSeparator;
+ return sep + b.join(sep);
+ },
+
+ bubble : function(fn, scope, args){
+ var p = this;
+ while(p){
+ if(fn.call(scope || p, args || p) === false){
+ break;
+ }
+ p = p.parentNode;
+ }
+ },
+
+ cascade : function(fn, scope, args){
+ if(fn.call(scope || this, args || this) !== false){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ cs[i].cascade(fn, scope, args);
+ }
+ }
+ },
+
+ eachChild : function(fn, scope, args){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ if(fn.call(scope || this, args || cs[i]) === false){
+ break;
+ }
+ }
+ },
+
+ findChild : function(attribute, value){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ if(cs[i].attributes[attribute] == value){
+ return cs[i];
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Sorts this nodes children using the supplied sort function
+ * @param {Function} fn
+ * @param {Object} scope
+ */
+ sort : function(fn, scope){
+ var cs = this.childNodes;
+ var len = cs.length;
+ if(len > 0){
+ var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
+ cs.sort(sortFn);
+ for(var i = 0; i < len; i++){
+ var n = cs[i];
+ n.previousSibling = cs[i-1];
+ n.nextSibling = cs[i+1];
+ if(i == 0){
+ this.setFirstChild(n);
+ }
+ if(i == len-1){
+ this.setLastChild(n);
+ }
+ }
+ }
+ },
+
+ contains : function(node){
+ return node.isAncestor(this);
+ },
+
+ isAncestor : function(node){
+ var p = this.parentNode;
+ while(p){
+ if(p == node){
+ return true;
+ }
+ p = p.parentNode;
+ }
+ return false;
+ },
+
+ toString : function(){
+ return '[Node'+(this.id?' '+this.id:'')+']';
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/data/XMLDataModel.js b/frontend/beta/js/YUI-extensions/data/XMLDataModel.js
new file mode 100644
index 0000000..e312a9e
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/data/XMLDataModel.js
@@ -0,0 +1,274 @@
+/**
+ * @class YAHOO.ext.grid.XMLDataModel
+ * This is an implementation of a DataModel used by the Grid. It works
+ * with XML data.
+ *
Example schema from Amazon search:
+ *
+ * @extends YAHOO.ext.grid.LoadableDataModel
+ * @constructor
+ * @param {Object} schema The schema to use
+ * @param {XMLDocument} xml An XML document to load immediately
+*/
+YAHOO.ext.grid.XMLDataModel = function(schema, xml){
+ YAHOO.ext.grid.XMLDataModel.superclass.constructor.call(this, YAHOO.ext.grid.LoadableDataModel.XML);
+ /**@private*/
+ this.schema = schema;
+ this.xml = xml;
+ if(xml){
+ this.loadData(xml);
+ }
+ this.idSeed = 0;
+};
+YAHOO.extendX(YAHOO.ext.grid.XMLDataModel, YAHOO.ext.grid.LoadableDataModel, {
+
+ getDocument: function(){
+ return this.xml;
+ },
+
+ /**
+ * Overrides loadData in LoadableDataModel to process XML
+ * @param {XMLDocument} doc The document to load
+ * @param {Function} callback (optional) callback to call when loading is complete
+ * @param {Boolean} keepExisting (optional) true to keep existing data
+ * @param {Number} insertIndex (optional) if present, loaded data is inserted at the specified index instead of overwriting existing data
+ */
+ loadData: function(doc, callback, keepExisting, insertIndex){
+ this.xml = doc;
+ var idField = this.schema.id;
+ var fields = this.schema.fields;
+ if(this.schema.totalTag){
+ this.totalCount = null;
+ var totalNode = doc.getElementsByTagName(this.schema.totalTag);
+ if(totalNode && totalNode.item(0) && totalNode.item(0).firstChild) {
+ var v = parseInt(totalNode.item(0).firstChild.nodeValue, 10);
+ if(!isNaN(v)){
+ this.totalCount = v;
+ }
+ }
+ }
+ var rowData = [];
+ var nodes = doc.getElementsByTagName(this.schema.tagName);
+ if(nodes && nodes.length > 0) {
+ for(var i = 0; i < nodes.length; i++) {
+ var node = nodes.item(i);
+ var colData = [];
+ colData.node = node;
+ colData.id = this.getNamedValue(node, idField, String(++this.idSeed));
+ for(var j = 0; j < fields.length; j++) {
+ var val = this.getNamedValue(node, fields[j], "");
+ if(this.preprocessors[j]){
+ val = this.preprocessors[j](val);
+ }
+ colData.push(val);
+ }
+ rowData.push(colData);
+ }
+ }
+ if(keepExisting !== true){
+ YAHOO.ext.grid.XMLDataModel.superclass.removeAll.call(this);
+ }
+ if(typeof insertIndex != 'number'){
+ insertIndex = this.getRowCount();
+ }
+ YAHOO.ext.grid.XMLDataModel.superclass.insertRows.call(this, insertIndex, rowData);
+ if(typeof callback == 'function'){
+ callback(this, true);
+ }
+ this.fireLoadEvent();
+ },
+
+ /**
+ * Adds a row to this DataModel and syncs the XML document
+ * @param {String} id The id of the row, if null the next row index is used
+ * @param {Array} cellValues The cell values for this row
+ * @return {Number} The index of the new row (if the model is sorted this index may not be accurate)
+ */
+ addRow: function(id, cellValues){
+ var node = this.createNode(this.xml, id, cellValues);
+ cellValues.id = id || ++this.idSeed;
+ cellValues.node = node;
+ return YAHOO.ext.grid.XMLDataModel.superclass.addRow.call(this, cellValues);
+ },
+
+ /**
+ * Inserts a row into this DataModel and syncs the XML document
+ * @param {Number} index The index to insert the row
+ * @param {String} id The id of the row, if null the next row index is used
+ * @param {Array} cellValues The cell values for this row
+ * @return {Number} The index of the new row (if the model is sorted this index may not be accurate)
+ */
+ insertRow: function(index, id, cellValues){
+ var node = this.createNode(this.xml, id, cellValues);
+ cellValues.id = id || ++this.idSeed;
+ cellValues.node = node;
+ return YAHOO.ext.grid.XMLDataModel.superclass.insertRow.call(this, index, cellValues);
+ },
+
+ /**
+ * Removes the row from DataModel and syncs the XML document
+ * @param {Number} index The index of the row to remove
+ */
+ removeRow: function(index){
+ var node = this.data[index].node;
+ node.parentNode.removeChild(node);
+ YAHOO.ext.grid.XMLDataModel.superclass.removeRow.call(this, index, index);
+ },
+
+ getNode: function(rowIndex){
+ return this.data[rowIndex].node;
+ },
+
+ /**
+ * Override this method to define your own node creation routine for when new rows are added.
+ * By default this method clones the first node and sets the column values in the newly cloned node.
+ * In many instances this will not work and you will have to create the node manually.
+ * @param {XMLDocument} xmlDoc The xml document being used by this model
+ * @param {String/Number} id The row id
+ * @param {Array} colData The column data for the new node
+ * @return {XMLNode} The created node
+ */
+ createNode: function(xmlDoc, id, colData){
+ var template = this.data[0].node;
+ var newNode = template.cloneNode(true);
+ var fields = this.schema.fields;
+ for(var i = 0, len = fields.length; i < len; i++){
+ var nodeValue = colData[i];
+ if(this.postprocessors[i]){
+ nodeValue = this.postprocessors[i](nodeValue);
+ }
+ this.setNamedValue(newNode, fields[i], nodeValue);
+ }
+ if(id){
+ this.setNamedValue(newNode, this.schema.idField, id);
+ }
+ template.parentNode.appendChild(newNode);
+ return newNode;
+ },
+
+ /**
+ * @private
+ * Convenience function looks for value in attributes, then in children tags - also
+ * normalizes namespace matches (ie matches ns:tag, FireFox matches tag and not ns:tag).
+ */
+ getNamedValue: function(node, name, defaultValue){
+ if(!node || !name){
+ return defaultValue;
+ }
+ var nodeValue = defaultValue;
+ var attrNode = node.attributes.getNamedItem(name);
+ if(attrNode) {
+ nodeValue = attrNode.value;
+ } else {
+ var childNode = node.getElementsByTagName(name);
+ if(childNode && childNode.item(0) && childNode.item(0).firstChild) {
+ nodeValue = childNode.item(0).firstChild.nodeValue;
+ }else{
+ // try to strip namespace for FireFox
+ var index = name.indexOf(':');
+ if(index > 0){
+ return this.getNamedValue(node, name.substr(index+1), defaultValue);
+ }
+ }
+ }
+ return nodeValue;
+ },
+
+ /**
+ * @private
+ * Convenience function set a value in the underlying xml node.
+ */
+ setNamedValue: function(node, name, value){
+ if(!node || !name){
+ return;
+ }
+ var attrNode = node.attributes.getNamedItem(name);
+ if(attrNode) {
+ attrNode.value = value;
+ return;
+ }
+ var childNode = node.getElementsByTagName(name);
+ if(childNode && childNode.item(0) && childNode.item(0).firstChild) {
+ childNode.item(0).firstChild.nodeValue = value;
+ }else{
+ // try to strip namespace for FireFox
+ var index = name.indexOf(':');
+ if(index > 0){
+ this.setNamedValue(node, name.substr(index+1), value);
+ }
+ }
+ },
+
+ /**
+ * Overrides DefaultDataModel.setValueAt to update the underlying XML Document
+ * @param {Object} value The new value
+ * @param {Number} rowIndex
+ * @param {Number} colIndex
+ */
+ setValueAt: function(value, rowIndex, colIndex){
+ var node = this.data[rowIndex].node;
+ if(node){
+ var nodeValue = value;
+ if(this.postprocessors[colIndex]){
+ nodeValue = this.postprocessors[colIndex](value);
+ }
+ this.setNamedValue(node, this.schema.fields[colIndex], nodeValue);
+ }
+ YAHOO.ext.grid.XMLDataModel.superclass.setValueAt.call(this, value, rowIndex, colIndex);
+ },
+
+ /**
+ * Overrides getRowId in DefaultDataModel to return the ID value of the specified node.
+ * @param {Number} rowIndex
+ * @return {Number}
+ */
+ getRowId: function(rowIndex){
+ return this.data[rowIndex].id;
+ },
+
+ addRows : function(rowData){
+ for(var j = 0, len = rowData.length; j < len; j++){
+ var cellValues = rowData[j];
+ var id = ++this.idSeed;
+ var node = this.createNode(this.xml, id, cellValues);
+ cellValues.node=node;
+ cellValues.id = cellValues.id || id;
+ YAHOO.ext.grid.XMLDataModel.superclass.addRow.call(this,cellValues);
+ }
+ },
+
+ insertRows : function(index, rowData){
+ // copy original array so it is not reversed
+ rowData = rowData.slice(0).reverse();
+ for(var j = 0, len = rowData.length; j < len; j++){
+ var cellValues = rowData[j];
+ var id = ++this.idSeed;
+ var node = this.createNode(this.xml, id, cellValues);
+ cellValues.id = cellValues.id || id;
+ cellValues.node = node;
+ YAHOO.ext.grid.XMLDataModel.superclass.insertRow.call(this, index, cellValues);
+ }
+ }
+});
+
+YAHOO.ext.grid.XMLQueryDataModel = function(){
+ YAHOO.ext.grid.XMLQueryDataModel.superclass.constructor.apply(this, arguments);
+};
+YAHOO.extendX(YAHOO.ext.grid.XMLQueryDataModel, YAHOO.ext.grid.XMLDataModel, {
+ getNamedValue: function(node, name, defaultValue){
+ if(!node || !name){
+ return defaultValue;
+ }
+ var nodeValue = defaultValue;
+ var childNode = cssQuery(name, node);
+ if(childNode && childNode[0]) {
+ nodeValue = childNode[0].firstChild.nodeValue;
+ }
+ return nodeValue;
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/dd/DragSource.js b/frontend/beta/js/YUI-extensions/dd/DragSource.js
new file mode 100644
index 0000000..efee2d6
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/dd/DragSource.js
@@ -0,0 +1,218 @@
+// kill drag drop dependency
+if(YAHOO.util.DragDrop){
+
+YAHOO.ext.dd.DragSource = function(el, config){
+ this.el = getEl(el);
+ this.dragData = {};
+
+ YAHOO.ext.util.Config.apply(this, config);
+
+ if(!this.proxy){
+ this.proxy = new YAHOO.ext.dd.StatusProxy();
+ }
+ this.el.on('mouseup', this.handleMouseUp);
+ YAHOO.ext.dd.DragSource.superclass.constructor.call(this, this.el.dom, this.ddGroup || this.group,
+ {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true});
+
+ this.dragging = false;
+};
+
+YAHOO.extendX(YAHOO.ext.dd.DragSource, YAHOO.util.DDProxy, {
+ dropAllowed : 'ydd-drop-ok',
+ dropNotAllowed : 'ydd-drop-nodrop',
+
+ getDragData : function(e){
+ return this.dragData;
+ },
+
+ onDragEnter : function(e, id){
+ var target = YAHOO.util.DragDropMgr.getDDById(id);
+ this.cachedTarget = target;
+ if(this.beforeDragEnter(target, e, id) !== false){
+ if(target.isNotifyTarget){
+ var status = target.notifyEnter(this, e, this.dragData);
+ this.proxy.setStatus(status);
+ }else{
+ this.proxy.setStatus(this.dropAllowed);
+ }
+
+ if(this.afterDragEnter){
+ this.afterDragEnter(target, e, id);
+ }
+ }
+ },
+
+ beforeDragEnter : function(target, e, id){
+ return true;
+ },
+
+ alignElWithMouse: function() {
+ YAHOO.ext.dd.DragSource.superclass.alignElWithMouse.apply(this, arguments);
+ this.proxy.sync();
+ },
+
+ onDragOver : function(e, id){
+ var target = this.cachedTarget || YAHOO.util.DragDropMgr.getDDById(id);
+ if(this.beforeDragOver(target, e, id) !== false){
+ if(target.isNotifyTarget){
+ var status = target.notifyOver(this, e, this.dragData);
+ this.proxy.setStatus(status);
+ }
+
+ if(this.afterDragOver){
+ this.afterDragOver(target, e, id);
+ }
+ }
+ },
+
+ beforeDragOver : function(target, e, id){
+ return true;
+ },
+
+ onDragOut : function(e, id){
+ var target = this.cachedTarget || YAHOO.util.DragDropMgr.getDDById(id);
+ if(this.beforeDragOut(target, e, id) !== false){
+ if(target.isNotifyTarget){
+ target.notifyOut(this, e, this.dragData);
+ }
+ this.proxy.reset();
+ if(this.afterDragOut){
+ this.afterDragOut(target, e, id);
+ }
+ }
+ this.cachedTarget = null;
+ },
+
+ beforeDragOut : function(target, e, id){
+ return true;
+ },
+
+
+ onDragDrop : function(e, id){
+ var target = this.cachedTarget || YAHOO.util.DragDropMgr.getDDById(id);
+ if(this.beforeDragDrop(target, e, id) !== false){
+ if(target.isNotifyTarget){
+ if(target.notifyDrop(this, e, this.dragData)){ // valid drop?
+ this.onValidDrop(target, e, id);
+ }else{
+ this.onInvalidDrop(target, e, id);
+ }
+ }else{
+ this.onValidDrop(target, e, id);
+ }
+
+ if(this.afterDragDrop){
+ this.afterDragDrop(target, e, id);
+ }
+ }
+ },
+
+ beforeDragDrop : function(target, e, id){
+ return true;
+ },
+
+ onValidDrop : function(target, e, id){
+ this.hideProxy();
+ },
+
+ getRepairXY : function(e, data){
+ return this.el.getXY();
+ },
+
+ onInvalidDrop : function(target, e, id){
+ this.beforeInvalidDrop(target, e, id);
+ if(this.cachedTarget){
+ if(this.cachedTarget.isNotifyTarget){
+ this.cachedTarget.notifyOut(this, e, this.dragData);
+ }
+ this.cacheTarget = null;
+ }
+ this.proxy.repair(this.getRepairXY(e, this.dragData), this.afterRepair, this);
+ if(this.afterInvalidDrop){
+ this.afterInvalidDrop(e, id);
+ }
+ },
+
+ afterRepair : function(){
+ this.el.highlight(this.hlColor || 'c3daf9');
+ this.dragging = false;
+ },
+
+ beforeInvalidDrop : function(target, e, id){
+ return true;
+ },
+
+ handleMouseDown : function(e){
+ if(this.dragging) {
+ return;
+ }
+ if(YAHOO.ext.QuickTips){
+ YAHOO.ext.QuickTips.disable();
+ }
+ var data = this.getDragData(e);
+ if(data && this.onBeforeDrag(data, e) !== false){
+ this.dragData = data;
+ this.proxy.stop();
+ YAHOO.ext.dd.DragSource.superclass.handleMouseDown.apply(this, arguments);
+ }
+ },
+
+ handleMouseUp : function(e){
+ if(YAHOO.ext.QuickTips){
+ YAHOO.ext.QuickTips.enable();
+ }
+ },
+
+ onBeforeDrag : function(data, e){
+ return true;
+ },
+
+ startDrag : function(e){
+ this.proxy.reset();
+ this.dragging = true;
+ this.proxy.update('');
+ this.onInitDrag(e);
+ this.proxy.show();
+ },
+
+ onInitDrag : function(e){
+ var clone = this.el.dom.cloneNode(true);
+ clone.id = YAHOO.util.Dom.generateId(); // prevent duplicate ids
+ this.proxy.update(clone);
+ return true;
+ },
+
+
+ getProxy : function(){
+ return this.proxy;
+ },
+
+ hideProxy : function(){
+ this.proxy.hide();
+ this.proxy.reset(true);
+ this.dragging = false;
+ },
+
+ triggerCacheRefresh : function(){
+ YAHOO.util.DDM.refreshCache(this.groups);
+ },
+
+ // override to prevent hiding
+ b4EndDrag: function(e) {
+ },
+
+ // override to prevent moving
+ endDrag : function(e){
+ this.onEndDrag(this.dragData, e);
+ },
+
+ onEndDrag : function(data, e){
+
+ },
+
+ // pin to cursor
+ autoOffset : function(x, y) {
+ this.setDelta(-12, -20);
+ }
+});
+}
diff --git a/frontend/beta/js/YUI-extensions/dd/DragZone.js b/frontend/beta/js/YUI-extensions/dd/DragZone.js
new file mode 100644
index 0000000..7a6edb6
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/dd/DragZone.js
@@ -0,0 +1,64 @@
+// kill drag drop dependency
+if(YAHOO.util.DragDrop){
+/**
+ * @class YAHOO.ext.dd.DragZone
+ * @extends YAHOO.ext.dd.Source
+ * This class provides a container DD instance that proxies for multiple child node sources.
+ * var schema = {
+ * tagName: 'Item',
+ * id: 'ASIN',
+ * fields: ['Author', 'Title', 'Manufacturer', 'ProductGroup']
+ * };
+ *
+ * By default, this class requires that draggable child nodes are registered with
+ * {@link YAHOO.ext.dd.Registry}.
+ * @cfg {Boolean} containerScroll True to register this container with the Scrollmanager
+ * for auto scrolling during drag operations.
+ * @constructor
+ * @param {String/HTMLElement/Element} el The container element
+ * @param {Object} config
+ */
+YAHOO.ext.dd.DragZone = function(el, config){
+ YAHOO.ext.dd.DragZone.superclass.constructor.call(this, el, config);
+ if(this.containerScroll){
+ YAHOO.ext.dd.ScrollManager.register(this.el);
+ }
+};
+
+YAHOO.extendX(YAHOO.ext.dd.DragZone, YAHOO.ext.dd.DragSource, {
+ /**
+ * Called when a mousedown occurs in this container. Looks in {@link YAHOO.ext.dd.Registry}
+ * for a valid target to drag based on the mouse down. Override this method
+ * to provide your own lookup logic (e.g. finding a child by class name). Make sure your returned
+ * object has a "ddel" attribute (with an HTML Element) for other functions to work.
+ * @param {EventObject} e The mouse down event
+ * @return {Object} The dragData
+ */
+ getDragData : function(e){
+ return YAHOO.ext.dd.Registry.getHandleFromEvent(e);
+ },
+
+ /**
+ * Called once drag threshold has been reached to initialize the proxy element. By default, it clones the
+ * this.dragData.ddel
+ * @param {EventObject} e The current event
+ * @return {Boolean} true to continue the drag, false to cancel
+ */
+ onInitDrag : function(e){
+ this.proxy.update(this.dragData.ddel.cloneNode(true));
+ return true;
+ },
+
+ /**
+ * Called after a repair of an invalid drop. By default, highlights this.dragData.ddel
+ */
+ afterRepair : function(){
+ YAHOO.ext.Element.fly(this.dragData.ddel).highlight(this.hlColor || 'c3daf9');
+ this.dragging = false;
+ },
+
+ /**
+ * Called before a repair of an invalid drop to get the XY to animate to. By default returns
+ * the XY of this.dragData.ddel
+ * @param {EventObject} e The mouse up event
+ * @return {Array} The xy location (e.g. [100, 200])
+ */
+ getRepairXY : function(e){
+ return YAHOO.ext.Element.fly(this.dragData.ddel).getXY();
+ }
+});
+}
diff --git a/frontend/beta/js/YUI-extensions/dd/DropTarget.js b/frontend/beta/js/YUI-extensions/dd/DropTarget.js
new file mode 100644
index 0000000..30e59cd
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/dd/DropTarget.js
@@ -0,0 +1,45 @@
+// kill drag drop dependency
+if(YAHOO.util.DragDrop){
+
+YAHOO.ext.dd.DropTarget = function(el, config){
+ this.el = getEl(el);
+
+ YAHOO.ext.util.Config.apply(this, config);
+
+ if(this.containerScroll){
+ YAHOO.ext.dd.ScrollManager.register(this.el);
+ }
+
+ YAHOO.ext.dd.DropTarget.superclass.constructor.call(this, this.el.dom, this.ddGroup || this.group,
+ {isTarget: true});
+
+};
+
+YAHOO.extendX(YAHOO.ext.dd.DropTarget, YAHOO.util.DDTarget, {
+ isTarget : true,
+ isNotifyTarget : true,
+ dropAllowed : 'ydd-drop-ok',
+ dropNotAllowed : 'ydd-drop-nodrop',
+
+ notifyEnter : function(dd, e, data){
+ if(this.overClass){
+ this.el.addClass(this.overClass);
+ }
+ return this.dropAllowed;
+ },
+
+ notifyOver : function(dd, e, data){
+ return this.dropAllowed;
+ },
+
+ notifyOut : function(dd, e, data){
+ if(this.overClass){
+ this.el.removeClass(this.overClass);
+ }
+ },
+
+ notifyDrop : function(dd, e, data){
+ return false;
+ }
+});
+}
diff --git a/frontend/beta/js/YUI-extensions/dd/DropZone.js b/frontend/beta/js/YUI-extensions/dd/DropZone.js
new file mode 100644
index 0000000..ce446fb
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/dd/DropZone.js
@@ -0,0 +1,81 @@
+// kill drag drop dependency
+if(YAHOO.util.DragDrop){
+YAHOO.ext.dd.DropZone = function(el, config){
+ YAHOO.ext.dd.DropZone.superclass.constructor.call(this, el, config);
+};
+
+YAHOO.extendX(YAHOO.ext.dd.DropZone, YAHOO.ext.dd.DropTarget, {
+ getTargetFromEvent : function(e){
+ return YAHOO.ext.dd.Registry.getTargetFromEvent(e);
+ },
+
+ onNodeEnter : function(n, dd, e, data){
+
+ },
+
+ onNodeOver : function(n, dd, e, data){
+ return this.dropAllowed;
+ },
+
+ onNodeOut : function(n, dd, e, data){
+
+ },
+
+ onNodeDrop : function(n, dd, e, data){
+ return false;
+ },
+
+ onContainerOver : function(n, dd, e, data){
+ return this.dropNotAllowed;
+ },
+
+ onContainerDrop : function(n, dd, e, data){
+ return false;
+ },
+
+ notifyEnter : function(dd, e, data){
+ return this.dropNotAllowed;
+ },
+
+ notifyOver : function(dd, e, data){
+ var n = this.getTargetFromEvent(e);
+ if(!n){ // not over valid drop target
+ if(this.lastOverNode){
+ this.onNodeOut(this.lastOverNode, dd, e, data);
+ this.lastOverNode = null;
+ }
+ return this.onContainerOver(dd, e, data);
+ }
+ if(this.lastOverNode != n){
+ if(this.lastOverNode){
+ this.onNodeOut(this.lastOverNode, dd, e, data);
+ }
+ this.onNodeEnter(n, dd, e, data);
+ this.lastOverNode = n;
+ }
+ return this.onNodeOver(n, dd, e, data);
+ },
+
+ notifyOut : function(dd, e, data){
+ if(this.lastOverNode){
+ this.onNodeOut(this.lastOverNode, dd, e, data);
+ this.lastOverNode = null;
+ }
+ },
+
+ notifyDrop : function(dd, e, data){
+ if(this.lastOverNode){
+ this.onNodeOut(this.lastOverNode, dd, e, data);
+ this.lastOverNode = null;
+ }
+ var n = this.getTargetFromEvent(e);
+ return n ?
+ this.onNodeDrop(n, dd, e, data) :
+ this.onContainerDrop(n, dd, e, data);
+ },
+
+ triggerCacheRefresh : function(){
+ YAHOO.util.DDM.refreshCache(this.groups);
+ }
+});
+}
diff --git a/frontend/beta/js/YUI-extensions/dd/Registry.js b/frontend/beta/js/YUI-extensions/dd/Registry.js
new file mode 100644
index 0000000..983b874
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/dd/Registry.js
@@ -0,0 +1,80 @@
+// kill drag drop dependency
+if(YAHOO.util.DragDrop){
+
+YAHOO.ext.dd.Registry = function(){
+ var elements = {};
+ var handles = {};
+ var autoIdSeed = 0;
+ var doc = document; // local reference for IE
+
+ var getId = function(el, autogen){
+ if(typeof el == 'string'){
+ return el;
+ }
+ var id = el.id;
+ if(!id && autogen !== false){
+ id = 'yddgen-' + (++autoIdSeed);
+ el.id = id;
+ }
+ return id;
+ };
+
+ return {
+ register : function(el, data){
+ data = data || {};
+ if(typeof el == 'string'){
+ el = doc.getElementById(el);
+ }
+ data.ddel = el;
+ elements[getId(el)] = data;
+ if(data.isHandle !== false){
+ handles[data.ddel.id] = data;
+ }
+ if(data.handles){
+ var hs = data.handles;
+ for(var i = 0, len = hs.length; i < len; i++){
+ handles[getId(hs[i])] = data;
+ }
+ }
+ },
+
+ unregister : function(el){
+ var id = getId(el, false);
+ var data = elements[id];
+ if(data){
+ delete elements[id];
+ if(data.handles){
+ var hs = data.handles;
+ for(var i = 0, len = hs.length; i < len; i++){
+ delete handles[getId(hs[i], false)];
+ }
+ }
+ }
+ },
+
+ getHandle : function(id){
+ if(typeof id != 'string'){ // must be element?
+ id = id.id;
+ }
+ return handles[id];
+ },
+
+ getHandleFromEvent : function(e){
+ var t = YAHOO.util.Event.getTarget(e);
+ return t ? handles[t.id] : null;
+ },
+
+ getTarget : function(id){
+ if(typeof id != 'string'){ // must be element?
+ id = id.id;
+ }
+ return elements[id];
+ },
+
+ getTargetFromEvent : function(e){
+ var t = YAHOO.util.Event.getTarget(e);
+ return t ? elements[t.id] || handles[t.id] : null;
+ }
+ };
+}();
+}
diff --git a/frontend/beta/js/YUI-extensions/dd/ScrollManager.js b/frontend/beta/js/YUI-extensions/dd/ScrollManager.js
new file mode 100644
index 0000000..615aadf
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/dd/ScrollManager.js
@@ -0,0 +1,171 @@
+// kill dependency issue
+if(YAHOO.util.DragDrop){
+/**
+ * @class YAHOO.ext.dd.ScrollManager
+ * Provides automatic scrolling of overflow regions in the page during drag operations.
+ * Note: This class uses "Point Mode" and is untested in "Intersect Mode".
+ * @singleton
+ */
+YAHOO.ext.dd.ScrollManager = function(){
+ var ddm = YAHOO.util.DragDropMgr;
+ var els = {};
+ var dragEl = null;
+ var proc = {};
+
+ var onStop = function(e){
+ dragEl = null;
+ clearProc();
+ };
+
+ var triggerRefresh = function(){
+ if(ddm.dragCurrent){
+ ddm.refreshCache(ddm.dragCurrent.groups);
+ }
+ }
+
+ var doScroll = function(){
+ if(ddm.dragCurrent){
+ var dds = YAHOO.ext.dd.ScrollManager;
+ if(!dds.animate || !YAHOO.util.Scroll){
+ if(proc.el.scroll(proc.dir, dds.increment)){
+ triggerRefresh();
+ }
+ }else{
+ proc.el.scroll(proc.dir, dds.increment, true, dds.animDuration, triggerRefresh);
+ }
+ }
+ };
+
+ var clearProc = function(){
+ if(proc.id){
+ clearInterval(proc.id);
+ }
+ proc.id = 0;
+ proc.el = null;
+ proc.dir = '';
+ };
+
+ var startProc = function(el, dir){
+ clearProc();
+ proc.el = el;
+ proc.dir = dir;
+ proc.id = setInterval(doScroll, YAHOO.ext.dd.ScrollManager.frequency);
+ };
+
+ var onFire = function(e, isDrop){
+ if(isDrop || !ddm.dragCurrent){ return; }
+ var dds = YAHOO.ext.dd.ScrollManager;
+ if(!dragEl || dragEl != ddm.dragCurrent){
+ dragEl = ddm.dragCurrent;
+ // refresh regions on drag start
+ dds.refreshCache();
+ }
+
+ var xy = YAHOO.util.Event.getXY(e);
+ var pt = new YAHOO.util.Point(xy[0], xy[1]);
+ for(var id in els){
+ var el = els[id], r = el._region;
+ if(r.contains(pt) && el.isScrollable()){
+ if(r.bottom - pt.y <= dds.thresh){
+ if(proc.el != el){
+ startProc(el, 'down');
+ }
+ return;
+ }else if(r.right - pt.x <= dds.thresh){
+ if(proc.el != el){
+ startProc(el, 'left');
+ }
+ return;
+ }else if(pt.y - r.top <= dds.thresh){
+ if(proc.el != el){
+ startProc(el, 'up');
+ }
+ return;
+ }else if(pt.x - r.left <= dds.thresh){
+ if(proc.el != el){
+ startProc(el, 'right');
+ }
+ return;
+ }
+ }
+ }
+ clearProc();
+ };
+
+ ddm.fireEvents = ddm.fireEvents.createSequence(onFire, ddm);
+ ddm.stopDrag = ddm.stopDrag.createSequence(onStop, ddm);
+
+ return {
+ /**
+ * Registers new overflow element(s) to auto scroll
+ * @param {String/HTMLElement/Element/Array} el The id of or the element to be scrolled or an array of either
+ */
+ register : function(el){
+ if(el instanceof Array){
+ for(var i = 0, len = el.length; i < len; i++) {
+ this.register(el[i]);
+ }
+ }else{
+ el = getEl(el);
+ els[el.id] = el;
+ }
+ },
+
+ /**
+ * Unregisters overflow element(s) so they are no longer scrolled
+ * @param {String/HTMLElement/Element/Array} el The id of or the element to be removed or an array of either
+ */
+ unregister : function(el){
+ if(el instanceof Array){
+ for(var i = 0, len = el.length; i < len; i++) {
+ this.unregister(el[i]);
+ }
+ }else{
+ el = getEl(el);
+ delete els[el.id];
+ }
+ },
+
+ /**
+ * The number of pixels from the edge of a container the pointer needs to be to
+ * trigger scrolling (defaults to 25)
+ * @type Number
+ */
+ thresh : 25,
+
+ /**
+ * The number of pixels to scroll in each scroll increment (defaults to 50)
+ * @type Number
+ */
+ increment : 100,
+
+ /**
+ * The frequency of scrolls in milliseconds (defaults to 500)
+ * @type Number
+ */
+ frequency : 500,
+
+ /**
+ * True to animate the scroll (defaults to true)
+ * @type Boolean
+ */
+ animate: true,
+
+ /**
+ * The animation duration in seconds -
+ * MUST BE less than YAHOO.ext.dd.ScrollManager.frequency! (defaults to .4)
+ * @type Number
+ */
+ animDuration: .4,
+
+ /**
+ * Manually trigger a cache refresh.
+ */
+ refreshCache : function(){
+ for(var id in els){
+ els[id]._region = els[id].getRegion();
+ }
+ }
+ }
+}();
+}
diff --git a/frontend/beta/js/YUI-extensions/dd/StatusProxy.js b/frontend/beta/js/YUI-extensions/dd/StatusProxy.js
new file mode 100644
index 0000000..97de4d9
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/dd/StatusProxy.js
@@ -0,0 +1,110 @@
+YAHOO.ext.dd.StatusProxy = function(config){
+ YAHOO.ext.util.Config.apply(this, config);
+ this.id = this.id || YAHOO.util.Dom.generateId();
+ this.el = new YAHOO.ext.Layer({
+ dh: {
+ id: this.id, tag: 'div', cls: 'ydd-drag-proxy '+this.dropNotAllowed, children: [
+ {tag: 'div', cls: 'ydd-drop-icon'},
+ {tag: 'div', cls: 'ydd-drag-ghost'}
+ ]
+ },
+ shadow: !config || config.shadow !== false
+ });
+ /*this.el = YAHOO.ext.DomHelper.insertBefore(document.body.firstChild, {
+ id: this.id, tag: 'div', cls: 'ydd-drag-proxy '+this.dropNotAllowed, children: [
+ {tag: 'div', cls: 'ydd-drop-icon'},
+ {tag: 'div', cls: 'ydd-drag-ghost'}
+ ]
+ }, true);*/
+ this.ghost = getEl(this.el.dom.childNodes[1]);
+ this.dropStatus = this.dropNotAllowed;
+};
+
+YAHOO.ext.dd.StatusProxy.prototype = {
+ dropAllowed : 'ydd-drop-ok',
+ dropNotAllowed : 'ydd-drop-nodrop',
+ /**
+ * Updates the DD visual element to allow/not allow a drop
+ * @param {String} cssClass The css class for the new drop status indicator image
+ */
+ setStatus : function(cssClass){
+ cssClass = cssClass || this.dropNotAllowed;
+ if(this.dropStatus != cssClass){
+ this.el.replaceClass(this.dropStatus, cssClass);
+ this.dropStatus = cssClass;
+ }
+ },
+
+ reset : function(clearGhost){
+ this.el.dom.className = 'ydd-drag-proxy ' + this.dropNotAllowed;
+ this.dropStatus = this.dropNotAllowed;
+ if(clearGhost){
+ this.ghost.update('');
+ }
+ },
+
+ update : function(html){
+ if(typeof html == 'string'){
+ this.ghost.update(html);
+ }else{
+ this.ghost.update('');
+ html.style.margin = '0';
+ this.ghost.dom.appendChild(html);
+ }
+ },
+
+ getEl : function(){
+ return this.el;
+ },
+
+ getGhost : function(){
+ return this.ghost;
+ },
+
+ hide : function(clear){
+ this.el.hide();
+ if(clear){
+ this.reset(true);
+ }
+ },
+
+ stop : function(){
+ if(this.anim && this.anim.isAnimated()){
+ this.anim.stop();
+ }
+ },
+
+ show : function(){
+ this.el.show();
+ },
+
+ sync : function(){
+ this.el.syncLocalXY();
+ },
+
+ repair : function(xy, callback, scope){
+ this.callback = callback;
+ this.scope = scope;
+ if(xy && this.animRepair !== false && YAHOO.util.Anim){
+ this.el.addClass('ydd-drag-repair');
+ this.el.hideUnders(true);
+ if(!this.anim){
+ this.anim = new YAHOO.util.Motion(this.el.dom, {}, this.repairDuration || .5, YAHOO.util.Easing.easeOut);
+ this.anim.onComplete.subscribe(this.afterRepair, this, true);
+ }
+ this.anim.attributes = {points: {to:xy}};
+ this.anim.animate();
+ }else{
+ this.afterRepair();
+ }
+ },
+
+ afterRepair : function(){
+ this.hide(true);
+ if(typeof this.callback == 'function'){
+ this.callback.call(this.scope || this);
+ }
+ this.callback == null;
+ this.scope == null;
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/grid/AbstractColumnModel.js b/frontend/beta/js/YUI-extensions/grid/AbstractColumnModel.js
new file mode 100644
index 0000000..1f93590
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/AbstractColumnModel.js
@@ -0,0 +1,131 @@
+/**
+ * @class YAHOO.ext.grid.AbstractColumnModel
+ * @extends YAHOO.ext.util.Observable
+ * This abstract class defines the ColumnModel interface and provides default implementations of the events required by the Grid.
+ * @constructor
+*/
+YAHOO.ext.grid.AbstractColumnModel = function(){
+ // legacy events
+ this.onWidthChange = new YAHOO.util.CustomEvent('widthChanged');
+ this.onHeaderChange = new YAHOO.util.CustomEvent('headerChanged');
+ this.onHiddenChange = new YAHOO.util.CustomEvent('hiddenChanged');
+
+ this.events = {
+ /**
+ * @event widthchange
+ * Fires when the width of a column changes
+ * @param {ColumnModel} this
+ * @param {Number} columnIndex The column index
+ * @param {Number} newWidth The new width
+ */
+ 'widthchange': this.onWidthChange,
+ /**
+ * @event headerchange
+ * Fires when the text of a header changes
+ * @param {ColumnModel} this
+ * @param {Number} columnIndex The column index
+ * @param {Number} newText The new header text
+ */
+ 'headerchange': this.onHeaderChange,
+ /**
+ * @event hiddenchange
+ * Fires when a column is hidden or "unhidden"
+ * @param {ColumnModel} this
+ * @param {Number} columnIndex The column index
+ * @param {Number} hidden true if hidden, false otherwise
+ */
+ 'hiddenchange': this.onHiddenChange
+ };
+};
+
+YAHOO.ext.grid.AbstractColumnModel.prototype = {
+ fireEvent : YAHOO.ext.util.Observable.prototype.fireEvent,
+ on : YAHOO.ext.util.Observable.prototype.on,
+ addListener : YAHOO.ext.util.Observable.prototype.addListener,
+ delayedListener : YAHOO.ext.util.Observable.prototype.delayedListener,
+ removeListener : YAHOO.ext.util.Observable.prototype.removeListener,
+ purgeListeners : YAHOO.ext.util.Observable.prototype.purgeListeners,
+ bufferedListener : YAHOO.ext.util.Observable.prototype.bufferedListener,
+
+ fireWidthChange : function(colIndex, newWidth){
+ this.onWidthChange.fireDirect(this, colIndex, newWidth);
+ },
+
+ fireHeaderChange : function(colIndex, newHeader){
+ this.onHeaderChange.fireDirect(this, colIndex, newHeader);
+ },
+
+ fireHiddenChange : function(colIndex, hidden){
+ this.onHiddenChange.fireDirect(this, colIndex, hidden);
+ },
+
+ /**
+ * Interface method - Returns the number of columns.
+ * @return {Number}
+ */
+ getColumnCount : function(){
+ return 0;
+ },
+
+ /**
+ * Interface method - Returns true if the specified column is sortable.
+ * @param {Number} col The column index
+ * @return {Boolean}
+ */
+ isSortable : function(col){
+ return false;
+ },
+
+ /**
+ * Interface method - Returns true if the specified column is hidden.
+ * @param {Number} col The column index
+ * @return {Boolean}
+ */
+ isHidden : function(col){
+ return false;
+ },
+
+ /**
+ * Interface method - Returns the sorting comparison function defined for the column (defaults to sortTypes.none).
+ * @param {Number} col The column index
+ * @return {Function}
+ */
+ getSortType : function(col){
+ return YAHOO.ext.grid.DefaultColumnModel.sortTypes.none;
+ },
+
+ /**
+ * Interface method - Returns the rendering (formatting) function defined for the column.
+ * @param {Number} col The column index
+ * @return {Function}
+ */
+ getRenderer : function(col){
+ return YAHOO.ext.grid.DefaultColumnModel.defaultRenderer;
+ },
+
+ /**
+ * Interface method - Returns the width for the specified column.
+ * @param {Number} col The column index
+ * @return {Number}
+ */
+ getColumnWidth : function(col){
+ return 0;
+ },
+
+ /**
+ * Interface method - Returns the total width of all columns.
+ * @return {Number}
+ */
+ getTotalWidth : function(){
+ return 0;
+ },
+
+ /**
+ * Interface method - Returns the header for the specified column.
+ * @param {Number} col The column index
+ * @return {String}
+ */
+ getColumnHeader : function(col){
+ return '';
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/grid/DefaultColumnModel.js b/frontend/beta/js/YUI-extensions/grid/DefaultColumnModel.js
new file mode 100644
index 0000000..fbdba26
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/DefaultColumnModel.js
@@ -0,0 +1,325 @@
+/**
+ * @class YAHOO.ext.grid.DefaultColumnModel
+ * @extends YAHOO.ext.grid.AbstractColumnModel
+ * This is the default implementation of a ColumnModel used by the Grid. It defines
+ * the columns in the grid.
+ *
Usage:
+
+ * @constructor
+ * @param {Object} config The config object
+*/
+YAHOO.ext.grid.DefaultColumnModel = function(config){
+ YAHOO.ext.grid.DefaultColumnModel.superclass.constructor.call(this);
+ /**
+ * The config passed into the constructor
+ */
+ this.config = config;
+
+ /**
+ * The width of columns which have no width specified (defaults to 100)
+ * @type Number
+ */
+ this.defaultWidth = 100;
+ /**
+ * Default sortable of columns which have no sortable specified (defaults to false)
+ * @type Boolean
+ */
+ this.defaultSortable = false;
+};
+YAHOO.extendX(YAHOO.ext.grid.DefaultColumnModel, YAHOO.ext.grid.AbstractColumnModel, {
+
+ /**
+ * Returns the number of columns.
+ * @return {Number}
+ */
+ getColumnCount : function(){
+ return this.config.length;
+ },
+
+ /**
+ * Returns true if the specified column is sortable.
+ * @param {Number} col The column index
+ * @return {Boolean}
+ */
+ isSortable : function(col){
+ if(typeof this.config[col].sortable == 'undefined'){
+ return this.defaultSortable;
+ }
+ return this.config[col].sortable;
+ },
+
+ /**
+ * Returns the sorting comparison function defined for the column (defaults to sortTypes.none).
+ * @param {Number} col The column index
+ * @return {Function}
+ */
+ getSortType : function(col){
+ if(!this.dataMap){
+ // build a lookup so we don't search every time
+ var map = [];
+ for(var i = 0, len = this.config.length; i < len; i++){
+ map[this.getDataIndex(i)] = i;
+ }
+ this.dataMap = map;
+ }
+ col = this.dataMap[col];
+ if(!this.config[col].sortType){
+ return YAHOO.ext.grid.DefaultColumnModel.sortTypes.none;
+ }
+ return this.config[col].sortType;
+ },
+
+ /**
+ * Sets the sorting comparison function for a column.
+ * @param {Number} col The column index
+ * @param {Function} fn
+ */
+ setSortType : function(col, fn){
+ this.config[col].sortType = fn;
+ },
+
+
+ /**
+ * Returns the rendering (formatting) function defined for the column.
+ * @param {Number} col The column index
+ * @return {Function}
+ */
+ getRenderer : function(col){
+ if(!this.config[col].renderer){
+ return YAHOO.ext.grid.DefaultColumnModel.defaultRenderer;
+ }
+ return this.config[col].renderer;
+ },
+
+ /**
+ * Sets the rendering (formatting) function for a column.
+ * @param {Number} col The column index
+ * @param {Function} fn
+ */
+ setRenderer : function(col, fn){
+ this.config[col].renderer = fn;
+ },
+
+ /**
+ * Returns the width for the specified column.
+ * @param {Number} col The column index
+ * @return {Number}
+ */
+ getColumnWidth : function(col){
+ return this.config[col].width || this.defaultWidth;
+ },
+
+ /**
+ * Sets the width for a column.
+ * @param {Number} col The column index
+ * @param {Number} width The new width
+ */
+ setColumnWidth : function(col, width, suppressEvent){
+ this.config[col].width = width;
+ this.totalWidth = null;
+ if(!suppressEvent){
+ this.onWidthChange.fireDirect(this, col, width);
+ }
+ },
+
+ /**
+ * Returns the total width of all columns.
+ * @param {Boolean} includeHidden True to include hidden column widths
+ * @return {Number}
+ */
+ getTotalWidth : function(includeHidden){
+ if(!this.totalWidth){
+ this.totalWidth = 0;
+ for(var i = 0; i < this.config.length; i++){
+ if(includeHidden || !this.isHidden(i)){
+ this.totalWidth += this.getColumnWidth(i);
+ }
+ }
+ }
+ return this.totalWidth;
+ },
+
+ /**
+ * Returns the header for the specified column.
+ * @param {Number} col The column index
+ * @return {String}
+ */
+ getColumnHeader : function(col){
+ return this.config[col].header;
+ },
+
+ /**
+ * Sets the header for a column.
+ * @param {Number} col The column index
+ * @param {String} header The new header
+ */
+ setColumnHeader : function(col, header){
+ this.config[col].header = header;
+ this.onHeaderChange.fireDirect(this, col, header);
+ },
+
+ /**
+ * Returns the tooltip for the specified column.
+ * @param {Number} col The column index
+ * @return {String}
+ */
+ getColumnTooltip : function(col){
+ return this.config[col].tooltip;
+ },
+ /**
+ * Sets the tooltip for a column.
+ * @param {Number} col The column index
+ * @param {String} tooltip The new tooltip
+ */
+ setColumnTooltip : function(col, header){
+ this.config[col].tooltip = tooltip;
+ },
+
+ /**
+ * Returns the dataIndex for the specified column.
+ * @param {Number} col The column index
+ * @return {Number}
+ */
+ getDataIndex : function(col){
+ if(typeof this.config[col].dataIndex != 'number'){
+ return col;
+ }
+ return this.config[col].dataIndex;
+ },
+
+ /**
+ * Sets the dataIndex for a column.
+ * @param {Number} col The column index
+ * @param {Number} dataIndex The new dataIndex
+ */
+ setDataIndex : function(col, dataIndex){
+ this.config[col].dataIndex = dataIndex;
+ },
+ /**
+ * Returns true if the cell is editable.
+ * @param {Number} colIndex The column index
+ * @param {Number} rowIndex The row index
+ * @return {Boolean}
+ */
+ isCellEditable : function(colIndex, rowIndex){
+ return this.config[colIndex].editable || (typeof this.config[colIndex].editable == 'undefined' && this.config[colIndex].editor);
+ },
+
+ /**
+ * Returns the editor defined for the cell/column.
+ * @param {Number} colIndex The column index
+ * @param {Number} rowIndex The row index
+ * @return {Object}
+ */
+ getCellEditor : function(colIndex, rowIndex){
+ return this.config[colIndex].editor;
+ },
+
+ /**
+ * Sets if a column is editable.
+ * @param {Number} col The column index
+ * @param {Boolean} editable True if the column is editable
+ */
+ setEditable : function(col, editable){
+ this.config[col].editable = editable;
+ },
+
+
+ /**
+ * Returns true if the column is hidden.
+ * @param {Number} colIndex The column index
+ * @return {Boolean}
+ */
+ isHidden : function(colIndex){
+ return this.config[colIndex].hidden;
+ },
+
+
+ /**
+ * Returns true if the column width cannot be changed
+ */
+ isFixed : function(colIndex){
+ return this.config[colIndex].fixed;
+ },
+
+ /**
+ * Returns true if the column cannot be resized
+ * @return {Boolean}
+ */
+ isResizable : function(colIndex){
+ return this.config[colIndex].resizable !== false;
+ },
+ /**
+ * Sets if a column is hidden.
+ * @param {Number} colIndex The column index
+ */
+ setHidden : function(colIndex, hidden){
+ this.config[colIndex].hidden = hidden;
+ this.totalWidth = null;
+ this.fireHiddenChange(colIndex, hidden);
+ },
+
+ /**
+ * Sets the editor for a column.
+ * @param {Number} col The column index
+ * @param {Object} editor The editor object
+ */
+ setEditor : function(col, editor){
+ this.config[col].editor = editor;
+ }
+});
+
+/**
+ * Defines the default sorting (casting?) comparison functions used when sorting data:
+ *
+ var sort = YAHOO.ext.grid.DefaultColumnModel.sortTypes;
+ var myColumns = [
+ {header: "Ticker", width: 60, sortable: true, sortType: sort.asUCString},
+ {header: "Company Name", width: 150, sortable: true, sortType: sort.asUCString},
+ {header: "Market Cap.", width: 100, sortable: true, sortType: sort.asFloat},
+ {header: "$ Sales", width: 100, sortable: true, sortType: sort.asFloat, renderer: money},
+ {header: "Employees", width: 100, sortable: true, sortType: sort.asFloat}
+ ];
+ var colModel = new YAHOO.ext.grid.DefaultColumnModel(myColumns);
+
sortTypes.none - sorts data as it is without casting or parsing (the default)
+ *
sortTypes.asUCString - case insensitive string
+ *
sortTypes.asDate - attempts to parse data as a date
+ *
sortTypes.asFloat
+ *
sortTypes.asInt
+ * @static
+ */
+YAHOO.ext.grid.DefaultColumnModel.sortTypes = {
+ none : function(s) {
+ return s;
+ },
+
+ asUCString : function(s) {
+ return String(s).toUpperCase();
+ },
+
+ asDate : function(s) {
+ if(s instanceof Date){
+ return s.getTime();
+ }
+ return Date.parse(String(s));
+ },
+
+ asFloat : function(s) {
+ var val = parseFloat(String(s).replace(/,/g, ''));
+ if(isNaN(val)) val = 0;
+ return val;
+ },
+
+ asInt : function(s) {
+ var val = parseInt(String(s).replace(/,/g, ''));
+ if(isNaN(val)) val = 0;
+ return val;
+ }
+};
+
+YAHOO.ext.grid.DefaultColumnModel.defaultRenderer = function(value){
+ if(typeof value == 'string' && value.length < 1){
+ return ' ';
+ }
+ return value;
+}
diff --git a/frontend/beta/js/YUI-extensions/grid/EditorGrid.js b/frontend/beta/js/YUI-extensions/grid/EditorGrid.js
new file mode 100644
index 0000000..e7405a0
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/EditorGrid.js
@@ -0,0 +1,16 @@
+/**
+ * @class YAHOO.ext.grid.EditorGrid
+ * @extends YAHOO.ext.grid.Grid
+ * Shortcut class for creating and editable grid.
+ * @param {String/HTMLElement/YAHOO.ext.Element} container The element into which this grid will be rendered -
+ * The container MUST have some type of size defined for the grid to fill. The container will be
+ * automatically set to position relative if it isn't already.
+ * @param {Object} dataModel The data model to bind to
+ * @param {Object} colModel The column model with info about this grid's columns
+ */
+YAHOO.ext.grid.EditorGrid = function(container, dataModel, colModel){
+ YAHOO.ext.grid.EditorGrid.superclass.constructor.call(this, container, dataModel,
+ colModel, new YAHOO.ext.grid.EditorSelectionModel());
+ this.container.addClass('yeditgrid');
+};
+YAHOO.extendX(YAHOO.ext.grid.EditorGrid, YAHOO.ext.grid.Grid);
diff --git a/frontend/beta/js/YUI-extensions/grid/EditorSelectionModel.js b/frontend/beta/js/YUI-extensions/grid/EditorSelectionModel.js
new file mode 100644
index 0000000..c1cb240
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/EditorSelectionModel.js
@@ -0,0 +1,182 @@
+
+/**
+ @class YAHOO.ext.grid.EditorSelectionModel
+ * Extends {@link YAHOO.ext.grid.DefaultSelectionModel} to enable cell navigation.
+ @extends YAHOO.ext.grid.DefaultSelectionModel
+ @constructor
+ */
+YAHOO.ext.grid.EditorSelectionModel = function(){
+ YAHOO.ext.grid.EditorSelectionModel.superclass.constructor.call(this);
+ /** Number of clicks to activate a cell (for editing) - valid values are 1 or 2
+ * @type Number */
+ this.clicksToActivateCell = 1;
+ this.events['cellactivate'] = new YAHOO.util.CustomEvent('cellactivate');
+};
+
+YAHOO.extendX(YAHOO.ext.grid.EditorSelectionModel, YAHOO.ext.grid.DefaultSelectionModel);
+
+YAHOO.ext.grid.EditorSelectionModel.prototype.disableArrowNavigation = false;
+YAHOO.ext.grid.EditorSelectionModel.prototype.controlForArrowNavigation = false;
+
+/** @ignore */
+YAHOO.ext.grid.EditorSelectionModel.prototype.initEvents = function(){
+ this.grid.addListener("cellclick", this.onCellClick, this, true);
+ this.grid.addListener("celldblclick", this.onCellDblClick, this, true);
+ this.grid.addListener("keydown", this.keyDown, this, true);
+};
+
+YAHOO.ext.grid.EditorSelectionModel.prototype.onCellClick = function(grid, rowIndex, colIndex){
+ if(this.clicksToActivateCell == 1){
+ var row = this.grid.getRow(rowIndex);
+ var cell = row.childNodes[colIndex];
+ if(cell){
+ this.activate(row, cell);
+ }
+ }
+};
+
+YAHOO.ext.grid.EditorSelectionModel.prototype.activate = function(row, cell){
+ this.fireEvent('cellactivate', this, row, cell);
+ this.grid.doEdit(row, cell);
+};
+
+YAHOO.ext.grid.EditorSelectionModel.prototype.onCellDblClick = function(grid, rowIndex, colIndex){
+ if(this.clicksToActivateCell == 2){
+ var row = this.grid.getRow(rowIndex);
+ var cell = row.childNodes[colIndex];
+ if(cell){
+ this.activate(row, cell);
+ }
+ }
+};
+
+/** @ignore */
+YAHOO.ext.grid.EditorSelectionModel.prototype.setRowState = function(row, selected){
+ YAHOO.ext.grid.EditorSelectionModel.superclass.setRowState.call(this, row, false, false);
+};
+/** @ignore */
+YAHOO.ext.grid.EditorSelectionModel.prototype.focusRow = function(row, selected){
+};
+
+YAHOO.ext.grid.EditorSelectionModel.prototype.getEditorCellAfter = function(cell, spanRows){
+ var g = this.grid;
+ var next = g.getCellAfter(cell);
+ while(next && !g.colModel.isCellEditable(next.columnIndex)){
+ next = g.getCellAfter(next);
+ }
+ if(!next && spanRows){
+ var row = g.getRowAfter(g.getRowFromChild(cell));
+ if(row){
+ next = g.getFirstCell(row);
+ if(!g.colModel.isCellEditable(next.columnIndex)){
+ next = this.getEditorCellAfter(next);
+ }
+ }
+ }
+ return next;
+};
+
+YAHOO.ext.grid.EditorSelectionModel.prototype.getEditorCellBefore = function(cell, spanRows){
+ var g = this.grid;
+ var prev = g.getCellBefore(cell);
+ while(prev && !g.colModel.isCellEditable(prev.columnIndex)){
+ prev = g.getCellBefore(prev);
+ }
+ if(!prev && spanRows){
+ var row = g.getRowBefore(g.getRowFromChild(cell));
+ if(row){
+ prev = g.getLastCell(row);
+ if(!g.colModel.isCellEditable(prev.columnIndex)){
+ prev = this.getEditorCellBefore(prev);
+ }
+ }
+ }
+ return prev;
+};
+
+YAHOO.ext.grid.EditorSelectionModel.prototype.allowArrowNav = function(e){
+ return (!this.disableArrowNavigation && (!this.controlForArrowNavigation || e.ctrlKey));
+}
+/** @ignore */
+YAHOO.ext.grid.EditorSelectionModel.prototype.keyDown = function(e){
+ var g = this.grid, cm = g.colModel, cell = g.getEditingCell();
+ if(!cell) return;
+ var newCell;
+ switch(e.browserEvent.keyCode){
+ case e.TAB:
+ if(e.shiftKey){
+ newCell = this.getEditorCellBefore(cell, true);
+ }else{
+ newCell = this.getEditorCellAfter(cell, true);
+ }
+ e.preventDefault();
+ break;
+ case e.DOWN:
+ if(this.allowArrowNav(e)){
+ var next = g.getRowAfter(g.getRowFromChild(cell));
+ if(next){
+ newCell = next.childNodes[cell.columnIndex];
+ }
+ }
+ break;
+ case e.UP:
+ if(this.allowArrowNav(e)){
+ var prev = g.getRowBefore(g.getRowFromChild(cell));
+ if(prev){
+ newCell = prev.childNodes[cell.columnIndex];
+ }
+ }
+ break;
+ case e.RETURN:
+ if(e.shiftKey){
+ var prev = g.getRowBefore(g.getRowFromChild(cell));
+ if(prev){
+ newCell = prev.childNodes[cell.columnIndex];
+ }
+ }else{
+ var next = g.getRowAfter(g.getRowFromChild(cell));
+ if(next){
+ newCell = next.childNodes[cell.columnIndex];
+ }
+ }
+ break;
+ case e.RIGHT:
+ if(this.allowArrowNav(e)){
+ newCell = this.getEditorCellAfter(cell);
+ }
+ break;
+ case e.LEFT:
+ if(this.allowArrowNav(e)){
+ newCell = this.getEditorCellBefore(cell);
+ }
+ break;
+ };
+ if(newCell){
+ this.activate(g.getRowFromChild(newCell), newCell);
+ e.stopEvent();
+ }
+};
+
+/**
+ * @class YAHOO.ext.grid.EditorAndSelectionModel
+ */
+YAHOO.ext.grid.EditorAndSelectionModel = function(){
+ YAHOO.ext.grid.EditorAndSelectionModel.superclass.constructor.call(this);
+ this.events['cellactivate'] = new YAHOO.util.CustomEvent('cellactivate');
+};
+
+YAHOO.extendX(YAHOO.ext.grid.EditorAndSelectionModel, YAHOO.ext.grid.DefaultSelectionModel);
+
+YAHOO.ext.grid.EditorAndSelectionModel.prototype.initEvents = function(){
+ YAHOO.ext.grid.EditorAndSelectionModel.superclass.initEvents.call(this);
+ this.grid.addListener("celldblclick", this.onCellDblClick, this, true);
+};
+
+YAHOO.ext.grid.EditorAndSelectionModel.prototype.onCellDblClick = function(grid, rowIndex, colIndex){
+ var row = this.grid.getRow(rowIndex);
+ var cell = row.childNodes[colIndex];
+ if(cell){
+ this.fireEvent('cellactivate', this, row, cell);
+ this.grid.doEdit(row, cell);
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/grid/Grid.js b/frontend/beta/js/YUI-extensions/grid/Grid.js
new file mode 100644
index 0000000..46d5de4
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/Grid.js
@@ -0,0 +1,965 @@
+/**
+ * @class YAHOO.ext.grid.Grid
+ * @extends YAHOO.ext.util.Observable
+ * This class represents the primary interface of a component based grid control.
+ *
Usage:
+ * Common Problems:
+ var grid = new YAHOO.ext.grid.Grid('my-container-id', dataModel, columnModel);
+ // set any options
+ grid.render();
+ // or using a config
+ var grid = new YAHOO.ext.grid.Grid('my-container-id', {
+ dataModel: myDataModel,
+ colModel: myColModel,
+ selModel: mySelectionModel,
+ autoSizeColumns: true,
+ monitorWindowResize: false,
+ trackMouseOver: true
+ }).render();
+ *
+ * - Grid does not resize properly when going smaller: Setting overflow hidden on the container
+ * element will correct this
+ * - If you get el.style[camel]= NaNpx or -2px or something related, be certain you have given your container element
+ * dimensions. The grid adapts to your container's size, if your container has no size defined then the results
+ * are unpredictable.
+ * - Do not render the grid into an element with display:none. Try using visibility:hidden. Otherwise there is no way for the
+ * grid to calculate dimensions/offsets.
+ * @requires YAHOO.util.Dom
+ * @requires YAHOO.util.Event
+ * @requires YAHOO.util.CustomEvent
+ * @requires YAHOO.ext.Element
+ * @requires YAHOO.ext.util.Browser
+ * @requires YAHOO.ext.util.CSS
+ * @requires YAHOO.ext.SplitBar
+ * @requires YAHOO.ext.EventObject
+ * @constructor
+ * @param {String/HTMLElement/YAHOO.ext.Element} container The element into which this grid will be rendered -
+ * The container MUST have some type of size defined for the grid to fill. The container will be
+ * automatically set to position relative if it isn't already.
+ * @param {Object} config A config object that sets properties on this grid OR the data model to bind to
+ * @param {Object} colModel (optional) The column model with info about this grid's columns
+ * @param {Object} selectionModel (optional) The selection model for this grid (defaults to DefaultSelectionModel)
+ */
+YAHOO.ext.grid.Grid = function(container, config, colModel, selectionModel){
+ /** @private */
+ this.container = YAHOO.ext.Element.get(container);
+ this.container.update('');
+ this.container.setStyle('overflow', 'hidden');
+ this.id = this.container.id;
+ this.rows = [];
+ this.rowCount = 0;
+ this.fieldId = null;
+ var dataModel = config; // for legacy pre config support
+ this.dataModel = dataModel;
+ this.colModel = colModel;
+ this.selModel = selectionModel;
+ this.activeEditor = null;
+ this.editingCell = null;
+
+
+ if(typeof config == 'object' && !config.getRowCount){// must be config object
+ YAHOO.ext.util.Config.apply(this, config);
+ }
+
+ /** @private */
+ this.setValueDelegate = this.setCellValue.createDelegate(this);
+
+ /** @private */
+ this.events = {
+ // raw events
+ /**
+ * @event click
+ * The raw click event for the entire grid.
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'click' : true,
+ /**
+ * @event dblclick
+ * The raw dblclick event for the entire grid.
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'dblclick' : true,
+ /**
+ * @event mousedown
+ * The raw mousedown event for the entire grid.
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'mousedown' : true,
+ /**
+ * @event mouseup
+ * The raw mouseup event for the entire grid.
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'mouseup' : true,
+ /**
+ * @event mouseover
+ * The raw mouseover event for the entire grid.
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'mouseover' : true,
+ /**
+ * @event mouseout
+ * The raw mouseout event for the entire grid.
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'mouseout' : true,
+ /**
+ * @event keypress
+ * The raw keypress event for the entire grid.
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'keypress' : true,
+ /**
+ * @event keydown
+ * The raw keydown event for the entire grid.
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'keydown' : true,
+
+ // custom events
+
+ /**
+ * @event cellclick
+ * Fires when a cell is clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Number} columnIndex
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'cellclick' : true,
+ /**
+ * @event celldblclick
+ * Fires when a cell is double clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Number} columnIndex
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'celldblclick' : true,
+ /**
+ * @event rowclick
+ * Fires when a row is clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'rowclick' : true,
+ /**
+ * @event rowdblclick
+ * Fires when a row is double clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'rowdblclick' : true,
+ /**
+ * @event headerclick
+ * Fires when a header is clicked
+ * @param {Grid} this
+ * @param {Number} columnIndex
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'headerclick' : true,
+ /**
+ * @event rowcontextmenu
+ * Fires when a row is right clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'rowcontextmenu' : true,
+ /**
+ * @event cellcontextmenu
+ * Fires when a cell is right clicked
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Number} cellIndex
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'cellcontextmenu' : true,
+ /**
+ * @event headercontextmenu
+ * Fires when a header is right clicked
+ * @param {Grid} this
+ * @param {Number} columnIndex
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'headercontextmenu' : true,
+ /**
+ * @event beforeedit
+ * Fires before a cell is edited
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Number} columnIndex
+ */
+ 'beforeedit' : true,
+ /**
+ * @event afteredit
+ * Fires after a cell is edited
+ * @param {Grid} this
+ * @param {Number} rowIndex
+ * @param {Number} columnIndex
+ */
+ 'afteredit' : true,
+ /**
+ * @event bodyscroll
+ * Fires when the body element is scrolled
+ * @param {Number} scrollLeft
+ * @param {Number} scrollTop
+ */
+ 'bodyscroll' : true,
+ /**
+ * @event columnresize
+ * Fires when the user resizes a column
+ * @param {Number} columnIndex
+ * @param {Number} newSize
+ */
+ 'columnresize' : true,
+ /**
+ * @event startdrag
+ * Fires when row(s) start being dragged
+ * @param {Grid} this
+ * @param {YAHOO.ext.GridDD} dd The drag drop object
+ * @param {event} e The raw browser event
+ */
+ 'startdrag' : true,
+ /**
+ * @event enddrag
+ * Fires when a drag operation is complete
+ * @param {Grid} this
+ * @param {YAHOO.ext.GridDD} dd The drag drop object
+ * @param {event} e The raw browser event
+ */
+ 'enddrag' : true,
+ /**
+ * @event dragdrop
+ * Fires when dragged row(s) are dropped on a valid DD target
+ * @param {Grid} this
+ * @param {YAHOO.ext.GridDD} dd The drag drop object
+ * @param {String} targetId The target drag drop object
+ * @param {event} e The raw browser event
+ */
+ 'dragdrop' : true,
+ /**
+ * @event dragover
+ * Fires while row(s) are being dragged. "targetId" is the id of the Yahoo.util.DD object the selected rows are being dragged over.
+ * @param {Grid} this
+ * @param {YAHOO.ext.GridDD} dd The drag drop object
+ * @param {String} targetId The target drag drop object
+ * @param {event} e The raw browser event
+ */
+ 'dragover' : true,
+ /**
+ * @event dragenter
+ * Fires when the dragged row(s) first cross another DD target while being dragged
+ * @param {Grid} this
+ * @param {YAHOO.ext.GridDD} dd The drag drop object
+ * @param {String} targetId The target drag drop object
+ * @param {event} e The raw browser event
+ */
+ 'dragenter' : true,
+ /**
+ * @event dragout
+ * Fires when the dragged row(s) leave another DD target while being dragged
+ * @param {Grid} this
+ * @param {YAHOO.ext.GridDD} dd The drag drop object
+ * @param {String} targetId The target drag drop object
+ * @param {event} e The raw browser event
+ */
+ 'dragout' : true
+ };
+};
+
+YAHOO.ext.grid.Grid.prototype = {
+ /** The minimum width a column can be resized to. (Defaults to 25)
+ * @type Number */
+ minColumnWidth : 25,
+
+ /** True to automatically resize the columns to fit their content on initial render
+ * @type Boolean */
+ autoSizeColumns : false,
+
+ /** True to measure headers with column data when auto sizing columns
+ * @type Boolean */
+ autoSizeHeaders : false,
+
+ /**
+ * True to autoSize the grid when the window resizes - defaults to true
+ */
+ monitorWindowResize : true,
+
+ /** If autoSizeColumns is on, maxRowsToMeasure can be used to limit the number of
+ * rows measured to get a columns size - defaults to 0 (all rows).
+ * @type Number */
+ maxRowsToMeasure : 0,
+
+ /** True to highlight rows when the mouse is over (default is false)
+ * @type Boolean */
+ trackMouseOver : false,
+
+ /** True to enable drag and drop of rows
+ * @type Boolean */
+ enableDragDrop : false,
+
+ /** True to stripe the rows (default is true)
+ * @type Boolean */
+ stripeRows : true,
+ /** True to fit the height of the grid container to the height of the data (defaults to false)
+ * @type Boolean */
+ autoHeight : false,
+
+ /** True to fit the width of the grid container to the width of the columns (defaults to false)
+ * @type Boolean */
+ autoWidth : false,
+
+ /**
+ * The view used by the grid. This can be set before a call to render().
+ * Defaults to a YAHOO.ext.grid.GridView or PagedGridView depending on the data model.
+ * @type Object
+ */
+ view : null,
+
+ /** A regular expression defining tagNames
+ * allowed to have text selection (Defaults to /INPUT|TEXTAREA|SELECT/i
) */
+ allowTextSelectionPattern : /INPUT|TEXTAREA|SELECT/i,
+
+ /**
+ * Called once after all setup has been completed and the grid is ready to be rendered.
+ * @return {YAHOO.ext.grid.Grid} this
+ */
+ render : function(){
+ if((!this.container.dom.offsetHeight || this.container.dom.offsetHeight < 20)
+ || this.container.getStyle('height') == 'auto'){
+ this.autoHeight = true;
+ }
+ if((!this.container.dom.offsetWidth || this.container.dom.offsetWidth < 20)){
+ this.autoWidth = true;
+ }
+ if(!this.view){
+ if(this.dataModel.isPaged()){
+ this.view = new YAHOO.ext.grid.PagedGridView();
+ }else{
+ this.view = new YAHOO.ext.grid.GridView();
+ }
+ }
+ this.view.init(this);
+ this.el = getEl(this.view.render(), true);
+ var c = this.container;
+ c.mon("click", this.onClick, this, true);
+ c.mon("dblclick", this.onDblClick, this, true);
+ c.mon("contextmenu", this.onContextMenu, this, true);
+ c.mon("selectstart", this.cancelTextSelection, this, true);
+ c.mon("mousedown", this.cancelTextSelection, this, true);
+ c.mon("mousedown", this.onMouseDown, this, true);
+ c.mon("mouseup", this.onMouseUp, this, true);
+ if(this.trackMouseOver){
+ this.el.mon("mouseover", this.onMouseOver, this, true);
+ this.el.mon("mouseout", this.onMouseOut, this, true);
+ }
+ c.mon("keypress", this.onKeyPress, this, true);
+ c.mon("keydown", this.onKeyDown, this, true);
+ this.init();
+ return this;
+ },
+
+ init : function(){
+ this.rows = this.el.dom.rows;
+ if(!this.disableSelection){
+ if(!this.selModel){
+ this.selModel = new YAHOO.ext.grid.DefaultSelectionModel(this);
+ }
+ this.selModel.init(this);
+ this.selModel.onSelectionChange.subscribe(this.updateField, this, true);
+ }else{
+ this.selModel = new YAHOO.ext.grid.DisableSelectionModel(this);
+ this.selModel.init(this);
+ }
+
+ if(this.enableDragDrop){
+ this.dd = new YAHOO.ext.grid.GridDD(this, this.container.dom);
+ }
+ },
+
+ /**
+ * Resets the grid for use with a new configuration and/or data and column models. After calling this function
+ * you will need to call render() again. Any listeners for this grid will be retained.
+ * Warning: any listeners manually attached (not through the grid) to the grid's container
+ * element will be removed.
+ * @param {Object} config Standard config object with properties to set on this grid
+ * @return {YAHOO.ext.grid.Grid} this
+ */
+ reset : function(config){
+ this.destroy(false, true);
+ YAHOO.ext.util.Config.apply(this, config);
+ return this;
+ },
+
+ /**
+ * Destroy this grid.
+ * @param {Boolean} removeEl True to remove the element
+ */
+ destroy : function(removeEl, keepListeners){
+ var c = this.container;
+ c.removeAllListeners();
+ this.view.destroy();
+ YAHOO.ext.EventManager.removeResizeListener(this.view.onWindowResize, this.view);
+ this.view = null;
+ this.colModel.purgeListeners();
+ if(!keepListeners){
+ this.purgeListeners();
+ }
+ c.update('');
+ if(removeEl === true){
+ c.remove();
+ }
+ },
+
+ /**
+ * Replace the current data model with a new one (experimental)
+ * @param {DataModel} dm The new data model
+ * @pram {Boolean} rerender true to render the grid rows from scratch
+ */
+ setDataModel : function(dm, rerender){
+ this.view.unplugDataModel(this.dataModel);
+ this.dataModel = dm;
+ this.view.plugDataModel(dm);
+ if(rerender){
+ dm.fireEvent('datachanged');
+ }
+ },
+
+ onMouseDown : function(e){
+ this.fireEvent('mousedown', e);
+ },
+
+ onMouseUp : function(e){
+ this.fireEvent('mouseup', e);
+ },
+
+ onMouseOver : function(e){
+ this.fireEvent('mouseover', e);
+ },
+
+ onMouseOut : function(e){
+ this.fireEvent('mouseout', e);
+ },
+
+ onKeyPress : function(e){
+ this.fireEvent('keypress', e);
+ },
+
+ onKeyDown : function(e){
+ this.fireEvent('keydown', e);
+ },
+
+ fireEvent : YAHOO.ext.util.Observable.prototype.fireEvent,
+ on : YAHOO.ext.util.Observable.prototype.on,
+ addListener : YAHOO.ext.util.Observable.prototype.addListener,
+ delayedListener : YAHOO.ext.util.Observable.prototype.delayedListener,
+ removeListener : YAHOO.ext.util.Observable.prototype.removeListener,
+ purgeListeners : YAHOO.ext.util.Observable.prototype.purgeListeners,
+ bufferedListener : YAHOO.ext.util.Observable.prototype.bufferedListener,
+
+ onClick : function(e){
+ this.fireEvent('click', e);
+ var target = e.getTarget();
+ var row = this.getRowFromChild(target);
+ var cell = this.getCellFromChild(target);
+ var header = this.getHeaderFromChild(target);
+ if(cell){
+ this.fireEvent('cellclick', this, row.rowIndex, cell.columnIndex, e);
+ }
+ if(row){
+ this.fireEvent('rowclick', this, row.rowIndex, e);
+ }
+ if(header){
+ this.fireEvent('headerclick', this, header.columnIndex, e);
+ }
+ },
+
+ onContextMenu : function(e){
+ var target = e.getTarget();
+ var row = this.getRowFromChild(target);
+ var cell = this.getCellFromChild(target);
+ var header = this.getHeaderFromChild(target);
+ if(cell){
+ this.fireEvent('cellcontextmenu', this, row.rowIndex, cell.columnIndex, e);
+ }
+ if(row){
+ this.fireEvent('rowcontextmenu', this, row.rowIndex, e);
+ }
+ if(header){
+ this.fireEvent('headercontextmenu', this, header.columnIndex, e);
+ }
+ e.preventDefault();
+ },
+
+ onDblClick : function(e){
+ this.fireEvent('dblclick', e);
+ var target = e.getTarget();
+ var row = this.getRowFromChild(target);
+ var cell = this.getCellFromChild(target);
+ if(row){
+ this.fireEvent('rowdblclick', this, row.rowIndex, e);
+ }
+ if(cell){
+ this.fireEvent('celldblclick', this, row.rowIndex, cell.columnIndex, e);
+ }
+ },
+
+ /**
+ * Starts editing the specified for the specified row/column
+ * @param {Number} rowIndex
+ * @param {Number} colIndex
+ */
+ startEditing : function(rowIndex, colIndex){
+ var row = this.rows[rowIndex];
+ var cell = row.childNodes[colIndex];
+ this.stopEditing();
+ setTimeout(this.doEdit.createDelegate(this, [row, cell]), 10);
+ },
+
+ /**
+ * Stops any active editing
+ */
+ stopEditing : function(){
+ if(this.activeEditor){
+ this.activeEditor.stopEditing();
+ }
+ },
+
+ /** @ignore */
+ doEdit : function(row, cell){
+ if(!row || !cell) return;
+ var cm = this.colModel;
+ var dm = this.dataModel;
+ var colIndex = cell.columnIndex;
+ var rowIndex = row.rowIndex;
+ if(cm.isCellEditable(colIndex, rowIndex)){
+ var ed = cm.getCellEditor(colIndex, rowIndex);
+ if(ed){
+ if(this.activeEditor){
+ this.activeEditor.stopEditing();
+ }
+ this.fireEvent('beforeedit', this, rowIndex, colIndex);
+ this.activeEditor = ed;
+ this.editingCell = cell;
+ this.view.ensureVisible(row, true);
+ try{
+ cell.focus();
+ }catch(e){}
+ ed.init(this, this.el.dom.parentNode, this.setValueDelegate);
+ var value = dm.getValueAt(rowIndex, cm.getDataIndex(colIndex));
+ // set timeout so firefox stops editing before starting a new edit
+ setTimeout(ed.startEditing.createDelegate(ed, [value, row, cell]), 1);
+ }
+ }
+ },
+
+ setCellValue : function(value, rowIndex, colIndex){
+ this.dataModel.setValueAt(value, rowIndex, this.colModel.getDataIndex(colIndex));
+ this.fireEvent('afteredit', this, rowIndex, colIndex);
+ },
+
+ /** @ignore Called when text selection starts or mousedown to prevent default */
+ cancelTextSelection : function(e){
+ var target = e.getTarget();
+ if(target && target != this.el.dom.parentNode && !this.allowTextSelectionPattern.test(target.tagName)){
+ e.preventDefault();
+ }
+ },
+
+ /**
+ * Causes the grid to manually recalculate it's dimensions. Generally this is done automatically,
+ * but if manual update is required this method will initiate it.
+ */
+ autoSize : function(){
+ this.view.updateWrapHeight();
+ this.view.adjustForScroll();
+ },
+
+ /**
+ * Scrolls the grid to the specified row
+ * @param {Number/HTMLElement} row The row object or index of the row
+ */
+ scrollTo : function(row){
+ if(typeof row == 'number'){
+ row = this.rows[row];
+ }
+ this.view.ensureVisible(row, true);
+ },
+
+ /** @private */
+ getEditingCell : function(){
+ return this.editingCell;
+ },
+
+ /**
+ * Binds this grid to the field with the specified id. Initially reads and parses the comma
+ * delimited ids in the field and selects those items. All selections made in the grid
+ * will be persisted to the field by their ids comma delimited.
+ * @param {String} The id of the field to bind to
+ */
+ bindToField : function(fieldId){
+ this.fieldId = fieldId;
+ this.readField();
+ },
+
+ /** @private */
+ updateField : function(){
+ if(this.fieldId){
+ var field = YAHOO.util.Dom.get(this.fieldId);
+ field.value = this.getSelectedRowIds().join(',');
+ }
+ },
+
+ /**
+ * Causes the grid to read and select the ids from the bound field - See {@link #bindToField}.
+ */
+ readField : function(){
+ if(this.fieldId){
+ var field = YAHOO.util.Dom.get(this.fieldId);
+ var values = field.value.split(',');
+ var rows = this.getRowsById(values);
+ this.selModel.selectRows(rows, false);
+ }
+ },
+
+ /**
+ * Returns the table row at the specified index
+ * @param {Number} index
+ * @return {HTMLElement}
+ */
+ getRow : function(index){
+ return this.rows[index];
+ },
+
+ /**
+ * Returns the rows that have the specified id(s). The id value for a row is provided
+ * by the DataModel. See {@link YAHOO.ext.grid.DefaultDataModel#getRowId}.
+ * @param {String/Array} An id to find or an array of ids
+ * @return {HtmlElement/Array} If one id was passed in, it returns one result.
+ * If an array of ids was specified, it returns an Array of HTMLElements
+ */
+ getRowsById : function(id){
+ var dm = this.dataModel;
+ if(!(id instanceof Array)){
+ for(var i = 0; i < this.rows.length; i++){
+ if(dm.getRowId(i) == id){
+ return this.rows[i];
+ }
+ }
+ return null;
+ }
+ var found = [];
+ var re = "^(?:";
+ for(var i = 0; i < id.length; i++){
+ re += id[i];
+ if(i != id.length-1) re += "|";
+ }
+ var regex = new RegExp(re + ")$");
+ for(var i = 0; i < this.rows.length; i++){
+ if(regex.test(dm.getRowId(i))){
+ found.push(this.rows[i]);
+ }
+ }
+ return found;
+ },
+
+ /**
+ * Returns the row that comes after the specified row - text nodes are skipped.
+ * @param {HTMLElement} row
+ * @return {HTMLElement}
+ */
+ getRowAfter : function(row){
+ return this.getSibling('next', row);
+ },
+
+ /**
+ * Returns the row that comes before the specified row - text nodes are skipped.
+ * @param {HTMLElement} row
+ * @return {HTMLElement}
+ */
+ getRowBefore : function(row){
+ return this.getSibling('previous', row);
+ },
+
+ /**
+ * Returns the cell that comes after the specified cell - text nodes are skipped.
+ * @param {HTMLElement} cell
+ * @param {Boolean} includeHidden
+ * @return {HTMLElement}
+ */
+ getCellAfter : function(cell, includeHidden){
+ var next = this.getSibling('next', cell);
+ if(next && !includeHidden && this.colModel.isHidden(next.columnIndex)){
+ return this.getCellAfter(next);
+ }
+ return next;
+ },
+
+ /**
+ * Returns the cell that comes before the specified cell - text nodes are skipped.
+ * @param {HTMLElement} cell
+ * @param {Boolean} includeHidden
+ * @return {HTMLElement}
+ */
+ getCellBefore : function(cell, includeHidden){
+ var prev = this.getSibling('previous', cell);
+ if(prev && !includeHidden && this.colModel.isHidden(prev.columnIndex)){
+ return this.getCellBefore(prev);
+ }
+ return prev;
+ },
+
+ /**
+ * Returns the last cell for the row - text nodes and hidden columns are skipped.
+ * @param {HTMLElement} row
+ * @param {Boolean} includeHidden
+ * @return {HTMLElement}
+ */
+ getLastCell : function(row, includeHidden){
+ var cell = this.getElement('previous', row.lastChild);
+ if(cell && !includeHidden && this.colModel.isHidden(cell.columnIndex)){
+ return this.getCellBefore(cell);
+ }
+ return cell;
+ },
+
+ /**
+ * Returns the first cell for the row - text nodes and hidden columns are skipped.
+ * @param {HTMLElement} row
+ * @param {Boolean} includeHidden
+ * @return {HTMLElement}
+ */
+ getFirstCell : function(row, includeHidden){
+ var cell = this.getElement('next', row.firstChild);
+ if(cell && !includeHidden && this.colModel.isHidden(cell.columnIndex)){
+ return this.getCellAfter(cell);
+ }
+ return cell;
+ },
+
+ /**
+ * @private
+ * Gets siblings, skipping text nodes
+ * @param {String} type The direction to walk: 'next' or 'previous'
+ * @param {HTMLElement} node
+ */
+ getSibling : function(type, node){
+ if(!node) return null;
+ type += 'Sibling';
+ var n = node[type];
+ while(n && n.nodeType != 1){
+ n = n[type];
+ }
+ return n;
+ },
+
+ /**
+ * Returns node if node is an HTMLElement else walks the siblings in direction looking for
+ * a node that is an element
+ * @param {String} direction The direction to walk: 'next' or 'previous'
+ * @private
+ */
+ getElement : function(direction, node){
+ if(!node || node.nodeType == 1) return node;
+ else return this.getSibling(direction, node);
+ },
+
+ /**
+ * @private
+ */
+ getElementFromChild : function(childEl, parentClass){
+ if(!childEl || (YAHOO.util.Dom.hasClass(childEl, parentClass))){
+ return childEl;
+ }
+ var p = childEl.parentNode;
+ var b = document.body;
+ while(p && p != b){
+ if(YAHOO.util.Dom.hasClass(p, parentClass)){
+ return p;
+ }
+ p = p.parentNode;
+ }
+ return null;
+ },
+
+ /**
+ * Returns the row that contains the specified child element.
+ * @param {HTMLElement} childEl
+ * @return {HTMLElement}
+ */
+ getRowFromChild : function(childEl){
+ return this.getElementFromChild(childEl, 'ygrid-row');
+ },
+
+ /**
+ * Returns the cell that contains the specified child element.
+ * @param {HTMLElement} childEl
+ * @return {HTMLElement}
+ */
+ getCellFromChild : function(childEl){
+ return this.getElementFromChild(childEl, 'ygrid-col');
+ },
+
+
+ /**
+ * Returns the header element that contains the specified child element.
+ * @param {HTMLElement} childEl
+ * @return {HTMLElement}
+ */
+ getHeaderFromChild : function(childEl){
+ return this.getElementFromChild(childEl, 'ygrid-hd');
+ },
+
+ /**
+ * Convenience method for getSelectionModel().getSelectedRows() -
+ * See {@link YAHOO.ext.grid.DefaultSelectionModel#getSelectedRows} for more details.
+ * @return {Array}
+ */
+ getSelectedRows : function(){
+ return this.selModel.getSelectedRows();
+ },
+
+ /**
+ * Convenience method for getSelectionModel().getSelectedRows()[0] -
+ * See {@link YAHOO.ext.grid.DefaultSelectionModel#getSelectedRows} for more details.
+ * @return {HTMLElement}
+ */
+ getSelectedRow : function(){
+ if(this.selModel.hasSelection()){
+ return this.selModel.getSelectedRows()[0];
+ }
+ return null;
+ },
+
+ /**
+ * Get the selected row indexes
+ * @return {Array} Array of indexes
+ */
+ getSelectedRowIndexes : function(){
+ var a = [];
+ var rows = this.selModel.getSelectedRows();
+ for(var i = 0; i < rows.length; i++) {
+ a[i] = rows[i].rowIndex;
+ }
+ return a;
+ },
+
+ /**
+ * Gets the first selected row or -1 if none are selected
+ * @return {Number}
+ */
+ getSelectedRowIndex : function(){
+ if(this.selModel.hasSelection()){
+ return this.selModel.getSelectedRows()[0].rowIndex;
+ }
+ return -1;
+ },
+
+ /**
+ * Convenience method for getSelectionModel().getSelectedRowIds()[0] -
+ * See {@link YAHOO.ext.grid.DefaultSelectionModel#getSelectedRowIds} for more details.
+ * @return {String}
+ */
+ getSelectedRowId : function(){
+ if(this.selModel.hasSelection()){
+ return this.selModel.getSelectedRowIds()[0];
+ }
+ return null;
+ },
+
+ /**
+ * Convenience method for getSelectionModel().getSelectedRowIds() -
+ * See {@link YAHOO.ext.grid.DefaultSelectionModel#getSelectedRowIds} for more details.
+ * @return {Array}
+ */
+ getSelectedRowIds : function(){
+ return this.selModel.getSelectedRowIds();
+ },
+
+ /**
+ * Convenience method for getSelectionModel().clearSelections() -
+ * See {@link YAHOO.ext.grid.DefaultSelectionModel#clearSelections} for more details.
+ */
+ clearSelections : function(){
+ this.selModel.clearSelections();
+ },
+
+
+ /**
+ * Convenience method for getSelectionModel().selectAll() -
+ * See {@link YAHOO.ext.grid.DefaultSelectionModel#selectAll} for more details.
+ */
+ selectAll : function(){
+ this.selModel.selectAll();
+ },
+
+
+ /**
+ * Convenience method for getSelectionModel().getCount() -
+ * See {@link YAHOO.ext.grid.DefaultSelectionModel#getCount} for more details.
+ * @return {Number}
+ */
+ getSelectionCount : function(){
+ return this.selModel.getCount();
+ },
+
+ /**
+ * Convenience method for getSelectionModel().hasSelection() -
+ * See {@link YAHOO.ext.grid.DefaultSelectionModel#hasSelection} for more details.
+ * @return {Boolean}
+ */
+ hasSelection : function(){
+ return this.selModel.hasSelection();
+ },
+
+ /**
+ * Returns the grid's SelectionModel.
+ * @return {SelectionModel}
+ */
+ getSelectionModel : function(){
+ if(!this.selModel){
+ this.selModel = new DefaultSelectionModel();
+ }
+ return this.selModel;
+ },
+
+ /**
+ * Returns the grid's DataModel.
+ * @return {DataModel}
+ */
+ getDataModel : function(){
+ return this.dataModel;
+ },
+
+ /**
+ * Returns the grid's ColumnModel.
+ * @return {ColumnModel}
+ */
+ getColumnModel : function(){
+ return this.colModel;
+ },
+
+ /**
+ * Returns the grid's GridView object.
+ * @return {GridView}
+ */
+ getView : function(){
+ return this.view;
+ },
+ /**
+ * Called to get grid's drag proxy text, by default returns this.ddText.
+ * @return {String}
+ */
+ getDragDropText : function(){
+ return this.ddText.replace('%0', this.selModel.getCount());
+ }
+};
+/**
+ * Configures the text is the drag proxy (defaults to "%0 selected row(s)").
+ * %0 is replaced with the number of selected rows.
+ * @type String
+ */
+YAHOO.ext.grid.Grid.prototype.ddText = "%0 selected row(s)";
diff --git a/frontend/beta/js/YUI-extensions/grid/GridDD.js b/frontend/beta/js/YUI-extensions/grid/GridDD.js
new file mode 100644
index 0000000..cdcaf39
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/GridDD.js
@@ -0,0 +1,101 @@
+
+// kill dependency issue
+if(YAHOO.util.DDProxy){
+/**
+ * @class YAHOO.ext.grid.GridDD
+ * Custom implementation of YAHOO.util.DDProxy used internally by the grid
+ * @extends YAHOO.util.DDProxy
+ */
+YAHOO.ext.grid.GridDD = function(grid, bwrap){
+ this.grid = grid;
+ var ddproxy = document.createElement('div');
+ ddproxy.id = grid.container.id + '-ddproxy';
+ ddproxy.className = 'ygrid-drag-proxy';
+ document.body.insertBefore(ddproxy, document.body.firstChild);
+ YAHOO.util.Dom.setStyle(ddproxy, 'opacity', .80);
+ var ddicon = document.createElement('span');
+ ddicon.className = 'ygrid-drop-icon ygrid-drop-nodrop';
+ ddproxy.appendChild(ddicon);
+ var ddtext = document.createElement('span');
+ ddtext.className = 'ygrid-drag-text';
+ ddtext.innerHTML = " ";
+ ddproxy.appendChild(ddtext);
+
+ this.ddproxy = ddproxy;
+ this.ddtext = ddtext;
+ this.ddicon = ddicon;
+ YAHOO.util.Event.on(bwrap, 'click', this.handleClick, this, true);
+ YAHOO.ext.grid.GridDD.superclass.constructor.call(this, bwrap.id, 'GridDD',
+ {dragElId : ddproxy.id, resizeFrame: false});
+
+ this.unlockDelegate = grid.selModel.unlock.createDelegate(grid.selModel);
+};
+YAHOO.extendX(YAHOO.ext.grid.GridDD, YAHOO.util.DDProxy);
+
+YAHOO.ext.grid.GridDD.prototype.handleMouseDown = function(e){
+ var row = this.grid.getRowFromChild(YAHOO.util.Event.getTarget(e));
+ if(!row) return;
+ if(this.grid.selModel.isSelected(row)){
+ YAHOO.ext.grid.GridDD.superclass.handleMouseDown.call(this, e);
+ }else {
+ this.grid.selModel.unlock();
+ YAHOO.ext.EventObject.setEvent(e);
+ this.grid.selModel.rowClick(this.grid, row.rowIndex, YAHOO.ext.EventObject);
+ YAHOO.ext.grid.GridDD.superclass.handleMouseDown.call(this, e);
+ this.grid.selModel.lock();
+ }
+};
+
+YAHOO.ext.grid.GridDD.prototype.handleClick = function(e){
+ if(this.grid.selModel.isLocked()){
+ setTimeout(this.unlockDelegate, 1);
+ YAHOO.util.Event.stopEvent(e);
+ }
+};
+
+/**
+ * Updates the DD visual element to allow/not allow a drop
+ * @param {Boolean} dropStatus True if drop is allowed on the target
+ */
+YAHOO.ext.grid.GridDD.prototype.setDropStatus = function(dropStatus){
+ if(dropStatus === true){
+ YAHOO.util.Dom.replaceClass(this.ddicon, 'ygrid-drop-nodrop', 'ygrid-drop-ok');
+ }else{
+ YAHOO.util.Dom.replaceClass(this.ddicon, 'ygrid-drop-ok', 'ygrid-drop-nodrop');
+ }
+};
+
+YAHOO.ext.grid.GridDD.prototype.startDrag = function(e){
+ this.ddtext.innerHTML = this.grid.getDragDropText();
+ this.setDropStatus(false);
+ this.grid.selModel.lock();
+ this.grid.fireEvent('startdrag', this.grid, this, e);
+};
+
+YAHOO.ext.grid.GridDD.prototype.endDrag = function(e){
+ YAHOO.util.Dom.setStyle(this.ddproxy, 'visibility', 'hidden');
+ this.grid.fireEvent('enddrag', this.grid, this, e);
+};
+
+YAHOO.ext.grid.GridDD.prototype.autoOffset = function(iPageX, iPageY) {
+ this.setDelta(-12, -20);
+};
+
+YAHOO.ext.grid.GridDD.prototype.onDragEnter = function(e, id) {
+ this.setDropStatus(true);
+ this.grid.fireEvent('dragenter', this.grid, this, id, e);
+};
+
+YAHOO.ext.grid.GridDD.prototype.onDragDrop = function(e, id) {
+ this.grid.fireEvent('dragdrop', this.grid, this, id, e);
+};
+
+YAHOO.ext.grid.GridDD.prototype.onDragOver = function(e, id) {
+ this.grid.fireEvent('dragover', this.grid, this, id, e);
+};
+
+YAHOO.ext.grid.GridDD.prototype.onDragOut = function(e, id) {
+ this.setDropStatus(false);
+ this.grid.fireEvent('dragout', this.grid, this, id, e);
+};
+};
diff --git a/frontend/beta/js/YUI-extensions/grid/GridView.js b/frontend/beta/js/YUI-extensions/grid/GridView.js
new file mode 100644
index 0000000..dbd47e3
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/GridView.js
@@ -0,0 +1,790 @@
+/**
+ * @class YAHOO.ext.grid.GridView
+ * Default UI code used internally by the Grid. This is the object returned by {@link YAHOO.ext.grid.Grid#getView}.
+ * @constructor
+ */
+YAHOO.ext.grid.GridView = function(){
+ this.grid = null;
+ this.lastFocusedRow = null;
+ this.onScroll = new YAHOO.util.CustomEvent('onscroll');
+ this.adjustScrollTask = new YAHOO.ext.util.DelayedTask(this._adjustForScroll, this);
+ this.ensureVisibleTask = new YAHOO.ext.util.DelayedTask();
+};
+
+YAHOO.ext.grid.GridView.prototype = {
+ init: function(grid){
+ this.grid = grid;
+ },
+
+ fireScroll: function(scrollLeft, scrollTop){
+ this.onScroll.fireDirect(this.grid, scrollLeft, scrollTop);
+ },
+
+ /**
+ * @private
+ * Utility method that gets an array of the cell renderers
+ */
+ getColumnRenderers : function(){
+ var renderers = [];
+ var cm = this.grid.colModel;
+ var colCount = cm.getColumnCount();
+ for(var i = 0; i < colCount; i++){
+ renderers.push(cm.getRenderer(i));
+ }
+ return renderers;
+ },
+
+ buildIndexMap : function(){
+ var colToData = {};
+ var dataToCol = {};
+ var cm = this.grid.colModel;
+ for(var i = 0, len = cm.getColumnCount(); i < len; i++){
+ var di = cm.getDataIndex(i);
+ colToData[i] = di;
+ dataToCol[di] = i;
+ }
+ return {'colToData': colToData, 'dataToCol': dataToCol};
+ },
+
+ getDataIndexes : function(){
+ if(!this.indexMap){
+ this.indexMap = this.buildIndexMap();
+ }
+ return this.indexMap.colToData;
+ },
+
+ getColumnIndexByDataIndex : function(dataIndex){
+ if(!this.indexMap){
+ this.indexMap = this.buildIndexMap();
+ }
+ return this.indexMap.dataToCol[dataIndex];
+ },
+
+ updateHeaders : function(){
+ var colModel = this.grid.colModel;
+ var hcells = this.headers;
+ var colCount = colModel.getColumnCount();
+ for(var i = 0; i < colCount; i++){
+ hcells[i].textNode.innerHTML = colModel.getColumnHeader(i);
+ }
+ },
+
+ adjustForScroll : function(disableDelay){
+ if(!disableDelay){
+ this.adjustScrollTask.delay(50);
+ }else{
+ this._adjustForScroll();
+ }
+ },
+
+ /**
+ * Returns the rowIndex/columnIndex of the cell found at the passed page coordinates
+ * @param {Number} x
+ * @param {Number} y
+ * @return {Array} [rowIndex, columnIndex]
+ */
+ getCellAtPoint : function(x, y){
+ var colIndex = null;
+ var rowIndex = null;
+
+ // translate page coordinates to local coordinates
+ var xy = YAHOO.util.Dom.getXY(this.wrap);
+ x = (x - xy[0]) + this.wrap.scrollLeft;
+ y = (y - xy[1]) + this.wrap.scrollTop;
+
+ var colModel = this.grid.colModel;
+ var pos = 0;
+ var colCount = colModel.getColumnCount();
+ for(var i = 0; i < colCount; i++){
+ if(colModel.isHidden(i)) continue;
+ var width = colModel.getColumnWidth(i);
+ if(x >= pos && x < pos+width){
+ colIndex = i;
+ break;
+ }
+ pos += width;
+ }
+ if(colIndex != null){
+ rowIndex = (y == 0 ? 0 : Math.floor(y / this.getRowHeight()));
+ if(rowIndex >= this.grid.dataModel.getRowCount()){
+ return null;
+ }
+ return [colIndex, rowIndex];
+ }
+ return null;
+ },
+
+ /** @private */
+ _adjustForScroll : function(){
+ this.forceScrollUpdate();
+ if(this.scrollbarMode == YAHOO.ext.grid.GridView.SCROLLBARS_OVERLAP){
+ var adjustment = 0;
+ if(this.wrap.clientWidth && this.wrap.clientWidth !== 0){
+ adjustment = this.wrap.offsetWidth - this.wrap.clientWidth;
+ }
+ this.hwrap.setWidth(this.wrap.offsetWidth-adjustment);
+ }else{
+ this.hwrap.setWidth(this.wrap.offsetWidth);
+ }
+ this.bwrap.setWidth(Math.max(this.grid.colModel.getTotalWidth(), this.wrap.clientWidth));
+ },
+
+ /**
+ * Focuses the specified row. The preferred way to scroll to a row is {@link #ensureVisible}.
+ * @param {Number/HTMLElement} row The index of a row or the row itself
+ */
+ focusRow : function(row){
+ if(typeof row == 'number'){
+ row = this.getBodyTable().childNodes[row];
+ }
+ if(!row) return;
+ var left = this.wrap.scrollLeft;
+ try{ // try catch for IE occasional focus bug
+ row.childNodes.item(0).hideFocus = true;
+ row.childNodes.item(0).focus();
+ }catch(e){}
+ this.ensureVisible(row);
+ this.wrap.scrollLeft = left;
+ this.handleScroll();
+ this.lastFocusedRow = row;
+ },
+
+ /**
+ * Scrolls the specified row into view. This call is automatically buffered (delayed), to disable
+ * the delay, pass true for disableDelay.
+ * @param {Number/HTMLElement} row The index of a row or the row itself
+ * @param {Boolean} disableDelay
+ */
+ ensureVisible : function(row, disableDelay){
+ if(!disableDelay){
+ this.ensureVisibleTask.delay(50, this._ensureVisible, this, [row]);
+ }else{
+ this._ensureVisible(row);
+ }
+ },
+
+ /** @ignore */
+ _ensureVisible : function(row){
+ if(typeof row == 'number'){
+ row = this.getBodyTable().childNodes[row];
+ }
+ if(!row) return;
+ var left = this.wrap.scrollLeft;
+ var rowTop = parseInt(row.offsetTop, 10); // parseInt for safari bug
+ var rowBottom = rowTop + row.offsetHeight;
+ var clientTop = parseInt(this.wrap.scrollTop, 10); // parseInt for safari bug
+ var clientBottom = clientTop + this.wrap.clientHeight;
+ if(rowTop < clientTop){
+ this.wrap.scrollTop = rowTop;
+ }else if(rowBottom > clientBottom){
+ this.wrap.scrollTop = rowBottom-this.wrap.clientHeight;
+ }
+ this.wrap.scrollLeft = left;
+ this.handleScroll();
+ },
+
+ updateColumns : function(){
+ this.grid.stopEditing();
+ var colModel = this.grid.colModel;
+ var hcols = this.headers;
+ var colCount = colModel.getColumnCount();
+ var pos = 0;
+ var totalWidth = colModel.getTotalWidth();
+ for(var i = 0; i < colCount; i++){
+ if(colModel.isHidden(i)) continue;
+ var width = colModel.getColumnWidth(i);
+ hcols[i].style.width = width + 'px';
+ hcols[i].style.left = pos + 'px';
+ hcols[i].split.style.left = (pos+width-3) + 'px';
+ this.setCSSWidth(i, width, pos);
+ pos += width;
+ }
+ this.lastWidth = totalWidth;
+ if(this.grid.autoWidth){
+ this.grid.container.setWidth(totalWidth+this.grid.container.getBorderWidth('lr'));
+ this.grid.autoSize();
+ }
+ this.bwrap.setWidth(Math.max(totalWidth, this.wrap.clientWidth));
+ if(!YAHOO.ext.util.Browser.isIE){ // fix scrolling prob in gecko and opera
+ this.wrap.scrollLeft = this.hwrap.dom.scrollLeft;
+ }
+ this.syncScroll();
+ this.forceScrollUpdate();
+ if(this.grid.autoHeight){
+ this.autoHeight();
+ this.updateWrapHeight();
+ }
+ },
+
+ setCSSWidth : function(colIndex, width, pos){
+ var selector = ["#" + this.grid.id + " .ygrid-col-" + colIndex, ".ygrid-col-" + colIndex];
+ YAHOO.ext.util.CSS.updateRule(selector, 'width', width + 'px');
+ if(typeof pos == 'number'){
+ YAHOO.ext.util.CSS.updateRule(selector, 'left', pos + 'px');
+ }
+ },
+
+ /**
+ * Set a css style for a column dynamically.
+ * @param {Number} colIndex The index of the column
+ * @param {String} name The css property name
+ * @param {String} value The css value
+ */
+ setCSSStyle : function(colIndex, name, value){
+ var selector = ["#" + this.grid.id + " .ygrid-col-" + colIndex, ".ygrid-col-" + colIndex];
+ YAHOO.ext.util.CSS.updateRule(selector, name, value);
+ },
+
+ handleHiddenChange : function(colModel, colIndex, hidden){
+ if(hidden){
+ this.hideColumn(colIndex);
+ }else{
+ this.unhideColumn(colIndex);
+ }
+ this.updateColumns();
+ },
+
+ hideColumn : function(colIndex){
+ var selector = ["#" + this.grid.id + " .ygrid-col-" + colIndex, ".ygrid-col-" + colIndex];
+ YAHOO.ext.util.CSS.updateRule(selector, 'position', 'absolute');
+ YAHOO.ext.util.CSS.updateRule(selector, 'visibility', 'hidden');
+
+ this.headers[colIndex].style.display = 'none';
+ this.headers[colIndex].split.style.display = 'none';
+ },
+
+ unhideColumn : function(colIndex){
+ var selector = ["#" + this.grid.id + " .ygrid-col-" + colIndex, ".ygrid-col-" + colIndex];
+ YAHOO.ext.util.CSS.updateRule(selector, 'position', '');
+ YAHOO.ext.util.CSS.updateRule(selector, 'visibility', 'visible');
+
+ this.headers[colIndex].style.display = '';
+ this.headers[colIndex].split.style.display = '';
+ },
+
+ getBodyTable : function(){
+ return this.bwrap.dom;
+ },
+
+ updateRowIndexes : function(firstRow, lastRow){
+ var stripeRows = this.grid.stripeRows;
+ var bt = this.getBodyTable();
+ var nodes = bt.childNodes;
+ firstRow = firstRow || 0;
+ lastRow = lastRow || nodes.length-1;
+ var re = /^(?:ygrid-row ygrid-row-alt|ygrid-row)/;
+ for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
+ var node = nodes[rowIndex];
+ if(stripeRows && (rowIndex+1) % 2 == 0){
+ node.className = node.className.replace(re, 'ygrid-row ygrid-row-alt');
+ }else{
+ node.className = node.className.replace(re, 'ygrid-row');
+ }
+ node.rowIndex = rowIndex;
+ nodes[rowIndex].style.top = (rowIndex * this.rowHeight) + 'px';
+ }
+ },
+
+ insertRows : function(dataModel, firstRow, lastRow){
+ this.updateBodyHeight();
+ this.adjustForScroll(true);
+ var renderers = this.getColumnRenderers();
+ var dindexes = this.getDataIndexes();
+ var colCount = this.grid.colModel.getColumnCount();
+ var beforeRow = null;
+ var bt = this.getBodyTable();
+ if(firstRow < bt.childNodes.length){
+ beforeRow = bt.childNodes[firstRow];
+ }
+ for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
+ var row = document.createElement('span');
+ row.className = 'ygrid-row';
+ row.style.top = (rowIndex * this.rowHeight) + 'px';
+ this.renderRow(dataModel, row, rowIndex, colCount, renderers, dindexes);
+ if(beforeRow){
+ bt.insertBefore(row, beforeRow);
+ }else{
+ bt.appendChild(row);
+ }
+ }
+ this.updateRowIndexes(firstRow);
+ this.adjustForScroll(true);
+ },
+
+ renderRow : function(dataModel, row, rowIndex, colCount, renderers, dindexes){
+ for(var colIndex = 0; colIndex < colCount; colIndex++){
+ var td = document.createElement('span');
+ td.className = 'ygrid-col ygrid-col-' + colIndex + (colIndex == colCount-1 ? ' ygrid-col-last' : '');
+ td.columnIndex = colIndex;
+ td.tabIndex = 0;
+ var span = document.createElement('span');
+ span.className = 'ygrid-cell-text';
+ td.appendChild(span);
+ var val = renderers[colIndex](dataModel.getValueAt(rowIndex, dindexes[colIndex]), rowIndex, colIndex, td, dataModel);
+ if(typeof val == 'undefined' || val === '') val = ' ';
+ span.innerHTML = val;
+ row.appendChild(td);
+ }
+ },
+
+ deleteRows : function(dataModel, firstRow, lastRow){
+ this.updateBodyHeight();
+ // first make sure they are deselected
+ this.grid.selModel.deselectRange(firstRow, lastRow);
+ var bt = this.getBodyTable();
+ var rows = []; // get references because the rowIndex will change
+ for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
+ rows.push(bt.childNodes[rowIndex]);
+ }
+ for(var i = 0; i < rows.length; i++){
+ bt.removeChild(rows[i]);
+ rows[i] = null;
+ }
+ rows = null;
+ this.updateRowIndexes(firstRow);
+ this.adjustForScroll();
+ },
+
+ updateRows : function(dataModel, firstRow, lastRow){
+ var bt = this.getBodyTable();
+ var dindexes = this.getDataIndexes();
+ var renderers = this.getColumnRenderers();
+ var colCount = this.grid.colModel.getColumnCount();
+ for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
+ var row = bt.rows[rowIndex];
+ var cells = row.childNodes;
+ for(var colIndex = 0; colIndex < colCount; colIndex++){
+ var td = cells[colIndex];
+ var val = renderers[colIndex](dataModel.getValueAt(rowIndex, dindexes[colIndex]), rowIndex, colIndex, td, dataModel);
+ if(typeof val == 'undefined' || val === '') val = ' ';
+ td.firstChild.innerHTML = val;
+ }
+ }
+ },
+
+ handleSort : function(dataModel, sortColumnIndex, sortDir, noRefresh){
+ var selectedRows;
+ this.grid.selModel.syncSelectionsToIds();
+ if(!noRefresh){
+ this.updateRows(dataModel, 0, dataModel.getRowCount()-1);
+ }
+ this.updateHeaderSortState();
+ selectedRows = this.grid.selModel.getSelectedRows();
+ if (selectedRows.length > 0) {
+ this.focusRow(selectedRows[0]);
+ }
+ },
+
+ syncScroll : function(){
+ this.hwrap.dom.scrollLeft = this.wrap.scrollLeft;
+ },
+
+ handleScroll : function(){
+ this.syncScroll();
+ this.fireScroll(this.wrap.scrollLeft, this.wrap.scrollTop);
+ this.grid.fireEvent('bodyscroll', this.wrap.scrollLeft, this.wrap.scrollTop);
+ },
+
+ getRowHeight : function(){
+ if(!this.rowHeight){
+ var rule = YAHOO.ext.util.CSS.getRule(["#" + this.grid.id + " .ygrid-row", ".ygrid-row"]);
+ if(rule && rule.style.height){
+ this.rowHeight = parseInt(rule.style.height, 10);
+ }else{
+ this.rowHeight = 21;
+ }
+ }
+ return this.rowHeight;
+ },
+
+ renderRows : function(dataModel){
+ this.grid.stopEditing();
+ if(this.grid.selModel){
+ this.grid.selModel.clearSelections();
+ }
+ var bt = this.getBodyTable();
+ bt.innerHTML = '';
+ this.rowHeight = this.getRowHeight();
+ this.insertRows(dataModel, 0, dataModel.getRowCount()-1);
+ },
+
+ updateCell : function(dataModel, rowIndex, dataIndex){
+ var colIndex = this.getColumnIndexByDataIndex(dataIndex);
+ if(typeof colIndex == 'undefined'){ // not present in grid
+ return;
+ }
+ var bt = this.getBodyTable();
+ var row = bt.childNodes[rowIndex];
+ var cell = row.childNodes[colIndex];
+ var renderer = this.grid.colModel.getRenderer(colIndex);
+ var val = renderer(dataModel.getValueAt(rowIndex, dataIndex), rowIndex, colIndex, cell, dataModel);
+ if(typeof val == 'undefined' || val === '') val = ' ';
+ cell.firstChild.innerHTML = val;
+ },
+
+ calcColumnWidth : function(colIndex, maxRowsToMeasure){
+ var maxWidth = 0;
+ var bt = this.getBodyTable();
+ var rows = bt.childNodes;
+ var stopIndex = Math.min(maxRowsToMeasure || rows.length, rows.length);
+ if(this.grid.autoSizeHeaders){
+ var h = this.headers[colIndex];
+ var curWidth = h.style.width;
+ h.style.width = this.grid.minColumnWidth+'px';
+ maxWidth = Math.max(maxWidth, h.scrollWidth);
+ h.style.width = curWidth;
+ }
+ for(var i = 0; i < stopIndex; i++){
+ var cell = rows[i].childNodes[colIndex].firstChild;
+ maxWidth = Math.max(maxWidth, cell.scrollWidth);
+ }
+ return maxWidth + /*margin for error in IE*/ 5;
+ },
+
+ /**
+ * Autofit a column to it's content.
+ * @param {Number} colIndex
+ * @param {Boolean} forceMinSize true to force the column to go smaller if possible
+ */
+ autoSizeColumn : function(colIndex, forceMinSize){
+ if(forceMinSize){
+ this.setCSSWidth(colIndex, this.grid.minColumnWidth);
+ }
+ var newWidth = this.calcColumnWidth(colIndex);
+ this.grid.colModel.setColumnWidth(colIndex,
+ Math.max(this.grid.minColumnWidth, newWidth));
+ this.grid.fireEvent('columnresize', colIndex, newWidth);
+ },
+
+ /**
+ * Autofits all columns to their content and then expands to fit any extra space in the grid
+ */
+ autoSizeColumns : function(){
+ var colModel = this.grid.colModel;
+ var colCount = colModel.getColumnCount();
+ var wrap = this.wrap;
+ for(var i = 0; i < colCount; i++){
+ this.setCSSWidth(i, this.grid.minColumnWidth);
+ colModel.setColumnWidth(i, this.calcColumnWidth(i, this.grid.maxRowsToMeasure), true);
+ }
+ if(colModel.getTotalWidth() < wrap.clientWidth){
+ var diff = Math.floor((wrap.clientWidth - colModel.getTotalWidth()) / colCount);
+ for(var i = 0; i < colCount; i++){
+ colModel.setColumnWidth(i, colModel.getColumnWidth(i) + diff, true);
+ }
+ }
+ this.updateColumns();
+ },
+
+ /**
+ * Autofits all columns to the grid's width proportionate with their current size
+ */
+ fitColumns : function(){
+ var cm = this.grid.colModel;
+ var colCount = cm.getColumnCount();
+ var cols = [];
+ var width = 0;
+ var i, w;
+ for (i = 0; i < colCount; i++){
+ if(!cm.isHidden(i) && !cm.isFixed(i)){
+ w = cm.getColumnWidth(i);
+ cols.push(i);
+ cols.push(w);
+ width += w;
+ }
+ }
+ var frac = (this.wrap.clientWidth - cm.getTotalWidth())/width;
+ while (cols.length){
+ w = cols.pop();
+ i = cols.pop();
+ cm.setColumnWidth(i, Math.floor(w + w*frac), true);
+ }
+ this.updateColumns();
+ },
+
+ onWindowResize : function(){
+ if(this.grid.monitorWindowResize){
+ this.adjustForScroll();
+ this.updateWrapHeight();
+ this.adjustForScroll();
+ }
+ },
+
+ updateWrapHeight : function(){
+ this.grid.container.beginMeasure();
+ this.autoHeight();
+ var box = this.grid.container.getSize(true);
+ this.wrapEl.setHeight(box.height-this.footerHeight-parseInt(this.wrap.offsetTop, 10));
+ this.pwrap.setSize(box.width, box.height);
+ this.grid.container.endMeasure();
+ },
+
+ forceScrollUpdate : function(){
+ var wrap = this.wrapEl;
+ wrap.setWidth(wrap.getWidth(true));
+ setTimeout(function(){ // set timeout so FireFox works
+ wrap.setWidth('');
+ }, 1);
+ },
+
+ updateHeaderSortState : function(){
+ var state = this.grid.dataModel.getSortState();
+ if(!state || typeof state.column == 'undefined') return;
+ var sortColumn = this.getColumnIndexByDataIndex(state.column);
+ var sortDir = state.direction;
+ for(var i = 0, len = this.headers.length; i < len; i++){
+ var h = this.headers[i];
+ if(i != sortColumn){
+ h.sortDesc.style.display = 'none';
+ h.sortAsc.style.display = 'none';
+ YAHOO.util.Dom.removeClass(h, 'ygrid-sort-col');
+ }else{
+ h.sortDesc.style.display = sortDir == 'DESC' ? 'block' : 'none';
+ h.sortAsc.style.display = sortDir == 'ASC' ? 'block' : 'none';
+ YAHOO.util.Dom.addClass(h, 'ygrid-sort-col');
+ }
+ }
+ },
+
+ unplugDataModel : function(dm){
+ dm.removeListener('cellupdated', this.updateCell, this);
+ dm.removeListener('datachanged', this.renderRows, this);
+ dm.removeListener('rowsdeleted', this.deleteRows, this);
+ dm.removeListener('rowsinserted', this.insertRows, this);
+ dm.removeListener('rowsupdated', this.updateRows, this);
+ dm.removeListener('rowssorted', this.handleSort, this);
+ },
+
+ plugDataModel : function(dm){
+ dm.on('cellupdated', this.updateCell, this, true);
+ dm.on('datachanged', this.renderRows, this, true);
+ dm.on('rowsdeleted', this.deleteRows, this, true);
+ dm.on('rowsinserted', this.insertRows, this, true);
+ dm.on('rowsupdated', this.updateRows, this, true);
+ dm.on('rowssorted', this.handleSort, this, true);
+ },
+
+ destroy : function(){
+ this.unplugDataModel(this.grid.dataModel);
+ var sp = this.splitters;
+ if(sp){
+ for(var i in sp){
+ if(sp[i] && typeof sp[i] != 'function'){
+ sp[i].destroy(true);
+ }
+ }
+ }
+ },
+
+ render : function(){
+ var grid = this.grid;
+ var container = grid.container.dom;
+ var dataModel = grid.dataModel;
+ this.plugDataModel(dataModel);
+
+ var colModel = grid.colModel;
+ colModel.onWidthChange.subscribe(this.updateColumns, this, true);
+ colModel.onHeaderChange.subscribe(this.updateHeaders, this, true);
+ colModel.onHiddenChange.subscribe(this.handleHiddenChange, this, true);
+
+ if(grid.monitorWindowResize === true){
+ YAHOO.ext.EventManager.onWindowResize(this.onWindowResize, this, true);
+ }
+ var autoSizeDelegate = this.autoSizeColumn.createDelegate(this);
+
+ var colCount = colModel.getColumnCount();
+
+ var dh = YAHOO.ext.DomHelper;
+ this.pwrap = dh.append(container,
+ {tag: 'div', cls: 'ygrid-positioner',
+ style: 'position:relative;width:100%;height:100%;left:0;top:0;overflow:hidden;'}, true);
+ var pos = this.pwrap.dom;
+
+ //create wrapper elements that handle offsets and scrolling
+ var wrap = dh.append(pos, {tag: 'div', cls: 'ygrid-wrap'});
+ this.wrap = wrap;
+ this.wrapEl = getEl(wrap, true);
+ YAHOO.ext.EventManager.on(wrap, 'scroll', this.handleScroll, this, true);
+
+ var hwrap = dh.append(pos, {tag: 'div', cls: 'ygrid-wrap-headers'});
+ this.hwrap = getEl(hwrap, true);
+
+ var bwrap = dh.append(wrap, {tag: 'div', cls: 'ygrid-wrap-body', id: container.id + '-body'});
+ this.bwrap = getEl(bwrap, true);
+ this.bwrap.setWidth(colModel.getTotalWidth());
+ bwrap.rows = bwrap.childNodes;
+
+ this.footerHeight = 0;
+ var foot = this.appendFooter(this.pwrap.dom);
+ if(foot){
+ this.footer = getEl(foot, true);
+ this.footerHeight = this.footer.getHeight();
+ }
+ this.updateWrapHeight();
+
+ var hrow = dh.append(hwrap, {tag: 'span', cls: 'ygrid-hrow'});
+ this.hrow = hrow;
+
+ if(!YAHOO.ext.util.Browser.isGecko){
+ // IE doesn't like iframes, we will leave this alone
+ var iframe = document.createElement('iframe');
+ iframe.className = 'ygrid-hrow-frame';
+ iframe.frameBorder = 0;
+ iframe.src = YAHOO.ext.SSL_SECURE_URL;
+ hwrap.appendChild(iframe);
+ }
+ this.headerCtrl = new YAHOO.ext.grid.HeaderController(this.grid);
+ this.headers = [];
+ this.cols = [];
+ this.splitters = [];
+
+ var htemplate = dh.createTemplate({
+ tag: 'span', cls: 'ygrid-hd ygrid-header-{0}', children: [{
+ tag: 'span',
+ cls: 'ygrid-hd-body',
+ html: '' +
+ '
'
+ }]
+ });
+ htemplate.compile();
+
+ var ruleBuf = [];
+
+ for(var i = 0; i < colCount; i++){
+ var hd = htemplate.append(hrow, [i, colModel.getColumnHeader(i), colModel.getColumnTooltip(i) || '']);
+ var spans = hd.getElementsByTagName('span');
+ hd.textNode = spans[1];
+ hd.sortDesc = spans[2];
+ hd.sortAsc = spans[3];
+ hd.columnIndex = i;
+ this.headers.push(hd);
+ if(colModel.isSortable(i)){
+ this.headerCtrl.register(hd);
+ }
+ var split = dh.append(hrow, {tag: 'span', cls: 'ygrid-hd-split'});
+ hd.split = split;
+
+ if(colModel.isResizable(i) && !colModel.isFixed(i)){
+ YAHOO.util.Event.on(split, 'dblclick', autoSizeDelegate.createCallback(i+0, true));
+ var sb = new YAHOO.ext.SplitBar(split, hd, null, YAHOO.ext.SplitBar.LEFT);
+ sb.columnIndex = i;
+ sb.minSize = grid.minColumnWidth;
+ sb.onMoved.subscribe(this.onColumnSplitterMoved, this, true);
+ YAHOO.util.Dom.addClass(sb.proxy, 'ygrid-column-sizer');
+ YAHOO.util.Dom.setStyle(sb.proxy, 'background-color', '');
+ sb.dd._resizeProxy = function(){
+ var el = this.getDragEl();
+ YAHOO.util.Dom.setStyle(el, 'height', (hwrap.clientHeight+wrap.clientHeight-2) +'px');
+ };
+ this.splitters[i] = sb;
+ }else{
+ split.style.cursor = 'default';
+ }
+ ruleBuf.push('#', container.id, ' .ygrid-col-', i, ' {\n}\n');
+ }
+
+ YAHOO.ext.util.CSS.createStyleSheet(ruleBuf.join(''));
+
+ if(grid.autoSizeColumns){
+ this.renderRows(dataModel);
+ this.autoSizeColumns();
+ }else{
+ this.updateColumns();
+ this.renderRows(dataModel);
+ }
+
+ for(var i = 0; i < colCount; i++){
+ if(colModel.isHidden(i)){
+ this.hideColumn(i);
+ }
+ }
+ this.updateHeaderSortState();
+ return this.bwrap;
+ },
+
+ onColumnSplitterMoved : function(splitter, newSize){
+ this.grid.colModel.setColumnWidth(splitter.columnIndex, newSize);
+ this.grid.fireEvent('columnresize', splitter.columnIndex, newSize);
+ },
+
+ appendFooter : function(parentEl){
+ return null;
+ },
+
+ autoHeight : function(){
+ if(this.grid.autoHeight){
+ var h = this.getBodyHeight();
+ var c = this.grid.container;
+ var total = h + (parseInt(this.wrap.offsetTop, 10)||0) +
+ this.footerHeight + c.getBorderWidth('tb') + c.getPadding('tb')
+ + (this.wrap.offsetHeight - this.wrap.clientHeight);
+ c.setHeight(total);
+
+ }
+ },
+
+ getBodyHeight : function(){
+ return this.grid.dataModel.getRowCount() * this.getRowHeight();;
+ },
+
+ updateBodyHeight : function(){
+ this.getBodyTable().style.height = this.getBodyHeight() + 'px';
+ if(this.grid.autoHeight){
+ this.autoHeight();
+ this.updateWrapHeight();
+ }
+ }
+};
+YAHOO.ext.grid.GridView.SCROLLBARS_UNDER = 0;
+YAHOO.ext.grid.GridView.SCROLLBARS_OVERLAP = 1;
+YAHOO.ext.grid.GridView.prototype.scrollbarMode = YAHOO.ext.grid.GridView.SCROLLBARS_UNDER;
+
+YAHOO.ext.grid.GridView.prototype.fitColumnsToContainer = YAHOO.ext.grid.GridView.prototype.fitColumns;
+
+YAHOO.ext.grid.HeaderController = function(grid){
+ this.grid = grid;
+ this.headers = [];
+};
+
+YAHOO.ext.grid.HeaderController.prototype = {
+ register : function(header){
+ this.headers.push(header);
+ YAHOO.ext.EventManager.on(header, 'selectstart', this.cancelTextSelection, this, true);
+ YAHOO.ext.EventManager.on(header, 'mousedown', this.cancelTextSelection, this, true);
+ YAHOO.ext.EventManager.on(header, 'mouseover', this.headerOver, this, true);
+ YAHOO.ext.EventManager.on(header, 'mouseout', this.headerOut, this, true);
+ YAHOO.ext.EventManager.on(header, 'click', this.headerClick, this, true);
+ },
+
+ headerClick : function(e){
+ var grid = this.grid, cm = grid.colModel, dm = grid.dataModel;
+ grid.stopEditing();
+ var header = grid.getHeaderFromChild(e.getTarget());
+ var state = dm.getSortState();
+ var direction = header.sortDir || 'ASC';
+ if(typeof state.column != 'undefined' &&
+ grid.getView().getColumnIndexByDataIndex(state.column) == header.columnIndex){
+ direction = (state.direction == 'ASC' ? 'DESC' : 'ASC');
+ }
+ header.sortDir = direction;
+ dm.sort(cm, cm.getDataIndex(header.columnIndex), direction);
+ },
+
+ headerOver : function(e){
+ var header = this.grid.getHeaderFromChild(e.getTarget());
+ YAHOO.util.Dom.addClass(header, 'ygrid-hd-over');
+ //YAHOO.ext.util.CSS.applyFirst(header, this.grid.id, '.ygrid-hd-over');
+ },
+
+ headerOut : function(e){
+ var header = this.grid.getHeaderFromChild(e.getTarget());
+ YAHOO.util.Dom.removeClass(header, 'ygrid-hd-over');
+ //YAHOO.ext.util.CSS.revertFirst(header, this.grid.id, '.ygrid-hd-over');
+ },
+
+ cancelTextSelection : function(e){
+ e.preventDefault();
+ }
+};
\ No newline at end of file
diff --git a/frontend/beta/js/YUI-extensions/grid/PagedGridView.js b/frontend/beta/js/YUI-extensions/grid/PagedGridView.js
new file mode 100644
index 0000000..ecaece2
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/PagedGridView.js
@@ -0,0 +1,194 @@
+/**
+ * @class YAHOO.ext.grid.PagedGridView
+ * @extends YAHOO.ext.grid.GridView
+ * Extends the default GridView to add a paging interface.
+ * @constructor
+ * This class is created for you automatically if your data model is set to use paging.
+ */
+YAHOO.ext.grid.PagedGridView = function(){
+ YAHOO.ext.grid.PagedGridView.superclass.constructor.call(this);
+ this.cursor = 1;
+};
+
+YAHOO.extendX(YAHOO.ext.grid.PagedGridView, YAHOO.ext.grid.GridView, {
+ appendFooter : function(parentEl){
+ var fwrap = document.createElement('div');
+ fwrap.className = 'ygrid-wrap-footer';
+ var fbody = document.createElement('span');
+ fbody.className = 'ygrid-footer';
+ fwrap.appendChild(fbody);
+ parentEl.appendChild(fwrap);
+ this.createPagingToolbar(fbody);
+ return fwrap;
+ },
+
+ createPagingToolbar : function(container){
+ var tb = new YAHOO.ext.Toolbar(container);
+ this.pageToolbar = tb;
+ this.first = tb.addButton({
+ tooltip: this.firstText,
+ className: 'ygrid-page-first',
+ disabled: true,
+ click: this.onClick.createDelegate(this, ['first'])
+ });
+ this.prev = tb.addButton({
+ tooltip: this.prevText,
+ className: 'ygrid-page-prev',
+ disabled: true,
+ click: this.onClick.createDelegate(this, ['prev'])
+ });
+ tb.addSeparator();
+ tb.add(this.beforePageText);
+ var pageBox = document.createElement('input');
+ pageBox.type = 'text';
+ pageBox.size = 3;
+ pageBox.value = '1';
+ pageBox.className = 'ygrid-page-number';
+ tb.add(pageBox);
+ this.field = getEl(pageBox, true);
+ this.field.mon('keydown', this.onEnter, this, true);
+ this.field.on('focus', function(){pageBox.select();});
+ this.afterTextEl = tb.addText(this.afterPageText.replace('%0', '1'));
+ this.field.setHeight(18);
+ tb.addSeparator();
+ this.next = tb.addButton({
+ tooltip: this.nextText,
+ className: 'ygrid-page-next',
+ disabled: true,
+ click: this.onClick.createDelegate(this, ['next'])
+ });
+ this.last = tb.addButton({
+ tooltip: this.lastText,
+ className: 'ygrid-page-last',
+ disabled: true,
+ click: this.onClick.createDelegate(this, ['last'])
+ });
+ tb.addSeparator();
+ this.loading = tb.addButton({
+ tooltip: this.refreshText,
+ className: 'ygrid-loading',
+ disabled: true,
+ click: this.onClick.createDelegate(this, ['refresh'])
+ });
+ this.onPageLoaded(1, this.grid.dataModel.getTotalPages());
+ },
+
+ /**
+ * Returns the toolbar used for paging so you can add new buttons.
+ * @return {YAHOO.ext.Toolbar}
+ */
+ getPageToolbar : function(){
+ return this.pageToolbar;
+ },
+
+ onPageLoaded : function(pageNum, totalPages){
+ this.cursor = pageNum;
+ this.lastPage = totalPages;
+ this.afterTextEl.innerHTML = this.afterPageText.replace('%0', totalPages);
+ this.field.dom.value = pageNum;
+ this.first.setDisabled(pageNum == 1);
+ this.prev.setDisabled(pageNum == 1);
+ this.next.setDisabled(pageNum == totalPages);
+ this.last.setDisabled(pageNum == totalPages);
+ this.loading.enable();
+ },
+
+ onLoadError : function(){
+ this.loading.enable();
+ },
+
+ onEnter : function(e){
+ if(e.browserEvent.keyCode == e.RETURN){
+ var v = this.field.dom.value;
+ if(!v){
+ this.field.dom.value = this.cursor;
+ return;
+ }
+ var pageNum = parseInt(v, 10);
+ if(isNaN(pageNum)){
+ this.field.dom.value = this.cursor;
+ return;
+ }
+ pageNum = Math.min(Math.max(1, pageNum), this.lastPage);
+ this.grid.dataModel.loadPage(pageNum);
+ e.stopEvent();
+ }
+ },
+
+ beforeLoad : function(){
+ this.grid.stopEditing();
+ if(this.loading){
+ this.loading.disable();
+ }
+ },
+
+ onClick : function(which){
+ switch(which){
+ case 'first':
+ this.grid.dataModel.loadPage(1);
+ break;
+ case 'prev':
+ this.grid.dataModel.loadPage(this.cursor -1);
+ break;
+ case 'next':
+ this.grid.dataModel.loadPage(this.cursor + 1);
+ break;
+ case 'last':
+ this.grid.dataModel.loadPage(this.lastPage);
+ break;
+ case 'refresh':
+ this.grid.dataModel.loadPage(this.cursor);
+ break;
+ }
+ },
+
+ unplugDataModel : function(dm){
+ dm.removeListener('beforeload', this.beforeLoad, this);
+ dm.removeListener('load', this.onPageLoaded, this);
+ dm.removeListener('loadexception', this.onLoadError, this);
+ YAHOO.ext.grid.PagedGridView.superclass.unplugDataModel.call(this, dm);
+ },
+
+ plugDataModel : function(dm){
+ dm.on('beforeload', this.beforeLoad, this, true);
+ dm.on('load', this.onPageLoaded, this, true);
+ dm.on('loadexception', this.onLoadError, this);
+ YAHOO.ext.grid.PagedGridView.superclass.plugDataModel.call(this, dm);
+ },
+
+ /**
+ * Customizable piece of the default paging text (defaults to "Page")
+ * @type String
+ */
+ beforePageText : "Page",
+ /**
+ * Customizable piece of the default paging text (defaults to "of %0")
+ * @type String
+ */
+ afterPageText : "of %0",
+ /**
+ * Customizable piece of the default paging text (defaults to "First Page")
+ * @type String
+ */
+ firstText : "First Page",
+ /**
+ * Customizable piece of the default paging text (defaults to "Previous Page")
+ * @type String
+ */
+ prevText : "Previous Page",
+ /**
+ * Customizable piece of the default paging text (defaults to "Next Page")
+ * @type String
+ */
+ nextText : "Next Page",
+ /**
+ * Customizable piece of the default paging text (defaults to "Last Page")
+ * @type String
+ */
+ lastText : "Last Page",
+ /**
+ * Customizable piece of the default paging text (defaults to "Refresh")
+ * @type String
+ */
+ refreshText : "Refresh"
+});
diff --git a/frontend/beta/js/YUI-extensions/grid/SelectionModel.js b/frontend/beta/js/YUI-extensions/grid/SelectionModel.js
new file mode 100644
index 0000000..6981440
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/SelectionModel.js
@@ -0,0 +1,445 @@
+/**
+ @class YAHOO.ext.grid.DefaultSelectionModel
+ * @extends YAHOO.ext.util.Observable
+ * The default SelectionModel used by {@link YAHOO.ext.grid.Grid}.
+ It supports multiple selections and keyboard selection/navigation. {1} ' +
+ '' +
+ '
+ @constructor
+ */
+YAHOO.ext.grid.DefaultSelectionModel = function(){
+ this.selectedRows = [];
+ this.selectedRowIds = [];
+ this.lastSelectedRow = null;
+
+ this.onRowSelect = new YAHOO.util.CustomEvent('SelectionTable.rowSelected');
+ this.onSelectionChange = new YAHOO.util.CustomEvent('SelectionTable.selectionChanged');
+
+ this.events = {
+ /**
+ * @event selectionchange
+ * Fires when the selection changes
+ * @param {SelectionModel} this
+ * @param {Array} rows Array of row elements that are selected
+ * @param {String} ids Array of ids that are selected
+ */
+ 'selectionchange' : this.onSelectionChange,
+ /**
+ * @event rowselect
+ * Fires when a row is selected or deselected
+ * @param {SelectionModel} this
+ * @param {HTMLElement} row The row element
+ * @param {Boolean} selected true if the row was selected, false if deselected
+ */
+ 'rowselect' : this.onRowSelect
+ };
+
+ this.locked = false;
+};
+
+YAHOO.ext.grid.DefaultSelectionModel.prototype = {
+ /** @ignore Called by the grid automatically. Do not call directly. */
+ init : function(grid){
+ this.grid = grid;
+ this.initEvents();
+ },
+
+ /**
+ * Lock the selections
+ */
+ lock : function(){
+ this.locked = true;
+ },
+
+ /**
+ * Unlock the selections
+ */
+ unlock : function(){
+ this.locked = false;
+ },
+
+ /**
+ * Returns true if the selections are locked
+ * @return {Boolean}
+ */
+ isLocked : function(){
+ return this.locked;
+ },
+
+ /** @ignore */
+ initEvents : function(){
+ if(this.grid.trackMouseOver){
+ this.grid.addListener("mouseover", this.handleOver, this, true);
+ this.grid.addListener("mouseout", this.handleOut, this, true);
+ }
+ this.grid.addListener("rowclick", this.rowClick, this, true);
+ this.grid.addListener("keydown", this.keyDown, this, true);
+ },
+
+ fireEvent : YAHOO.ext.util.Observable.prototype.fireEvent,
+ on : YAHOO.ext.util.Observable.prototype.on,
+ addListener : YAHOO.ext.util.Observable.prototype.addListener,
+ delayedListener : YAHOO.ext.util.Observable.prototype.delayedListener,
+ removeListener : YAHOO.ext.util.Observable.prototype.removeListener,
+ purgeListeners : YAHOO.ext.util.Observable.prototype.purgeListeners,
+ bufferedListener : YAHOO.ext.util.Observable.prototype.bufferedListener,
+
+ /** @ignore Syncs selectedRows with the correct row by looking it up by id.
+ Used after a sort moves data around. */
+ syncSelectionsToIds : function(){
+ if(this.getCount() > 0){
+ var ids = this.selectedRowIds.concat();
+ this.clearSelections();
+ this.selectRowsById(ids, true);
+ }
+ },
+
+ /**
+ * Set the selected rows by their ID(s). IDs must match what is returned by the DataModel getRowId(index).
+ * @param {String/Array} id The id(s) to select
+ * @param {Boolean} keepExisting (optional) True to retain existing selections
+ */
+ selectRowsById : function(id, keepExisting){
+ var rows = this.grid.getRowsById(id);
+ if (!(rows instanceof Array)){
+ this.selectRow(rows, keepExisting);
+ return;
+ }
+ this.selectRows(rows, keepExisting);
+ },
+
+ /**
+ * Gets the number of selected rows.
+ * @return {Number}
+ */
+ getCount : function(){
+ return this.selectedRows.length;
+ },
+
+ /**
+ * Selects the first row in the grid.
+ */
+ selectFirstRow : function(){
+ for(var j = 0; j < this.grid.rows.length; j++){
+ if(this.isSelectable(this.grid.rows[j])){
+ this.focusRow(this.grid.rows[j]);
+ this.setRowState(this.grid.rows[j], true);
+ return;
+ }
+ }
+ },
+
+ /**
+ * Selects the row immediately following the last selected row.
+ * @param {Boolean} keepExisting (optional) True to retain existing selections
+ */
+ selectNext : function(keepExisting){
+ if(this.lastSelectedRow){
+ for(var j = (this.lastSelectedRow.rowIndex+1); j < this.grid.rows.length; j++){
+ var row = this.grid.rows[j];
+ if(this.isSelectable(row)){
+ this.focusRow(row);
+ this.setRowState(row, true, keepExisting);
+ return;
+ }
+ }
+ }
+ },
+
+ /**
+ * Selects the row that precedes the last selected row.
+ * @param {Boolean} keepExisting (optional) True to retain existing selections
+ */
+ selectPrevious : function(keepExisting){
+ if(this.lastSelectedRow){
+ for(var j = (this.lastSelectedRow.rowIndex-1); j >= 0; j--){
+ var row = this.grid.rows[j];
+ if(this.isSelectable(row)){
+ this.focusRow(row);
+ this.setRowState(row, true, keepExisting);
+ return;
+ }
+ }
+ }
+ },
+
+ /**
+ * Returns the selected rows.
+ * @return {Array} Array of DOM row elements
+ */
+ getSelectedRows : function(){
+ return this.selectedRows;
+ },
+
+ /**
+ * Returns the selected row ids.
+ * @return {Array} Array of String ids
+ */
+ getSelectedRowIds : function(){
+ return this.selectedRowIds;
+ },
+
+ /**
+ * Clears all selections.
+ */
+ clearSelections : function(){
+ if(this.isLocked()) return;
+ var oldSelections = this.selectedRows.concat();
+ for(var j = 0; j < oldSelections.length; j++){
+ this.setRowState(oldSelections[j], false);
+ }
+ this.selectedRows = [];
+ this.selectedRowIds = [];
+ },
+
+
+ /**
+ * Selects all rows.
+ */
+ selectAll : function(){
+ if(this.isLocked()) return;
+ this.selectedRows = [];
+ this.selectedRowIds = [];
+ for(var j = 0, len = this.grid.rows.length; j < len; j++){
+ this.setRowState(this.grid.rows[j], true, true);
+ }
+ },
+
+ /**
+ * Returns True if there is a selection.
+ * @return {Boolean}
+ */
+ hasSelection : function(){
+ return this.selectedRows.length > 0;
+ },
+
+ /**
+ * Returns True if the specified row is selected.
+ * @param {HTMLElement} row The row to check
+ * @return {Boolean}
+ */
+ isSelected : function(row){
+ return row && (row.selected === true || row.getAttribute('selected') == 'true');
+ },
+
+ /**
+ * Returns True if the specified row is selectable.
+ * @param {HTMLElement} row The row to check
+ * @return {Boolean}
+ */
+ isSelectable : function(row){
+ return row && row.getAttribute('selectable') != 'false';
+ },
+
+ /** @ignore */
+ rowClick : function(grid, rowIndex, e){
+ if(this.isLocked()) return;
+ var row = grid.getRow(rowIndex);
+ if(this.isSelectable(row)){
+ if(e.shiftKey && this.lastSelectedRow){
+ var lastIndex = this.lastSelectedRow.rowIndex;
+ this.selectRange(this.lastSelectedRow, row, e.ctrlKey);
+ this.lastSelectedRow = this.grid.el.dom.rows[lastIndex];
+ }else{
+ this.focusRow(row);
+ var rowState = e.ctrlKey ? !this.isSelected(row) : true;
+ this.setRowState(row, rowState, e.hasModifier());
+ }
+ }
+ },
+
+ /**
+ * Deprecated. Tries to focus the row and scroll it into view - Use grid.scrollTo or grid.getView().focusRow() instead.
+ * @deprecated
+ * @param {HTMLElement} row The row to focus
+ */
+ focusRow : function(row){
+ this.grid.view.focusRow(row);
+ },
+
+ /**
+ * Selects a row.
+ * @param {Number/HTMLElement} row The row or index of the row to select
+ * @param {Boolean} keepExisting (optional) True to retain existing selections
+ */
+ selectRow : function(row, keepExisting){
+ this.setRowState(this.getRow(row), true, keepExisting);
+ },
+
+ /**
+ * Selects multiple rows.
+ * @param {Array} rows Array of the rows or indexes of the row to select
+ * @param {Boolean} keepExisting (optional) True to retain existing selections
+ */
+ selectRows : function(rows, keepExisting){
+ if(!keepExisting){
+ this.clearSelections();
+ }
+ for(var i = 0; i < rows.length; i++){
+ this.selectRow(rows[i], true);
+ }
+ },
+
+ /**
+ * Deselects a row.
+ * @param {Number/HTMLElement} row The row or index of the row to deselect
+ */
+ deselectRow : function(row){
+ this.setRowState(this.getRow(row), false);
+ },
+
+ /** @ignore */
+ getRow : function(row){
+ if(typeof row == 'number'){
+ row = this.grid.rows[row];
+ }
+ return row;
+ },
+
+ /**
+ * Selects a range of rows. All rows in between startRow and endRow are also selected.
+ * @param {Number/HTMLElement} startRow The row or index of the first row in the range
+ * @param {Number/HTMLElement} endRow The row or index of the last row in the range
+ * @param {Boolean} keepExisting (optional) True to retain existing selections
+ */
+ selectRange : function(startRow, endRow, keepExisting){
+ startRow = this.getRow(startRow);
+ endRow = this.getRow(endRow);
+ this.setRangeState(startRow, endRow, true, keepExisting);
+ },
+
+ /**
+ * Deselects a range of rows. All rows in between startRow and endRow are also deselected.
+ * @param {Number/HTMLElement} startRow The row or index of the first row in the range
+ * @param {Number/HTMLElement} endRow The row or index of the last row in the range
+ */
+ deselectRange : function(startRow, endRow){
+ startRow = this.getRow(startRow);
+ endRow = this.getRow(endRow);
+ this.setRangeState(startRow, endRow, false, true);
+ },
+
+ /** @ignore */
+ setRowStateFromChild : function(childEl, selected, keepExisting){
+ var row = this.grid.getRowFromChild(childEl);
+ this.setRowState(row, selected, keepExisting);
+ },
+
+ /** @ignore */
+ setRangeState : function(startRow, endRow, selected, keepExisting){
+ if(this.isLocked()) return;
+ if(!keepExisting){
+ this.clearSelections();
+ }
+ var curRow = startRow;
+ while(curRow.rowIndex != endRow.rowIndex){
+ this.setRowState(curRow, selected, true);
+ curRow = (startRow.rowIndex < endRow.rowIndex ?
+ this.grid.getRowAfter(curRow) : this.grid.getRowBefore(curRow))
+ }
+ this.setRowState(endRow, selected, true);
+ },
+
+ /** @ignore */
+ setRowState : function(row, selected, keepExisting){
+ if(this.isLocked()) return;
+ if(this.isSelectable(row)){
+ if(selected){
+ if(!keepExisting){
+ this.clearSelections();
+ }
+ this.setRowClass(row, 'selected');
+ row.selected = true;
+ this.selectedRows.push(row);
+ this.selectedRowIds.push(this.grid.dataModel.getRowId(row.rowIndex));
+ this.lastSelectedRow = row;
+ }else{
+ this.setRowClass(row, '');
+ row.selected = false;
+ this._removeSelected(row);
+ }
+ this.fireEvent('rowselect', this, row, selected);
+ this.fireEvent('selectionchange', this, this.selectedRows, this.selectedRowIds);
+ }
+ },
+
+ /** @ignore */
+ handleOver : function(e){
+ var row = this.grid.getRowFromChild(e.getTarget());
+ if(this.isSelectable(row) && !this.isSelected(row)){
+ this.setRowClass(row, 'over');
+ }
+ },
+
+ /** @ignore */
+ handleOut : function(e){
+ var row = this.grid.getRowFromChild(e.getTarget());
+ if(this.isSelectable(row) && !this.isSelected(row)){
+ this.setRowClass(row, '');
+ }
+ },
+
+ /** @ignore */
+ keyDown : function(e){
+ if(e.browserEvent.keyCode == e.DOWN){
+ this.selectNext(e.shiftKey);
+ e.preventDefault();
+ }else if(e.browserEvent.keyCode == e.UP){
+ this.selectPrevious(e.shiftKey);
+ e.preventDefault();
+ }
+ },
+
+ /** @ignore */
+ setRowClass : function(row, cssClass){
+ if(this.isSelectable(row)){
+ if(cssClass == 'selected'){
+ YAHOO.util.Dom.removeClass(row, 'ygrid-row-over');
+ YAHOO.util.Dom.addClass(row, 'ygrid-row-selected');
+ }else if(cssClass == 'over'){
+ YAHOO.util.Dom.removeClass(row, 'ygrid-row-selected');
+ YAHOO.util.Dom.addClass(row, 'ygrid-row-over');
+ }else if(cssClass == ''){
+ YAHOO.util.Dom.removeClass(row, 'ygrid-row-selected');
+ YAHOO.util.Dom.removeClass(row, 'ygrid-row-over');
+ }
+ }
+ },
+
+ /** @ignore */
+ _removeSelected : function(row){
+ var sr = this.selectedRows;
+ for (var i = 0; i < sr.length; i++) {
+ if (sr[i] === row){
+ this.selectedRows.splice(i, 1);
+ this.selectedRowIds.splice(i, 1);
+ return;
+ }
+ }
+ }
+};
+
+/**
+ @class YAHOO.ext.grid.SingleSelectionModel
+ @extends YAHOO.ext.grid.DefaultSelectionModel
+ Allows only one row to be selected at a time.
+ @constructor
+ * Create new SingleSelectionModel
+ */
+YAHOO.ext.grid.SingleSelectionModel = function(){
+ YAHOO.ext.grid.SingleSelectionModel.superclass.constructor.call(this);
+};
+
+YAHOO.extendX(YAHOO.ext.grid.SingleSelectionModel, YAHOO.ext.grid.DefaultSelectionModel);
+
+/** @ignore */
+YAHOO.ext.grid.SingleSelectionModel.prototype.setRowState = function(row, selected){
+ YAHOO.ext.grid.SingleSelectionModel.superclass.setRowState.call(this, row, selected, false);
+};
+
+YAHOO.ext.grid.DisableSelectionModel = function(){
+ YAHOO.ext.grid.DisableSelectionModel.superclass.constructor.call(this);
+};
+
+YAHOO.extendX(YAHOO.ext.grid.DisableSelectionModel, YAHOO.ext.grid.DefaultSelectionModel);
+
+YAHOO.ext.grid.DisableSelectionModel.prototype.initEvents = function(){
+};
diff --git a/frontend/beta/js/YUI-extensions/grid/editor/CellEditor.js b/frontend/beta/js/YUI-extensions/grid/editor/CellEditor.js
new file mode 100644
index 0000000..7c51a48
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/editor/CellEditor.js
@@ -0,0 +1,91 @@
+/**
+ * @class YAHOO.ext.grid.CellEditor
+ * Base class for all EditorGrid editors
+ */
+YAHOO.ext.grid.CellEditor = function(element){
+ this.colIndex = null;
+ this.rowIndex = null;
+ this.grid = null;
+ this.editing = false;
+ this.originalValue = null;
+ this.element = getEl(element, true);
+ this.element.addClass('ygrid-editor');
+ this.element.dom.tabIndex = 1;
+ this.initialized = false;
+ this.callback = null;
+};
+
+YAHOO.ext.grid.CellEditor.prototype = {
+ init : function(grid, bodyElement, callback){
+ // there's no way for the grid to know if multiple columns
+ // share the same editor so it will try to initialize the
+ // same one over and over
+ if(this.initialized) return;
+ this.initialized = true;
+ this.callback = callback;
+ this.grid = grid;
+ bodyElement.appendChild(this.element.dom);
+ this.initEvents();
+ },
+
+ initEvents : function(){
+ var stopOnEnter = function(e){
+ if(e.browserEvent.keyCode == e.RETURN){
+ this.stopEditing(true);
+ }else if(e.browserEvent.keyCode == e.ESC){
+ this.setValue(this.originalValue);
+ this.stopEditing(true);
+ }
+ }
+ this.element.mon('keydown', stopOnEnter, this, true);
+ this.element.on('blur', this.stopEditing, this, true);
+ },
+
+ startEditing : function(value, row, cell){
+ this.originalValue = value;
+ this.rowIndex = row.rowIndex;
+ this.colIndex = cell.columnIndex;
+ this.cell = cell;
+ this.setValue(value);
+ var cellbox = getEl(cell, true).getBox();
+ this.fitToCell(cellbox);
+ this.editing = true;
+ this.show();
+ },
+
+ stopEditing : function(focusCell){
+ if(this.editing){
+ this.editing = false;
+ var newValue = this.getValue();
+ this.hide();
+ //if(focusCell){try{this.cell.focus();}catch(e){}}; // try to give the cell focus so keyboard nav still works
+ if(this.originalValue != newValue){
+ this.callback(newValue, this.rowIndex, this.colIndex);
+ }
+ }
+ },
+
+ setValue : function(value){
+ this.element.dom.value = value;
+ },
+
+ getValue : function(){
+ return this.element.dom.value;
+ },
+
+ fitToCell : function(box){
+ this.element.setBox(box, true);
+ },
+
+ show : function(){
+ this.element.show();
+ this.element.focus();
+ },
+
+ hide : function(){
+ try{
+ this.element.dom.blur();
+ }catch(e){}
+ this.element.hide();
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/grid/editor/CheckboxEditor.js b/frontend/beta/js/YUI-extensions/grid/editor/CheckboxEditor.js
new file mode 100644
index 0000000..681b847
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/editor/CheckboxEditor.js
@@ -0,0 +1,60 @@
+/**
+ * @class YAHOO.ext.grid.CheckboxEditor
+ * @extends YAHOO.ext.grid.CellEditor
+Provides a checkbox for editing boolean values. It currently has no configuration options.
+For more information on using this editor, see this blog post.
+* @constructor
+* Create a new CheckboxEditor
+ */
+YAHOO.ext.grid.CheckboxEditor = function(){
+ var div = document.createElement('span');
+ div.className = 'ygrid-editor ygrid-checkbox-editor';
+ var cb = document.createElement('input');
+ cb.type = 'checkbox';
+ cb.setAttribute('autocomplete', 'off');
+ div.appendChild(cb);
+ document.body.appendChild(div);
+ YAHOO.ext.grid.CheckboxEditor.superclass.constructor.call(this, div);
+ div.tabIndex = '';
+ cb.tabIndex = 1;
+ this.cb = getEl(cb, true);
+};
+
+YAHOO.extendX(YAHOO.ext.grid.CheckboxEditor, YAHOO.ext.grid.CellEditor);
+
+YAHOO.ext.grid.CheckboxEditor.prototype.fitToCell = function(box){
+ this.element.setBox(box, true);
+};
+
+YAHOO.ext.grid.CheckboxEditor.prototype.setValue = function(value){
+ this.cb.dom.checked = (value === true || value === 'true' || value === 1 || value === '1');
+};
+
+YAHOO.ext.grid.CheckboxEditor.prototype.getValue = function(){
+ return this.cb.dom.checked;
+};
+
+YAHOO.ext.grid.CheckboxEditor.prototype.show = function(){
+ this.element.show();
+ this.cb.focus();
+};
+
+YAHOO.ext.grid.CheckboxEditor.prototype.initEvents = function(){
+ var stopOnEnter = function(e){
+ if(e.browserEvent.keyCode == e.RETURN){
+ this.stopEditing(true);
+ }else if(e.browserEvent.keyCode == e.ESC){
+ this.setValue(this.originalValue);
+ this.stopEditing(true);
+ }
+ }
+ this.cb.mon('keydown', stopOnEnter, this, true);
+ this.cb.on('blur', this.stopEditing, this, true);
+};
+
+YAHOO.ext.grid.CheckboxEditor.prototype.hide = function(){
+ try{
+ this.cb.dom.blur();
+ }catch(e){}
+ this.element.hide();
+};
diff --git a/frontend/beta/js/YUI-extensions/grid/editor/DateEditor.js b/frontend/beta/js/YUI-extensions/grid/editor/DateEditor.js
new file mode 100644
index 0000000..303ad2b
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/editor/DateEditor.js
@@ -0,0 +1,268 @@
+/**
+ * @class YAHOO.ext.grid.DateEditor
+ * @extends YAHOO.ext.grid.CellEditor
+Provides a date editor field, and optionally a DatePicker. The DateEditor provides a method to override (showCalendar) if you don't want to use the built in DatePicker control. The reason I chose to use my own DatePicker control rather than the nice YUI Calendar component is my control was very easy to override events to make it work well with the grid. It's also only 5k compressed, while the YUI Calendar is 40k compressed. The DatePicker supports left/right keys to move months, up/down keys to move years and the mouse wheel to quickly go through the months. The DateEditor supports the following configuration options:
+
+
+For more information on using this editor, see this blog post.
+* @constructor
+* Create a new DateEditor
+* @param {Object} config
+ */
+YAHOO.ext.grid.DateEditor = function(config){
+ var div = document.createElement('span');
+ div.className = 'ygrid-editor ygrid-editor-container';
+
+ var element = document.createElement('input');
+ element.type = 'text';
+ element.tabIndex = 1;
+ element.setAttribute('autocomplete', 'off');
+ div.appendChild(element);
+
+ var pick = document.createElement('span');
+ pick.className = 'pick-button';
+ div.appendChild(pick);
+
+ document.body.appendChild(div);
+
+ this.div = getEl(div, true);
+ this.element = getEl(element, true);
+ this.pick = getEl(pick, true);
+
+ this.colIndex = null;
+ this.rowIndex = null;
+ this.grid = null;
+ this.editing = false;
+ this.originalValue = null;
+ this.initialized = false;
+ this.callback = null;
+
+ this.cal = null;
+ this.mouseDownHandler = YAHOO.ext.EventManager.wrap(this.handleMouseDown, this, true);
+
+ YAHOO.ext.util.Config.apply(this, config);
+ if(typeof this.minValue == 'string') this.minValue = this.parseDate(this.minValue);
+ if(typeof this.maxValue == 'string') this.maxValue = this.parseDate(this.maxValue);
+ this.ddMatch = /ddnone/;
+ if(this.disabledDates){
+ var dd = this.disabledDates;
+ var re = "(?:";
+ for(var i = 0; i < dd.length; i++){
+ re += dd[i];
+ if(i != dd.length-1) re += "|";
+ }
+ this.ddMatch = new RegExp(re + ")");
+ }
+};
+
+YAHOO.ext.grid.DateEditor.prototype = {
+ init : function(grid, bodyElement, callback){
+ if(this.initialized) return;
+
+ this.initialized = true;
+ this.callback = callback;
+ this.grid = grid;
+ bodyElement.appendChild(this.div.dom);
+ this.initEvents();
+ },
+
+ initEvents : function(){
+ var stopOnEnter = function(e){
+ if(e.browserEvent.keyCode == e.RETURN){
+ this.stopEditing(true);
+ }else if(e.browserEvent.keyCode == e.ESC){
+ this.setValue(this.originalValue);
+ this.stopEditing(true);
+ }
+ }
+ this.element.mon('keydown', stopOnEnter, this, true);
+ var vtask = new YAHOO.ext.util.DelayedTask(this.validate, this);
+ this.element.mon('keyup', vtask.delay.createDelegate(vtask, [this.validationDelay]));
+ this.pick.on('click', this.showCalendar, this, true);
+ },
+
+ startEditing : function(value, row, cell){
+ this.originalValue = value;
+ this.rowIndex = row.rowIndex;
+ this.colIndex = cell.columnIndex;
+ this.cell = cell;
+ this.setValue(value);
+ this.validate();
+ var cellbox = getEl(cell, true).getBox();
+ this.div.setBox(cellbox, true);
+ this.element.setWidth(cellbox.width-this.pick.getWidth());
+ this.editing = true;
+ YAHOO.util.Event.on(document, "mousedown", this.mouseDownHandler);
+ this.show();
+ },
+
+ stopEditing : function(focusCell){
+ if(this.editing){
+ YAHOO.util.Event.removeListener(document, "mousedown", this.mouseDownHandler);
+ this.editing = false;
+ var newValue = this.getValue();
+ this.hide();
+ //if(focusCell){try{this.cell.focus();}catch(e){}}// try to give the cell focus so keyboard nav still works
+ if(this.originalValue != newValue){
+ this.callback(newValue, this.rowIndex, this.colIndex);
+ }
+ }
+ },
+
+ setValue : function(value){
+ this.element.dom.value = this.formatDate(value);
+ this.validate();
+ },
+
+ getValue : function(){
+ if(!this.validate()){
+ return this.originalValue;
+ }else{
+ var value = this.element.dom.value;
+ if(value.length < 1){
+ return value;
+ } else{
+ return this.parseDate(value);
+ }
+ }
+ },
+
+ show : function() {
+ this.div.show();
+ this.element.focus();
+ this.validate();
+ },
+
+ hide : function(){
+ try{
+ this.element.dom.blur();
+ }catch(e){}
+ this.div.hide();
+ },
+
+ validate : function(){
+ var dom = this.element.dom;
+ var value = dom.value;
+ if(value.length < 1){ // if it's blank
+ if(this.allowBlank){
+ dom.title = '';
+ this.element.removeClass('ygrid-editor-invalid');
+ return true;
+ }else{
+ dom.title = this.blankText;
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ }
+ value = this.parseDate(value);
+ if(!value){
+ dom.title = this.invalidText.replace('%0', dom.value).replace('%1', this.format);
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ var time = value.getTime();
+ if(this.minValue && time < this.minValue.getTime()){
+ dom.title = this.minText.replace('%0', this.formatDate(this.minValue));
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ if(this.maxValue && time > this.maxValue.getTime()){
+ dom.title = this.maxText.replace('%0', this.formatDate(this.maxValue));
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ if(this.disabledDays){
+ var day = value.getDay();
+ for(var i = 0; i < this.disabledDays.length; i++) {
+ if(day === this.disabledDays[i]){
+ dom.title = this.disabledDaysText;
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ }
+ }
+ var fvalue = this.formatDate(value);
+ if(this.ddMatch.test(fvalue)){
+ dom.title = this.disabledDatesText.replace('%0', fvalue);
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ var msg = this.validator(value);
+ if(msg !== true){
+ dom.title = msg;
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ dom.title = '';
+ this.element.removeClass('ygrid-editor-invalid');
+ return true;
+ },
+
+ handleMouseDown : function(e){
+ var t = e.getTarget();
+ var dom = this.div.dom;
+ if(t != dom && !YAHOO.util.Dom.isAncestor(dom, t)){
+ this.stopEditing();
+ }
+ },
+
+ showCalendar : function(value){
+ if(this.cal == null){
+ this.cal = new YAHOO.ext.DatePicker(this.div.dom.parentNode.parentNode);
+ }
+ this.cal.minDate = this.minValue;
+ this.cal.maxDate = this.maxValue;
+ this.cal.disabledDatesRE = this.ddMatch;
+ this.cal.disabledDatesText = this.disabledDatesText;
+ this.cal.disabledDays = this.disabledDays;
+ this.cal.disabledDaysText = this.disabledDaysText;
+ this.cal.format = this.format;
+ if(this.minValue){
+ this.cal.minText = this.minText.replace('%0', this.formatDate(this.minValue));
+ }
+ if(this.maxValue){
+ this.cal.maxText = this.maxText.replace('%0', this.formatDate(this.maxValue));
+ }
+ var r = this.div.getRegion();
+ this.cal.show(r.left, r.bottom, this.getValue(), this.setValue.createDelegate(this));
+ },
+
+ parseDate : function(value){
+ if(!value || value instanceof Date) return value;
+ return Date.parseDate(value, this.format);
+ },
+
+ formatDate : function(date){
+ if(!date || !(date instanceof Date)) return date;
+ return date.format(this.format);
+ }
+};
+
+YAHOO.ext.grid.DateEditor.prototype.format = 'm/d/y';
+YAHOO.ext.grid.DateEditor.prototype.disabledDays = null;
+YAHOO.ext.grid.DateEditor.prototype.disabledDaysText = '';
+YAHOO.ext.grid.DateEditor.prototype.disabledDates = null;
+YAHOO.ext.grid.DateEditor.prototype.disabledDatesText = '';
+YAHOO.ext.grid.DateEditor.prototype.allowBlank = true;
+YAHOO.ext.grid.DateEditor.prototype.minValue = null;
+YAHOO.ext.grid.DateEditor.prototype.maxValue = null;
+YAHOO.ext.grid.DateEditor.prototype.minText = 'The date in this field must be after %0';
+YAHOO.ext.grid.DateEditor.prototype.maxText = 'The date in this field must be before %0';
+YAHOO.ext.grid.DateEditor.prototype.blankText = 'This field cannot be blank';
+YAHOO.ext.grid.DateEditor.prototype.invalidText = '%0 is not a valid date - it must be in the format %1';
+YAHOO.ext.grid.DateEditor.prototype.validationDelay = 200;
+YAHOO.ext.grid.DateEditor.prototype.validator = function(){return true;};
diff --git a/frontend/beta/js/YUI-extensions/grid/editor/NumberEditor.js b/frontend/beta/js/YUI-extensions/grid/editor/NumberEditor.js
new file mode 100644
index 0000000..f74d3d9
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/editor/NumberEditor.js
@@ -0,0 +1,166 @@
+/**
+ * @class YAHOO.ext.grid.NumberEditor
+ * @extends YAHOO.ext.grid.CellEditor
+Provides a masked editor for numeric values. Invalid keys are ignored. It supports the following configuration options:
+
+
+For more information on using this editor, see this blog post.
+* @constructor
+* Create a new NumberEditor
+* @param {Object} config
+ */
+YAHOO.ext.grid.NumberEditor = function(config){
+ var element = document.createElement('input');
+ element.type = 'text';
+ element.className = 'ygrid-editor ygrid-num-editor';
+ element.setAttribute('autocomplete', 'off');
+ document.body.appendChild(element);
+ YAHOO.ext.grid.NumberEditor.superclass.constructor.call(this, element);
+ YAHOO.ext.util.Config.apply(this, config);
+};
+YAHOO.extendX(YAHOO.ext.grid.NumberEditor, YAHOO.ext.grid.CellEditor);
+
+YAHOO.ext.grid.NumberEditor.prototype.initEvents = function(){
+ var stopOnEnter = function(e){
+ if(e.browserEvent.keyCode == e.RETURN){
+ this.stopEditing(true);
+ }else if(e.browserEvent.keyCode == e.ESC){
+ this.setValue(this.originalValue);
+ this.stopEditing(true);
+ }
+ };
+
+ var allowed = "0123456789";
+ if(this.allowDecimals){
+ allowed += this.decimalSeparator;
+ }
+ if(this.allowNegative){
+ allowed += '-';
+ }
+ var keyPress = function(e){
+ var c = e.getCharCode();
+ if(c != e.BACKSPACE && allowed.indexOf(String.fromCharCode(c)) === -1){
+ e.stopEvent();
+ }
+ };
+ this.element.mon('keydown', stopOnEnter, this, true);
+ var vtask = new YAHOO.ext.util.DelayedTask(this.validate, this);
+ this.element.mon('keyup', vtask.delay.createDelegate(vtask, [this.validationDelay]));
+ this.element.mon('keypress', keyPress, this, true);
+ this.element.on('blur', this.stopEditing, this, true);
+};
+
+YAHOO.ext.grid.NumberEditor.prototype.validate = function(){
+ var dom = this.element.dom;
+ var value = dom.value;
+ if(value.length < 1){ // if it's blank
+ if(this.allowBlank){
+ dom.title = '';
+ this.element.removeClass('ygrid-editor-invalid');
+ return true;
+ }else{
+ dom.title = this.blankText;
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ }
+ if(value.search(/\d+/) === -1){
+ dom.title = this.nanText.replace('%0', value);
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ var num = this.parseValue(value);
+ if(num < this.minValue){
+ dom.title = this.minText.replace('%0', this.minValue);
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ if(num > this.maxValue){
+ dom.title = this.maxText.replace('%0', this.maxValue);
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ var msg = this.validator(value);
+ if(msg !== true){
+ dom.title = msg;
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ dom.title = '';
+ this.element.removeClass('ygrid-editor-invalid');
+ return true;
+};
+
+YAHOO.ext.grid.NumberEditor.prototype.show = function(){
+ this.element.dom.title = '';
+ YAHOO.ext.grid.NumberEditor.superclass.show.call(this);
+ if(this.selectOnFocus){
+ try{
+ this.element.dom.select();
+ }catch(e){}
+ }
+ this.validate(this.element.dom.value);
+};
+
+YAHOO.ext.grid.NumberEditor.prototype.getValue = function(){
+ if(!this.validate()){
+ return this.originalValue;
+ }else{
+ var value = this.element.dom.value;
+ if(value.length < 1){
+ return value;
+ } else{
+ return this.fixPrecision(this.parseValue(value));
+ }
+ }
+};
+YAHOO.ext.grid.NumberEditor.prototype.parseValue = function(value){
+ return parseFloat(new String(value).replace(this.decimalSeparator, '.'));
+};
+
+YAHOO.ext.grid.NumberEditor.prototype.fixPrecision = function(value){
+ if(!this.allowDecimals || this.decimalPrecision == -1 || isNaN(value) || value == 0 || !value){
+ return value;
+ }
+ // this should work but doesn't due to precision error in JS
+ // var scale = Math.pow(10, this.decimalPrecision);
+ // var fixed = this.decimalPrecisionFcn(value * scale);
+ // return fixed / scale;
+ //
+ // so here's our workaround:
+ var scale = Math.pow(10, this.decimalPrecision+1);
+ var fixed = this.decimalPrecisionFcn(value * scale);
+ fixed = this.decimalPrecisionFcn(fixed/10);
+ return fixed / (scale/10);
+};
+
+YAHOO.ext.grid.NumberEditor.prototype.allowBlank = true;
+YAHOO.ext.grid.NumberEditor.prototype.allowDecimals = true;
+YAHOO.ext.grid.NumberEditor.prototype.decimalSeparator = '.';
+YAHOO.ext.grid.NumberEditor.prototype.decimalPrecision = 2;
+YAHOO.ext.grid.NumberEditor.prototype.decimalPrecisionFcn = Math.floor;
+YAHOO.ext.grid.NumberEditor.prototype.allowNegative = true;
+YAHOO.ext.grid.NumberEditor.prototype.selectOnFocus = true;
+YAHOO.ext.grid.NumberEditor.prototype.minValue = Number.NEGATIVE_INFINITY;
+YAHOO.ext.grid.NumberEditor.prototype.maxValue = Number.MAX_VALUE;
+YAHOO.ext.grid.NumberEditor.prototype.minText = 'The minimum value for this field is %0';
+YAHOO.ext.grid.NumberEditor.prototype.maxText = 'The maximum value for this field is %0';
+YAHOO.ext.grid.NumberEditor.prototype.blankText = 'This field cannot be blank';
+YAHOO.ext.grid.NumberEditor.prototype.nanText = '%0 is not a valid number';
+YAHOO.ext.grid.NumberEditor.prototype.validationDelay = 100;
+YAHOO.ext.grid.NumberEditor.prototype.validator = function(){return true;};
diff --git a/frontend/beta/js/YUI-extensions/grid/editor/SelectEditor.js b/frontend/beta/js/YUI-extensions/grid/editor/SelectEditor.js
new file mode 100644
index 0000000..200b8e3
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/editor/SelectEditor.js
@@ -0,0 +1,37 @@
+/**
+ * @class YAHOO.ext.grid.SelectEditor
+ * @extends YAHOO.ext.grid.CellEditor
+Creates an editor out of an existing select field. You can create the select element through DOM in Javascript and pass it to the SelectEditor's constructor or an easier way is like this:
+
+Define the select field in your document, giving it the ygrid-editor class.
+
+Create the SelectEditor object, passing in the id of your select field.
+
+<select id="light" class="ygrid-editor">
+ <option value="Shade">Shade</option>
+ <option value="Mostly Shady">Mostly Shady</option>
+ <option value="Sun or Shade">Sun or Shade</option>
+ <option value="Mostly Sunny">Mostly Sunny</option>
+ <option value="Sunny">Sunny</option>
+</select>
+
+For more information on using this editor, see this blog post.
+* @constructor
+* Create a new SelectEditor
+* @param {HTMLElement/String} element
+ */
+YAHOO.ext.grid.SelectEditor = function(element){
+ element.hideFocus = true;
+ YAHOO.ext.grid.SelectEditor.superclass.constructor.call(this, element);
+ this.element.swallowEvent('click');
+};
+YAHOO.extendX(YAHOO.ext.grid.SelectEditor, YAHOO.ext.grid.CellEditor);
+
+YAHOO.ext.grid.SelectEditor.prototype.fitToCell = function(box){
+ if(YAHOO.ext.util.Browser.isGecko){
+ box.height -= 3;
+ }
+ this.element.setBox(box, true);
+};
diff --git a/frontend/beta/js/YUI-extensions/grid/editor/TextEditor.js b/frontend/beta/js/YUI-extensions/grid/editor/TextEditor.js
new file mode 100644
index 0000000..3c97acd
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/grid/editor/TextEditor.js
@@ -0,0 +1,110 @@
+/**
+ * @class YAHOO.ext.grid.TextEditor
+ * @extends YAHOO.ext.grid.CellEditor
+Provides basic text editing for a cells and supports the following configuration options:
+
+var editor = new YAHOO.ext.grid.SelectEditor('light');
+
+
+For more information on using this editor, see this blog post.
+* @constructor
+* Create a new TextEditor
+* @param {Object} config
+ */
+YAHOO.ext.grid.TextEditor = function(config){
+ var element = document.createElement('input');
+ element.type = 'text';
+ element.className = 'ygrid-editor ygrid-text-editor';
+ element.setAttribute('autocomplete', 'off');
+ document.body.appendChild(element);
+ YAHOO.ext.grid.TextEditor.superclass.constructor.call(this, element);
+ YAHOO.ext.util.Config.apply(this, config);
+};
+YAHOO.extendX(YAHOO.ext.grid.TextEditor, YAHOO.ext.grid.CellEditor);
+
+YAHOO.ext.grid.TextEditor.prototype.validate = function(){
+ var dom = this.element.dom;
+ var value = dom.value;
+ if(value.length < 1){ // if it's blank
+ if(this.allowBlank){
+ dom.title = '';
+ this.element.removeClass('ygrid-editor-invalid');
+ return true;
+ }else{
+ dom.title = this.blankText;
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ }
+ if(value.length < this.minLength){
+ dom.title = this.minText.replace('%0', this.minLength);
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ if(value.length > this.maxLength){
+ dom.title = this.maxText.replace('%0', this.maxLength);
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ var msg = this.validator(value);
+ if(msg !== true){
+ dom.title = msg;
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ if(this.regex && !this.regex.test(value)){
+ dom.title = this.regexText;
+ this.element.addClass('ygrid-editor-invalid');
+ return false;
+ }
+ dom.title = '';
+ this.element.removeClass('ygrid-editor-invalid');
+ return true;
+};
+
+YAHOO.ext.grid.TextEditor.prototype.initEvents = function(){
+ YAHOO.ext.grid.TextEditor.superclass.initEvents.call(this);
+ var vtask = new YAHOO.ext.util.DelayedTask(this.validate, this);
+ this.element.mon('keyup', vtask.delay.createDelegate(vtask, [this.validationDelay]));
+};
+
+YAHOO.ext.grid.TextEditor.prototype.show = function(){
+ this.element.dom.title = '';
+ YAHOO.ext.grid.TextEditor.superclass.show.call(this);
+ this.element.focus();
+ if(this.selectOnFocus){
+ try{
+ this.element.dom.select();
+ }catch(e){}
+ }
+ this.validate(this.element.dom.value);
+};
+
+YAHOO.ext.grid.TextEditor.prototype.getValue = function(){
+ if(!this.validate()){
+ return this.originalValue;
+ }else{
+ return this.element.dom.value;
+ }
+};
+
+YAHOO.ext.grid.TextEditor.prototype.allowBlank = true;
+YAHOO.ext.grid.TextEditor.prototype.minLength = 0;
+YAHOO.ext.grid.TextEditor.prototype.maxLength = Number.MAX_VALUE;
+YAHOO.ext.grid.TextEditor.prototype.minText = 'The minimum length for this field is %0';
+YAHOO.ext.grid.TextEditor.prototype.maxText = 'The maximum length for this field is %0';
+YAHOO.ext.grid.TextEditor.prototype.selectOnFocus = true;
+YAHOO.ext.grid.TextEditor.prototype.blankText = 'This field cannot be blank';
+YAHOO.ext.grid.TextEditor.prototype.validator = function(){return true;};
+YAHOO.ext.grid.TextEditor.prototype.validationDelay = 200;
+YAHOO.ext.grid.TextEditor.prototype.regex = null;
+YAHOO.ext.grid.TextEditor.prototype.regexText = '';
diff --git a/frontend/beta/js/YUI-extensions/layout/BasicLayoutRegion.js b/frontend/beta/js/YUI-extensions/layout/BasicLayoutRegion.js
new file mode 100644
index 0000000..b7ea273
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/layout/BasicLayoutRegion.js
@@ -0,0 +1,265 @@
+/**
+ * @class YAHOO.ext.BasicLayoutRegion
+ * @extends YAHOO.ext.util.Observable
+ * This class represents a lightweight region in a layout manager. This region does not move dom nodes
+ * and does not have a titlebar, tabs or any other features. All it does is size and position
+ * panels. To create a BasicLayoutRegion, add lightweight:true or basic:true to your regions config.
+ */
+YAHOO.ext.BasicLayoutRegion = function(mgr, config, pos, skipConfig){
+ this.mgr = mgr;
+ this.position = pos;
+ this.events = {
+ /**
+ * @event beforeremove
+ * Fires before a panel is removed (or closed). To cancel the removal set "e.cancel = true" on the event argument.
+ * @param {YAHOO.ext.LayoutRegion} this
+ * @param {YAHOO.ext.ContentPanel} panel The panel
+ * @param {Object} e The cancel event object
+ */
+ 'beforeremove' : true,
+ /**
+ * @event invalidated
+ * Fires when the layout for this region is changed.
+ * @param {YAHOO.ext.LayoutRegion} this
+ */
+ 'invalidated' : true,
+ /**
+ * @event visibilitychange
+ * Fires when this region is shown or hidden
+ * @param {YAHOO.ext.LayoutRegion} this
+ * @param {Boolean} visibility true or false
+ */
+ 'visibilitychange' : true,
+ /**
+ * @event paneladded
+ * Fires when a panel is added.
+ * @param {YAHOO.ext.LayoutRegion} this
+ * @param {YAHOO.ext.ContentPanel} panel The panel
+ */
+ 'paneladded' : true,
+ /**
+ * @event panelremoved
+ * Fires when a panel is removed.
+ * @param {YAHOO.ext.LayoutRegion} this
+ * @param {YAHOO.ext.ContentPanel} panel The panel
+ */
+ 'panelremoved' : true,
+ /**
+ * @event collapsed
+ * Fires when this region is collapsed.
+ * @param {YAHOO.ext.LayoutRegion} this
+ */
+ 'collapsed' : true,
+ /**
+ * @event expanded
+ * Fires when this region is expanded.
+ * @param {YAHOO.ext.LayoutRegion} this
+ */
+ 'expanded' : true,
+ /**
+ * @event panelactivated
+ * Fires when a panel is activated.
+ * @param {YAHOO.ext.LayoutRegion} this
+ * @param {YAHOO.ext.ContentPanel} panel The activated panel
+ */
+ 'panelactivated' : true,
+ /**
+ * @event resized
+ * Fires when the user resizes this region.
+ * @param {YAHOO.ext.LayoutRegion} this
+ * @param {Number} newSize The new size (width for east/west, height for north/south)
+ */
+ 'resized' : true
+ };
+ /** A collection of panels in this region. @type YAHOO.ext.util.MixedCollection */
+ this.panels = new YAHOO.ext.util.MixedCollection();
+ this.panels.getKey = this.getPanelId.createDelegate(this);
+ this.box = null;
+ this.activePanel = null;
+ if(skipConfig !== true){
+ this.applyConfig(config);
+ }
+};
+
+YAHOO.extendX(YAHOO.ext.BasicLayoutRegion, YAHOO.ext.util.Observable, {
+ getPanelId : function(p){
+ return p.getId();
+ },
+
+ applyConfig : function(config){
+ this.margins = config.margins || this.margins || {top: 0, left: 0, right:0, bottom: 0};
+ this.config = config;
+ },
+
+ /**
+ * Resizes the region to the specified size. For vertical regions (west, east) this adjusts
+ * the width, for horizontal (north, south) the height.
+ * @param {Number} newSize The new width or height
+ */
+ resizeTo : function(newSize){
+ if(this.activePanel){
+ var el = this.activePanel.getEl();
+ switch(this.position){
+ case 'east':
+ case 'west':
+ el.setWidth(newSize);
+ this.fireEvent('resized', this, newSize);
+ break;
+ case 'north':
+ case 'south':
+ el.setHeight(newSize);
+ this.fireEvent('resized', this, newSize);
+ break;
+ }
+ }
+ },
+
+ getBox : function(){
+ return this.activePanel ? this.activePanel.getEl().getBox(false, true) : null;
+ },
+
+ getMargins : function(){
+ return this.margins;
+ },
+
+ updateBox : function(box){
+ this.box = box;
+ var el = this.activePanel.getEl();
+ el.dom.style.left = box.x + 'px';
+ el.dom.style.top = box.y + 'px';
+ el.setSize(box.width, box.height);
+ },
+
+ /**
+ * Returns the container element for this region.
+ * @return {YAHOO.ext.Element}
+ */
+ getEl : function(){
+ return this.activePanel;
+ },
+
+ /**
+ * Returns true if this region is currently visible.
+ * @return {Boolean}
+ */
+ isVisible : function(){
+ return this.activePanel ? true : false;
+ },
+
+ setActivePanel : function(panel){
+ panel = this.getPanel(panel);
+ if(this.activePanel && this.activePanel != panel){
+ this.activePanel.setActiveState(false);
+ this.activePanel.getEl().setStyle({left:-10000,right:-10000});
+ }
+ this.activePanel = panel;
+ panel.setActiveState(true);
+ if(this.box){
+ panel.setSize(this.box.width, this.box.height);
+ }
+ this.fireEvent('panelactivated', this, panel);
+ this.fireEvent('invalidated');
+ },
+
+ /**
+ * Show the specified panel.
+ * @param {Number/String/ContentPanel} panelId The panels index, id or the panel itself
+ * @return {YAHOO.ext.ContentPanel} The shown panel or null
+ */
+ showPanel : function(panel){
+ if(panel = this.getPanel(panel)){
+ this.setActivePanel(panel);
+ }
+ return panel;
+ },
+
+ /**
+ * Get the active panel for this region.
+ * @return {YAHOO.ext.ContentPanel} The active panel or null
+ */
+ getActivePanel : function(){
+ return this.activePanel;
+ },
+
+ /**
+ * Add the passed ContentPanel(s)
+ * @param {ContentPanel...} panel The ContentPanel(s) to add (you can pass more than one)
+ * @return {YAHOO.ext.ContentPanel} The panel added (if only one was added)
+ */
+ add : function(panel){
+ if(arguments.length > 1){
+ for(var i = 0, len = arguments.length; i < len; i++) {
+ this.add(arguments[i]);
+ }
+ return null;
+ }
+ if(this.hasPanel(panel)){
+ this.showPanel(panel);
+ return panel;
+ }
+ panel.setRegion(this);
+ this.panels.add(panel);
+ panel.getEl().setStyle('position', 'absolute');
+ if(!panel.background){
+ this.setActivePanel(panel);
+ if(this.config.initialSize && this.panels.getCount()==1){
+ this.resizeTo(this.config.initialSize);
+ }
+ }
+ this.fireEvent('paneladded', this, panel);
+ return panel;
+ },
+
+ /**
+ * Returns true if the panel is in this region.
+ * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
+ * @return {Boolean}
+ */
+ hasPanel : function(panel){
+ if(typeof panel == 'object'){ // must be panel obj
+ panel = panel.getId();
+ }
+ return this.getPanel(panel) ? true : false;
+ },
+
+ /**
+ * Removes the specified panel. If preservePanel is not true (either here or in the config), the panel is destroyed.
+ * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
+ * @param {Boolean} preservePanel Overrides the config preservePanel option
+ * @return {YAHOO.ext.ContentPanel} The panel that was removed
+ */
+ remove : function(panel, preservePanel){
+ panel = this.getPanel(panel);
+ if(!panel){
+ return null;
+ }
+ var e = {};
+ this.fireEvent('beforeremove', this, panel, e);
+ if(e.cancel === true){
+ return null;
+ }
+ var panelId = panel.getId();
+ this.panels.removeKey(panelId);
+ return panel;
+ },
+
+ /**
+ * Returns the panel specified or null if it's not in this region.
+ * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
+ * @return {YAHOO.ext.ContentPanel}
+ */
+ getPanel : function(id){
+ if(typeof id == 'object'){ // must be panel obj
+ return id;
+ }
+ return this.panels.get(id);
+ },
+
+ /**
+ * Returns this regions position (north/south/east/west/center).
+ * @return {String}
+ */
+ getPosition: function(){
+ return this.position;
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/layout/BorderLayout.js b/frontend/beta/js/YUI-extensions/layout/BorderLayout.js
new file mode 100644
index 0000000..0529c24
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/layout/BorderLayout.js
@@ -0,0 +1,281 @@
+/**
+ * @class YAHOO.ext.BorderLayout
+ * @extends YAHOO.ext.LayoutManager
+ * This class represents a common layout manager used in desktop applications. For screenshots and more details,
+ * please see:
+ * Cross Browser Layouts - Part 1
+ * Cross Browser Layouts - Part 2
+ * Example:
+
+* @constructor
+* Create a new BorderLayout
+* @param {String/HTMLElement/Element} container The container this layout is bound to
+* @param {Object} config Configuration options
+ */
+YAHOO.ext.BorderLayout = function(container, config){
+ config = config || {};
+ YAHOO.ext.BorderLayout.superclass.constructor.call(this, container);
+ this.factory = config.factory || YAHOO.ext.BorderLayout.RegionFactory;
+ /**
+ * True to hide the center panel while performing layouts. This helps when the center region contains
+ * heavy components such as a yui-ext grid.
+ * @type Boolean
+ */
+ this.hideOnLayout = config.hideOnLayout || false;
+ for(var i = 0, len = this.factory.validRegions.length; i < len; i++) {
+ var target = this.factory.validRegions[i];
+ if(config[target]){
+ this.addRegion(target, config[target]);
+ }
+ }
+ //this.dragOverDelegate = YAHOO.ext.EventManager.wrap(this.onDragOver, this, true);
+};
+
+YAHOO.extendX(YAHOO.ext.BorderLayout, YAHOO.ext.LayoutManager, {
+ /**
+ * Creates and adds a new region if it doesn't already exist.
+ * @param {String} target The target region key (north, south, east, west or center).
+ * @param {Object} config The regions config object
+ * @return {BorderLayoutRegion} The new region
+ */
+ addRegion : function(target, config){
+ if(!this.regions[target]){
+ var r = this.factory.create(target, this, config);
+ this.regions[target] = r;
+ r.on('visibilitychange', this.layout, this, true);
+ r.on('paneladded', this.layout, this, true);
+ r.on('panelremoved', this.layout, this, true);
+ r.on('invalidated', this.layout, this, true);
+ r.on('resized', this.onRegionResized, this, true);
+ r.on('collapsed', this.onRegionCollapsed, this, true);
+ r.on('expanded', this.onRegionExpanded, this, true);
+ }
+ return this.regions[target];
+ },
+
+ /**
+ * Performs a layout update.
+ */
+ layout : function(){
+ if(this.updating) return;
+ //var bench = new YAHOO.ext.util.Bench();
+ //bench.start('Layout...');
+ var size = this.getViewSize();
+ var w = size.width, h = size.height;
+ var centerW = w, centerH = h, centerY = 0, centerX = 0;
+ var x = 0, y = 0;
+
+ var rs = this.regions;
+ var n = rs['north'], s = rs['south'], west = rs['west'], e = rs['east'], c = rs['center'];
+ if(this.hideOnLayout){
+ c.el.setStyle('display', 'none');
+ }
+ if(n && n.isVisible()){
+ var b = n.getBox();
+ var m = n.getMargins();
+ b.width = w - (m.left+m.right);
+ b.x = m.left;
+ b.y = m.top;
+ centerY = b.height + b.y + m.bottom;
+ centerH -= centerY;
+ n.updateBox(this.safeBox(b));
+ }
+ if(s && s.isVisible()){
+ var b = s.getBox();
+ var m = s.getMargins();
+ b.width = w - (m.left+m.right);
+ b.x = m.left;
+ var totalHeight = (b.height + m.top + m.bottom);
+ b.y = h - totalHeight + m.top;
+ centerH -= totalHeight;
+ s.updateBox(this.safeBox(b));
+ }
+ if(west && west.isVisible()){
+ var b = west.getBox();
+ var m = west.getMargins();
+ b.height = centerH - (m.top+m.bottom);
+ b.x = m.left;
+ b.y = centerY + m.top;
+ var totalWidth = (b.width + m.left + m.right);
+ centerX += totalWidth;
+ centerW -= totalWidth;
+ west.updateBox(this.safeBox(b));
+ }
+ if(e && e.isVisible()){
+ var b = e.getBox();
+ var m = e.getMargins();
+ b.height = centerH - (m.top+m.bottom);
+ var totalWidth = (b.width + m.left + m.right);
+ b.x = w - totalWidth + m.left;
+ b.y = centerY + m.top;
+ centerW -= totalWidth;
+ e.updateBox(this.safeBox(b));
+ }
+ if(c){
+ var m = c.getMargins();
+ var centerBox = {
+ x: centerX + m.left,
+ y: centerY + m.top,
+ width: centerW - (m.left+m.right),
+ height: centerH - (m.top+m.bottom)
+ };
+ if(this.hideOnLayout){
+ c.el.setStyle('display', 'block');
+ }
+ c.updateBox(this.safeBox(centerBox));
+ }
+ this.el.repaint();
+ this.fireEvent('layout', this);
+ //bench.stop();
+ //alert(bench.toString());
+ },
+
+ safeBox : function(box){
+ box.width = Math.max(0, box.width);
+ box.height = Math.max(0, box.height);
+ return box;
+ },
+
+ /**
+ * Adds a ContentPanel (or subclass) to this layout.
+ * @param {String} target The target region key (north, south, east, west or center).
+ * @param {YAHOO.ext.ContentPanel} panel The panel to add
+ * @return {YAHOO.ext.ContentPanel} The added panel
+ */
+ add : function(target, panel){
+ target = target.toLowerCase();
+ return this.regions[target].add(panel);
+ },
+
+ /**
+ * Adds a ContentPanel (or subclass) to this layout.
+ * @param {String} target The target region key (north, south, east, west or center).
+ * @param {Number/String/YAHOO.ext.ContentPanel} panel The index, id or panel to remove
+ * @return {YAHOO.ext.ContentPanel} The removed panel
+ */
+ remove : function(target, panel){
+ target = target.toLowerCase();
+ return this.regions[target].remove(panel);
+ },
+
+ /**
+ * Searches all regions for a panel with the specified id
+ * @param {String} panelId
+ * @return {YAHOO.ext.ContentPanel} The panel or null if it wasn't found
+ */
+ findPanel : function(panelId){
+ var rs = this.regions;
+ for(var target in rs){
+ if(typeof rs[target] != 'function'){
+ var p = rs[target].getPanel(panelId);
+ if(p){
+ return p;
+ }
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Searches all regions for a panel with the specified id and activates (shows) it.
+ * @param {String/ContentPanel} panelId The panels id or the panel itself
+ * @return {YAHOO.ext.ContentPanel} The shown panel or null
+ */
+ showPanel : function(panelId) {
+ var rs = this.regions;
+ for(var target in rs){
+ var r = rs[target];
+ if(typeof r != 'function'){
+ if(r.hasPanel(panelId)){
+ return r.showPanel(panelId);
+ }
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Restores this layouts state using YAHOO.ext.state.Manager or the state provided by the passed provider.
+ * @param {YAHOO.ext.state.Provider} provider (optional) An alternate state provider
+ */
+ restoreState : function(provider){
+ if(!provider){
+ provider = YAHOO.ext.state.Manager;
+ }
+ var sm = new YAHOO.ext.LayoutStateManager();
+ sm.init(this, provider);
+ }
+});
+
+YAHOO.ext.BorderLayout.RegionFactory = {};
+YAHOO.ext.BorderLayout.RegionFactory.validRegions = ['north','south','east','west','center'];
+YAHOO.ext.BorderLayout.RegionFactory.create = function(target, mgr, config){
+ target = target.toLowerCase();
+ if(config.lightweight || config.basic){
+ return new YAHOO.ext.BasicLayoutRegion(mgr, config, target);
+ }
+ switch(target){
+ case 'north':
+ return new YAHOO.ext.NorthLayoutRegion(mgr, config);
+ case 'south':
+ return new YAHOO.ext.SouthLayoutRegion(mgr, config);
+ case 'east':
+ return new YAHOO.ext.EastLayoutRegion(mgr, config);
+ case 'west':
+ return new YAHOO.ext.WestLayoutRegion(mgr, config);
+ case 'center':
+ return new YAHOO.ext.CenterLayoutRegion(mgr, config);
+ }
+ throw 'Layout region "'+target+'" not supported.';
+};
diff --git a/frontend/beta/js/YUI-extensions/layout/BorderLayoutRegions.js b/frontend/beta/js/YUI-extensions/layout/BorderLayoutRegions.js
new file mode 100644
index 0000000..9b4a09f
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/layout/BorderLayoutRegions.js
@@ -0,0 +1,207 @@
+/*
+ * These classes are private internal classes
+ */
+YAHOO.ext.CenterLayoutRegion = function(mgr, config){
+ YAHOO.ext.CenterLayoutRegion.superclass.constructor.call(this, mgr, config, 'center');
+ this.visible = true;
+ this.minWidth = config.minWidth || 20;
+ this.minHeight = config.minHeight || 20;
+};
+
+YAHOO.extendX(YAHOO.ext.CenterLayoutRegion, YAHOO.ext.LayoutRegion, {
+ hide : function(){
+ // center panel can't be hidden
+ },
+
+ show : function(){
+ // center panel can't be hidden
+ },
+
+ getMinWidth: function(){
+ return this.minWidth;
+ },
+
+ getMinHeight: function(){
+ return this.minHeight;
+ }
+});
+
+
+YAHOO.ext.NorthLayoutRegion = function(mgr, config){
+ YAHOO.ext.NorthLayoutRegion.superclass.constructor.call(this, mgr, config, 'north', 'n-resize');
+ if(this.split){
+ this.split.placement = YAHOO.ext.SplitBar.TOP;
+ this.split.orientation = YAHOO.ext.SplitBar.VERTICAL;
+ this.split.el.addClass('ylayout-split-v');
+ }
+ if(typeof config.initialSize != 'undefined'){
+ this.el.setHeight(config.initialSize);
+ }
+};
+YAHOO.extendX(YAHOO.ext.NorthLayoutRegion, YAHOO.ext.SplitLayoutRegion, {
+ getBox : function(){
+ if(this.collapsed){
+ return this.collapsedEl.getBox();
+ }
+ var box = this.el.getBox();
+ if(this.split){
+ box.height += this.split.el.getHeight();
+ }
+ return box;
+ },
+
+ updateBox : function(box){
+ if(this.split && !this.collapsed){
+ box.height -= this.split.el.getHeight();
+ this.split.el.setLeft(box.x);
+ this.split.el.setTop(box.y+box.height);
+ this.split.el.setWidth(box.width);
+ }
+ if(this.collapsed){
+ this.el.setWidth(box.width);
+ var bodyWidth = box.width - this.el.getBorderWidth('rl');
+ this.bodyEl.setWidth(bodyWidth);
+ if(this.activePanel && this.panelSize){
+ this.activePanel.setSize(bodyWidth, this.panelSize.height);
+ }
+ }
+ YAHOO.ext.NorthLayoutRegion.superclass.updateBox.call(this, box);
+ }
+});
+
+YAHOO.ext.SouthLayoutRegion = function(mgr, config){
+ YAHOO.ext.SouthLayoutRegion.superclass.constructor.call(this, mgr, config, 'south', 's-resize');
+ if(this.split){
+ this.split.placement = YAHOO.ext.SplitBar.BOTTOM;
+ this.split.orientation = YAHOO.ext.SplitBar.VERTICAL;
+ this.split.el.addClass('ylayout-split-v');
+ }
+ if(typeof config.initialSize != 'undefined'){
+ this.el.setHeight(config.initialSize);
+ }
+};
+YAHOO.extendX(YAHOO.ext.SouthLayoutRegion, YAHOO.ext.SplitLayoutRegion, {
+ getBox : function(){
+ if(this.collapsed){
+ return this.collapsedEl.getBox();
+ }
+ var box = this.el.getBox();
+ if(this.split){
+ var sh = this.split.el.getHeight();
+ box.height += sh;
+ box.y -= sh;
+ }
+ return box;
+ },
+
+ updateBox : function(box){
+ if(this.split && !this.collapsed){
+ var sh = this.split.el.getHeight();
+ box.height -= sh;
+ box.y += sh;
+ this.split.el.setLeft(box.x);
+ this.split.el.setTop(box.y-sh);
+ this.split.el.setWidth(box.width);
+ }
+ if(this.collapsed){
+ this.el.setWidth(box.width);
+ var bodyWidth = box.width - this.el.getBorderWidth('rl');
+ this.bodyEl.setWidth(bodyWidth);
+ if(this.activePanel && this.panelSize){
+ this.activePanel.setSize(bodyWidth, this.panelSize.height);
+ }
+ }
+ YAHOO.ext.SouthLayoutRegion.superclass.updateBox.call(this, box);
+ }
+});
+
+YAHOO.ext.EastLayoutRegion = function(mgr, config){
+ YAHOO.ext.EastLayoutRegion.superclass.constructor.call(this, mgr, config, 'east', 'e-resize');
+ if(this.split){
+ this.split.placement = YAHOO.ext.SplitBar.RIGHT;
+ this.split.orientation = YAHOO.ext.SplitBar.HORIZONTAL;
+ this.split.el.addClass('ylayout-split-h');
+ }
+ if(typeof config.initialSize != 'undefined'){
+ this.el.setWidth(config.initialSize);
+ }
+};
+YAHOO.extendX(YAHOO.ext.EastLayoutRegion, YAHOO.ext.SplitLayoutRegion, {
+ getBox : function(){
+ if(this.collapsed){
+ return this.collapsedEl.getBox();
+ }
+ var box = this.el.getBox();
+ if(this.split){
+ var sw = this.split.el.getWidth();
+ box.width += sw;
+ box.x -= sw;
+ }
+ return box;
+ },
+
+ updateBox : function(box){
+ if(this.split && !this.collapsed){
+ var sw = this.split.el.getWidth();
+ box.width -= sw;
+ this.split.el.setLeft(box.x);
+ this.split.el.setTop(box.y);
+ this.split.el.setHeight(box.height);
+ box.x += sw;
+ }
+ if(this.collapsed){
+ this.el.setHeight(box.height);
+ var bodyHeight = this.config.titlebar ? box.height - (this.titleEl.getHeight()||0) : box.height;
+ bodyHeight -= this.el.getBorderWidth('tb');
+ this.bodyEl.setHeight(bodyHeight);
+ if(this.activePanel && this.panelSize){
+ this.activePanel.setSize(this.panelSize.width, bodyHeight);
+ }
+ }
+ YAHOO.ext.EastLayoutRegion.superclass.updateBox.call(this, box);
+ }
+});
+
+YAHOO.ext.WestLayoutRegion = function(mgr, config){
+ YAHOO.ext.WestLayoutRegion.superclass.constructor.call(this, mgr, config, 'west', 'w-resize');
+ if(this.split){
+ this.split.placement = YAHOO.ext.SplitBar.LEFT;
+ this.split.orientation = YAHOO.ext.SplitBar.HORIZONTAL;
+ this.split.el.addClass('ylayout-split-h');
+ }
+ if(typeof config.initialSize != 'undefined'){
+ this.el.setWidth(config.initialSize);
+ }
+};
+YAHOO.extendX(YAHOO.ext.WestLayoutRegion, YAHOO.ext.SplitLayoutRegion, {
+ getBox : function(){
+ if(this.collapsed){
+ return this.collapsedEl.getBox();
+ }
+ var box = this.el.getBox();
+ if(this.split){
+ box.width += this.split.el.getWidth();
+ }
+ return box;
+ },
+
+ updateBox : function(box){
+ if(this.split && !this.collapsed){
+ var sw = this.split.el.getWidth();
+ box.width -= sw;
+ this.split.el.setLeft(box.x+box.width);
+ this.split.el.setTop(box.y);
+ this.split.el.setHeight(box.height);
+ }
+ if(this.collapsed){
+ this.el.setHeight(box.height);
+ var bodyHeight = this.config.titlebar ? box.height - (this.titleEl.getHeight()||0) : box.height;
+ bodyHeight -= this.el.getBorderWidth('tb');
+ this.bodyEl.setHeight(bodyHeight);
+ if(this.activePanel && this.panelSize){
+ this.activePanel.setSize(this.panelSize.width, bodyHeight);
+ }
+ }
+ YAHOO.ext.WestLayoutRegion.superclass.updateBox.call(this, box);
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/layout/ContentPanels.js b/frontend/beta/js/YUI-extensions/layout/ContentPanels.js
new file mode 100644
index 0000000..7cfdde7
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/layout/ContentPanels.js
@@ -0,0 +1,325 @@
+/**
+ * @class YAHOO.ext.ContentPanel
+ * @extends YAHOO.ext.util.Observable
+ * A basic ContentPanel element.
+ * @cfg {Boolean} fitToFrame True for this panel to manually adjust it's size when the region resizes (defaults to false)
+ * @cfg {Boolean/Object} autoCreate True to auto generate the DOM element for this panel, or a DomHelper config of the element to create
+ * @cfg {Boolean} closable True if the panel can be closed/removed
+ * @cfg {Boolean} background True if the panel should not be activated when it is added (defaults to false)
+ * @cfg {String/HTMLElement/Element} resizeEl An element to resize if fitToFrame is true (instead of this panel's element)
+ * @cfg {Toolbar} toolbar A toolbar for this panel
+ * @cfg {Boolean} autoScroll True to scroll overflow in this panel (use with fitToFrame)
+ * @cfg {String} title The title for this panel
+ * @cfg {Array} adjustments Values to add to the width/height when doing a fitToFrame (default is [0, 0])
+ * @constructor
+ * Create a new ContentPanel.
+ * @param {String/HTMLElement/Element} el The container element for this panel
+ * @param {String/Object} config A string to set only the title or a config object
+ * @param {String} content (optional) Set the HTML content for this panel
+ */
+YAHOO.ext.ContentPanel = function(el, config, content){
+ YAHOO.ext.ContentPanel.superclass.constructor.call(this);
+ this.el = getEl(el, true);
+ if(!this.el && config && config.autoCreate){
+ if(typeof config.autoCreate == 'object'){
+ if(!config.autoCreate.id){
+ config.autoCreate.id = el;
+ }
+ this.el = YAHOO.ext.DomHelper.append(document.body,
+ config.autoCreate, true);
+ }else{
+ this.el = YAHOO.ext.DomHelper.append(document.body,
+ {tag: 'div', cls: 'ylayout-inactive-content', id: el}, true);
+ }
+ }
+ this.closable = false;
+ this.loaded = false;
+ this.active = false;
+ if(typeof config == 'string'){
+ this.title = config;
+ }else{
+ YAHOO.ext.util.Config.apply(this, config);
+ }
+ if(this.resizeEl){
+ this.resizeEl = getEl(this.resizeEl, true);
+ }else{
+ this.resizeEl = this.el;
+ }
+ this.events = {
+ /**
+ * @event activate
+ * Fires when this panel is activated.
+ * @param {YAHOO.ext.ContentPanel} this
+ */
+ 'activate' : new YAHOO.util.CustomEvent('activate'),
+ /**
+ * @event deactivate
+ * Fires when this panel is activated.
+ * @param {YAHOO.ext.ContentPanel} this
+ */
+ 'deactivate' : new YAHOO.util.CustomEvent('deactivate')
+ };
+ if(this.autoScroll){
+ this.resizeEl.setStyle('overflow', 'auto');
+ }
+ if(content){
+ this.setContent(content);
+ }
+};
+
+YAHOO.extendX(YAHOO.ext.ContentPanel, YAHOO.ext.util.Observable, {
+ setRegion : function(region){
+ this.region = region;
+ if(region){
+ this.el.replaceClass('ylayout-inactive-content', 'ylayout-active-content');
+ }else{
+ this.el.replaceClass('ylayout-active-content', 'ylayout-inactive-content');
+ }
+ },
+
+ /**
+ * Returns the toolbar for this Panel if one was configured
+ * @return {YAHOO.ext.Toolbar}
+ */
+ getToolbar : function(){
+ return this.toolbar;
+ },
+
+ setActiveState : function(active){
+ this.active = active;
+ if(!active){
+ this.fireEvent('deactivate', this);
+ }else{
+ this.fireEvent('activate', this);
+ }
+ },
+ /**
+ * Updates this panel's element
+ * @param {String} content The new content
+ * @param {Boolean} loadScripts (optional) true to look for and process scripts
+ */
+ setContent : function(content, loadScripts){
+ this.el.update(content, loadScripts);
+ },
+
+ /**
+ * Get the {@link YAHOO.ext.UpdateManager} for this panel. Enables you to perform Ajax updates.
+ * @return {YAHOO.ext.UpdateManager} The UpdateManager
+ */
+ getUpdateManager : function(){
+ return this.el.getUpdateManager();
+ },
+
+ /**
+ * Set a URL to be used to load the content for this panel.
+ * @param {String/Function} url The url to load the content from or a function to call to get the url
+ * @param {String/Object} params (optional) The string params for the update call or an object of the params. See {@link YAHOO.ext.UpdateManager#update} for more details. (Defaults to null)
+ * @param {Boolean} loadOnce (optional) Whether to only load the content once. If this is false it makes the Ajax call every time this panel is activated. (Defaults to false)
+ * @return {YAHOO.ext.UpdateManager} The UpdateManager
+ */
+ setUrl : function(url, params, loadOnce){
+ if(this.refreshDelegate){
+ this.removeListener('activate', this.refreshDelegate);
+ }
+ this.refreshDelegate = this._handleRefresh.createDelegate(this, [url, params, loadOnce]);
+ this.on('activate', this._handleRefresh.createDelegate(this, [url, params, loadOnce]));
+ return this.el.getUpdateManager();
+ },
+
+ _handleRefresh : function(url, params, loadOnce){
+ if(!loadOnce || !this.loaded){
+ var updater = this.el.getUpdateManager();
+ updater.update(url, params, this._setLoaded.createDelegate(this));
+ }
+ },
+
+ _setLoaded : function(){
+ this.loaded = true;
+ },
+
+ /**
+ * Returns this panel's id
+ * @return {String}
+ */
+ getId : function(){
+ return this.el.id;
+ },
+
+ /**
+ * Returns this panel's element
+ * @return {YAHOO.ext.Element}
+ */
+ getEl : function(){
+ return this.el;
+ },
+
+ adjustForComponents : function(width, height){
+ if(this.toolbar){
+ var te = this.toolbar.getEl();
+ height -= te.getHeight();
+ te.setWidth(width);
+ }
+ if(this.adjustments){
+ width += this.adjustments[0];
+ height += this.adjustments[1];
+ }
+ return {'width': width, 'height': height};
+ },
+
+ setSize : function(width, height){
+ if(this.fitToFrame){
+ var size = this.adjustForComponents(width, height);
+ this.resizeEl.setSize(this.autoWidth ? 'auto' : size.width, size.height);
+ }
+ },
+
+ /**
+ * Returns this panel's title
+ * @return {String}
+ */
+ getTitle : function(){
+ return this.title;
+ },
+
+ /**
+ * Set this panel's title
+ * @param {String} title
+ */
+ setTitle : function(title){
+ this.title = title;
+ if(this.region){
+ this.region.updatePanelTitle(this, title);
+ }
+ },
+
+ /**
+ * Returns true is this panel was configured to be closable
+ * @return {Boolean}
+ */
+ isClosable : function(){
+ return this.closable;
+ },
+
+ beforeSlide : function(){
+ this.el.clip();
+ this.resizeEl.clip();
+ },
+
+ afterSlide : function(){
+ this.el.unclip();
+ this.resizeEl.unclip();
+ },
+
+ /**
+ * Force a content refresh from the URL specified in the setUrl() method.
+ * Will fail silently if the setUrl method has not been called.
+ * This does not activate the panel, just updates its content.
+ */
+ refresh : function(){
+ if(this.refreshDelegate){
+ this.loaded = false;
+ this.refreshDelegate();
+ }
+ },
+
+ /**
+ * Destroys this panel
+ */
+ destroy : function(){
+ this.el.removeAllListeners();
+ var tempEl = document.createElement('span');
+ tempEl.appendChild(this.el.dom);
+ tempEl.innerHTML = '';
+ this.el = null;
+ }
+});
+
+/**
+ * @class YAHOO.ext.GridPanel
+ * @extends YAHOO.ext.ContentPanel
+ * @constructor
+ * Create a new GridPanel.
+ * @param {YAHOO.ext.grid.Grid} grid The grid for this panel
+ * @param {String/Object} config A string to set only the title or a config object
+ */
+YAHOO.ext.GridPanel = function(grid, config){
+ this.wrapper = YAHOO.ext.DomHelper.append(document.body, // wrapper for IE7 strict & safari scroll issue
+ {tag: 'div', cls: 'ylayout-grid-wrapper ylayout-inactive-content'}, true);
+ this.wrapper.dom.appendChild(grid.container.dom);
+ YAHOO.ext.GridPanel.superclass.constructor.call(this, this.wrapper, config);
+ if(this.toolbar){
+ this.toolbar.el.insertBefore(this.wrapper.dom.firstChild);
+ }
+ grid.monitorWindowResize = false; // turn off autosizing
+ grid.autoHeight = false;
+ grid.autoWidth = false;
+ this.grid = grid;
+ this.grid.container.replaceClass('ylayout-inactive-content', 'ylayout-component-panel');
+};
+
+YAHOO.extendX(YAHOO.ext.GridPanel, YAHOO.ext.ContentPanel, {
+ getId : function(){
+ return this.grid.id;
+ },
+
+ /**
+ * Returns the grid for this panel
+ * @return {YAHOO.ext.grid.Grid}
+ */
+ getGrid : function(){
+ return this.grid;
+ },
+
+ setSize : function(width, height){
+ var grid = this.grid;
+ var size = this.adjustForComponents(width, height);
+ grid.container.setSize(size.width, size.height);
+ grid.autoSize();
+ },
+
+ beforeSlide : function(){
+ this.grid.getView().wrapEl.clip();
+ },
+
+ afterSlide : function(){
+ this.grid.getView().wrapEl.unclip();
+ },
+
+ destroy : function(){
+ this.grid.getView().unplugDataModel(this.grid.getDataModel());
+ this.grid.container.removeAllListeners();
+ YAHOO.ext.GridPanel.superclass.destroy.call(this);
+ }
+});
+
+
+/**
+ * @class YAHOO.ext.NestedLayoutPanel
+ * @extends YAHOO.ext.ContentPanel
+ * @constructor
+ * Create a new NestedLayoutPanel.
+ * @param {YAHOO.ext.BorderLayout} layout The layout for this panel
+ * @param {String/Object} config A string to set only the title or a config object
+ */
+YAHOO.ext.NestedLayoutPanel = function(layout, config){
+ YAHOO.ext.NestedLayoutPanel.superclass.constructor.call(this, layout.getEl(), config);
+ layout.monitorWindowResize = false; // turn off autosizing
+ this.layout = layout;
+ this.layout.getEl().addClass('ylayout-nested-layout');
+};
+
+YAHOO.extendX(YAHOO.ext.NestedLayoutPanel, YAHOO.ext.ContentPanel, {
+ setSize : function(width, height){
+ var size = this.adjustForComponents(width, height);
+ this.layout.getEl().setSize(size.width, size.height);
+ this.layout.layout();
+ },
+
+ /**
+ * Returns the nested BorderLayout for this panel
+ * @return {YAHOO.ext.BorderLayout}
+ */
+ getLayout : function(){
+ return this.layout;
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/layout/LayoutManager.js b/frontend/beta/js/YUI-extensions/layout/LayoutManager.js
new file mode 100644
index 0000000..c59bf0e
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/layout/LayoutManager.js
@@ -0,0 +1,135 @@
+/**
+ * @class YAHOO.ext.LayoutManager
+ * @extends YAHOO.ext.util.Observable
+ * Base class for layout managers.
+ */
+YAHOO.ext.LayoutManager = function(container){
+ YAHOO.ext.LayoutManager.superclass.constructor.call(this);
+ this.el = getEl(container, true);
+ // ie scrollbar fix
+ if(this.el.dom == document.body && YAHOO.ext.util.Browser.isIE){
+ document.body.scroll = 'no';
+ }
+ this.id = this.el.id;
+ this.el.addClass('ylayout-container');
+ /** false to disable window resize monitoring @type Boolean */
+ this.monitorWindowResize = true;
+ this.regions = {};
+ this.events = {
+ /**
+ * @event layout
+ * Fires when a layout is performed.
+ * @param {YAHOO.ext.LayoutManager} this
+ */
+ 'layout' : new YAHOO.util.CustomEvent(),
+ /**
+ * @event regionresized
+ * Fires when the user resizes a region.
+ * @param {YAHOO.ext.LayoutRegion} region
+ * @param {Number} newSize The new size (width for east/west, height for north/south)
+ */
+ 'regionresized' : new YAHOO.util.CustomEvent(),
+ /**
+ * @event regioncollapsed
+ * Fires when a region is collapsed.
+ * @param {YAHOO.ext.LayoutRegion} region
+ */
+ 'regioncollapsed' : new YAHOO.util.CustomEvent(),
+ /**
+ * @event regionexpanded
+ * Fires when a region is expanded.
+ * @param {YAHOO.ext.LayoutRegion} region
+ */
+ 'regionexpanded' : new YAHOO.util.CustomEvent()
+ };
+ this.updating = false;
+ YAHOO.ext.EventManager.onWindowResize(this.onWindowResize, this, true);
+};
+
+YAHOO.extendX(YAHOO.ext.LayoutManager, YAHOO.ext.util.Observable, {
+ /**
+ * Returns true if this layout is currently being updated
+ * @return {Boolean}
+ */
+ isUpdating : function(){
+ return this.updating;
+ },
+
+ /**
+ * Suspend the LayoutManager from doing auto-layouts while
+ * making multiple add or remove calls
+ */
+ beginUpdate : function(){
+ this.updating = true;
+ },
+
+ /**
+ * Restore auto-layouts and optionally disable the manager from performing a layout
+ * @param {Boolean} noLayout true to disable a layout update
+ */
+ endUpdate : function(noLayout){
+ this.updating = false;
+ if(!noLayout){
+ this.layout();
+ }
+ },
+
+ layout: function(){
+
+ },
+
+ onRegionResized : function(region, newSize){
+ this.fireEvent('regionresized', region, newSize);
+ this.layout();
+ },
+
+ onRegionCollapsed : function(region){
+ this.fireEvent('regioncollapsed', region);
+ },
+
+ onRegionExpanded : function(region){
+ this.fireEvent('regionexpanded', region);
+ },
+
+ /**
+ * Returns the size of the current view, This method normalizes document.body and element embedded layouts and
+ * performs box-model adjustments.
+ * @return {Object} The size as an object {width: (the width), height: (the height)}
+ */
+ getViewSize : function(){
+ var size;
+ if(this.el.dom != document.body){
+ this.el.beginMeasure();
+ size = this.el.getSize();
+ this.el.endMeasure();
+ }else{
+ size = {width: YAHOO.util.Dom.getViewportWidth(), height: YAHOO.util.Dom.getViewportHeight()};
+ }
+ size.width -= this.el.getBorderWidth('lr')-this.el.getPadding('lr');
+ size.height -= this.el.getBorderWidth('tb')-this.el.getPadding('tb');
+ return size;
+ },
+
+ /**
+ * Returns the element this layout is bound to.
+ * @return {YAHOO.ext.Element}
+ */
+ getEl : function(){
+ return this.el;
+ },
+
+ /**
+ * Returns the specified region.
+ * @param {String} target The region key
+ * @return {YAHOO.ext.LayoutRegion}
+ */
+ getRegion : function(target){
+ return this.regions[target.toLowerCase()];
+ },
+
+ onWindowResize : function(){
+ if(this.monitorWindowResize){
+ this.layout();
+ }
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/layout/LayoutRegion.js b/frontend/beta/js/YUI-extensions/layout/LayoutRegion.js
new file mode 100644
index 0000000..fa8a1b6
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/layout/LayoutRegion.js
@@ -0,0 +1,496 @@
+/**
+ * @class YAHOO.ext.LayoutRegion
+ * @extends YAHOO.ext.util.Observable
+ * This class represents a region in a layout manager.
+ * @cfg {Boolean} collapsible False to disable collapsing (defaults to true)
+ * @cfg {Boolean} floatable False to disable floating (defaults to true)
+ * @cfg {Object} margins Margins for the element (defaults to {top: 0, left: 0, right:0, bottom: 0})
+ * @cfg {Object} cmargins Margins for the element when collapsed (defaults to: north/south {top: 2, left: 0, right:0, bottom: 2} or east/west {top: 0, left: 2, right:2, bottom: 0})
+ * @cfg {String} tabPosition 'top' or 'bottom' (defaults to 'bottom')
+ * @cfg {Boolean} alwaysShowTabs True to always display tabs even when only 1 panel (defaults to false)
+ * @cfg {Boolean} autoScroll True to enable overflow scrolling (defaults to false)
+ * @cfg {Boolean} titlebar True to display a title bar (defaults to true)
+ * @cfg {String} title The title for the region (overrides panel titles)
+ * @cfg {Boolean} animate True to animate expand/collapse (defaults to false)
+ * @cfg {Float} duration The duration of the expand/collapse animation in seconds
+ * @cfg {Float} slideDuration The duration of the slide out/in when collapsed in seconds
+ * @cfg {Boolean} autoHide False to disable disable autoHide when the mouse leaves the "floated" region (defaults to true)
+ * @cfg {Boolean} preservePanels True to preserve removed panels so they can be readded later (defaults to false)
+ * @cfg {Boolean} closeOnTabs True to place the close icon on the tabs instead of the region titlebar (defaults to false)
+ * @cfg {Boolean} hideTabs True to hide the tab strip (defaults to false)
+ * @cfg {Boolean} resizeTabs True to enable automatic tab resizing. This will resize the tabs so they are all the same size and fit within
+ * the space available, similar to FireFox 1.5 tabs (defaults to false)
+ * @cfg {Number} minTabWidth The minimum tab width (defaults to 40)
+ * @cfg {Number} preferredTabWidth The preferred tab width (defaults to 150)
+ */
+YAHOO.ext.LayoutRegion = function(mgr, config, pos){
+ YAHOO.ext.LayoutRegion.superclass.constructor.call(this, mgr, config, pos, true);
+ var dh = YAHOO.ext.DomHelper;
+ /** This regions container element @type YAHOO.ext.Element */
+ this.el = dh.append(mgr.el.dom, {tag: 'div', cls: 'ylayout-panel ylayout-panel-' + this.position}, true);
+ /** This regions title element @type YAHOO.ext.Element */
+ this.titleEl = dh.append(this.el.dom, {tag: 'div', unselectable: 'on', cls: 'yunselectable ylayout-panel-hd ylayout-title-'+this.position, children:[
+ {tag: 'span', cls: 'yunselectable ylayout-panel-hd-text', unselectable: 'on', html: ' '},
+ {tag: 'div', cls: 'yunselectable ylayout-panel-hd-tools', unselectable: 'on'}
+ ]}, true);
+ this.titleEl.enableDisplayMode();
+ /** This regions title text element @type HTMLElement */
+ this.titleTextEl = this.titleEl.dom.firstChild;
+ this.tools = getEl(this.titleEl.dom.childNodes[1], true);
+ this.closeBtn = this.createTool(this.tools.dom, 'ylayout-close');
+ this.closeBtn.enableDisplayMode();
+ this.closeBtn.on('click', this.closeClicked, this, true);
+ this.closeBtn.hide();
+ /** This regions body element @type YAHOO.ext.Element */
+ this.bodyEl = dh.append(this.el.dom, {tag: 'div', cls: 'ylayout-panel-body'}, true);
+ this.visible = false;
+ this.collapsed = false;
+ this.hide();
+ this.on('paneladded', this.validateVisibility, this, true);
+ this.on('panelremoved', this.validateVisibility, this, true);
+
+ this.applyConfig(config);
+};
+
+YAHOO.extendX(YAHOO.ext.LayoutRegion, YAHOO.ext.BasicLayoutRegion, {
+ applyConfig : function(config){
+ if(config.collapsible && this.position != 'center' && !this.collapsedEl){
+ var dh = YAHOO.ext.DomHelper;
+ this.collapseBtn = this.createTool(this.tools.dom, 'ylayout-collapse-'+this.position);
+ this.collapseBtn.mon('click', this.collapse, this, true);
+ /** This regions collapsed element @type YAHOO.ext.Element */
+ this.collapsedEl = dh.append(this.mgr.el.dom, {tag: 'div', cls: 'ylayout-collapsed ylayout-collapsed-'+this.position, children:[
+ {tag: 'div', cls: 'ylayout-collapsed-tools'}
+ ]}, true);
+ if(config.floatable !== false){
+ this.collapsedEl.addClassOnOver('ylayout-collapsed-over');
+ this.collapsedEl.mon('click', this.collapseClick, this, true);
+ }
+ this.expandBtn = this.createTool(this.collapsedEl.dom.firstChild, 'ylayout-expand-'+this.position);
+ this.expandBtn.mon('click', this.expand, this, true);
+ }
+ if(this.collapseBtn){
+ this.collapseBtn.setVisible(config.collapsible == true);
+ }
+ this.cmargins = config.cmargins || this.cmargins ||
+ (this.position == 'west' || this.position == 'east' ?
+ {top: 0, left: 2, right:2, bottom: 0} :
+ {top: 2, left: 0, right:0, bottom: 2});
+ this.margins = config.margins || this.margins || {top: 0, left: 0, right:0, bottom: 0};
+ this.bottomTabs = config.tabPosition != 'top';
+ this.autoScroll = config.autoScroll || false;
+ if(this.autoScroll){
+ this.bodyEl.setStyle('overflow', 'auto');
+ }else{
+ this.bodyEl.setStyle('overflow', 'hidden');
+ }
+ if((!config.titlebar && !config.title) || config.titlebar === false){
+ this.titleEl.hide();
+ }else{
+ this.titleEl.show();
+ if(config.title){
+ this.titleTextEl.innerHTML = config.title;
+ }
+ }
+ this.duration = config.duration || .30;
+ this.slideDuration = config.slideDuration || .45;
+ this.config = config;
+ if(config.collapsed){
+ this.collapse(true);
+ }
+ },
+ /**
+ * Returns true if this region is currently visible.
+ * @return {Boolean}
+ */
+ isVisible : function(){
+ return this.visible;
+ },
+
+ getBox : function(){
+ var b;
+ if(!this.collapsed){
+ b = this.el.getBox(false, true);
+ }else{
+ b = this.collapsedEl.getBox(false, true);
+ }
+ return b;
+ },
+
+ getMargins : function(){
+ return this.collapsed ? this.cmargins : this.margins;
+ },
+
+ highlight : function(){
+ this.el.addClass('ylayout-panel-dragover');
+ },
+
+ unhighlight : function(){
+ this.el.removeClass('ylayout-panel-dragover');
+ },
+
+ updateBox : function(box){
+ this.box = box;
+ if(!this.collapsed){
+ this.el.dom.style.left = box.x + 'px';
+ this.el.dom.style.top = box.y + 'px';
+ this.el.setSize(box.width, box.height);
+ var bodyHeight = this.titleEl.isVisible() ? box.height - (this.titleEl.getHeight()||0) : box.height;
+ bodyHeight -= this.el.getBorderWidth('tb');
+ bodyWidth = box.width - this.el.getBorderWidth('rl');
+ this.bodyEl.setHeight(bodyHeight);
+ this.bodyEl.setWidth(bodyWidth);
+ var tabHeight = bodyHeight;
+ if(this.tabs){
+ tabHeight = this.tabs.syncHeight(bodyHeight);
+ if(YAHOO.ext.util.Browser.isIE) this.tabs.el.repaint();
+ }
+ this.panelSize = {width: bodyWidth, height: tabHeight};
+ if(this.activePanel){
+ this.activePanel.setSize(bodyWidth, tabHeight);
+ }
+ }else{
+ this.collapsedEl.dom.style.left = box.x + 'px';
+ this.collapsedEl.dom.style.top = box.y + 'px';
+ this.collapsedEl.setSize(box.width, box.height);
+ }
+ if(this.tabs){
+ this.tabs.autoSizeTabs();
+ }
+ },
+
+ /**
+ * Returns the container element for this region.
+ * @return {YAHOO.ext.Element}
+ */
+ getEl : function(){
+ return this.el;
+ },
+
+ /**
+ * Hides this region.
+ */
+ hide : function(){
+ if(!this.collapsed){
+ this.el.dom.style.left = '-2000px';
+ this.el.hide();
+ }else{
+ this.collapsedEl.dom.style.left = '-2000px';
+ this.collapsedEl.hide();
+ }
+ this.visible = false;
+ this.fireEvent('visibilitychange', this, false);
+ },
+
+ /**
+ * Shows this region if it was previously hidden.
+ */
+ show : function(){
+ if(!this.collapsed){
+ this.el.show();
+ }else{
+ this.collapsedEl.show();
+ }
+ this.visible = true;
+ this.fireEvent('visibilitychange', this, true);
+ },
+
+ closeClicked : function(){
+ if(this.activePanel){
+ this.remove(this.activePanel);
+ }
+ },
+
+ collapseClick : function(e){
+ if(this.isSlid){
+ e.stopPropagation();
+ this.slideIn();
+ }else{
+ e.stopPropagation();
+ this.slideOut();
+ }
+ },
+
+ /**
+ * Collapses this region.
+ * @param {Boolean} skipAnim (optional) true to collapse the element without animation (if animate is true)
+ */
+ collapse : function(skipAnim){
+ if(this.collapsed) return;
+ this.collapsed = true;
+ if(this.split){
+ this.split.el.hide();
+ }
+ if(this.config.animate && skipAnim !== true){
+ this.fireEvent('invalidated', this);
+ this.animateCollapse();
+ }else{
+ this.el.setLocation(-20000,-20000);
+ this.el.hide();
+ this.collapsedEl.show();
+ this.fireEvent('collapsed', this);
+ this.fireEvent('invalidated', this);
+ }
+ },
+
+ animateCollapse : function(){
+ // overridden
+ },
+
+ /**
+ * Expand this region if it was previously collapsed.
+ * @param {YAHOO.ext.EventObject} e The event that triggered the expand (or null if calling manually)
+ * @param {Boolean} skipAnim (optional) true to expand the element without animation (if animate is true)
+ */
+ expand : function(e, skipAnim){
+ if(e) e.stopPropagation();
+ if(!this.collapsed) return;
+ if(this.isSlid){
+ this.slideIn(this.expand.createDelegate(this));
+ return;
+ }
+ this.collapsed = false;
+ this.el.show();
+ if(this.config.animate && skipAnim !== true){
+ this.animateExpand();
+ }else{
+ if(this.split){
+ this.split.el.show();
+ }
+ this.collapsedEl.setLocation(-2000,-2000);
+ this.collapsedEl.hide();
+ this.fireEvent('invalidated', this);
+ this.fireEvent('expanded', this);
+ }
+ },
+
+ animateExpand : function(){
+ // overridden
+ },
+
+ initTabs : function(){
+ this.bodyEl.setStyle('overflow', 'hidden');
+ var ts = new YAHOO.ext.TabPanel(this.bodyEl.dom, this.bottomTabs);
+ if(this.config.hideTabs){
+ ts.stripWrap.setDisplayed(false);
+ }
+ this.tabs = ts;
+ ts.resizeTabs = this.config.resizeTabs === true;
+ ts.minTabWidth = this.config.minTabWidth || 40;
+ ts.maxTabWidth = this.config.maxTabWidth || 250;
+ ts.preferredTabWidth = this.config.preferredTabWidth || 150;
+ ts.monitorResize = false;
+ ts.bodyEl.setStyle('overflow', this.config.autoScroll ? 'auto' : 'hidden');
+ this.panels.each(this.initPanelAsTab, this);
+ },
+
+ initPanelAsTab : function(panel){
+ var ti = this.tabs.addTab(panel.getEl().id, panel.getTitle(), null,
+ this.config.closeOnTab && panel.isClosable());
+ ti.on('activate', function(){
+ this.setActivePanel(panel);
+ }, this, true);
+ if(this.config.closeOnTab){
+ ti.on('beforeclose', function(t, e){
+ e.cancel = true;
+ this.remove(panel);
+ }, this, true);
+ }
+ return ti;
+ },
+
+ updatePanelTitle : function(panel, title){
+ if(this.activePanel == panel){
+ this.updateTitle(title);
+ }
+ if(this.tabs){
+ this.tabs.getTab(panel.getEl().id).setText(title);
+ }
+ },
+
+ updateTitle : function(title){
+ if(this.titleTextEl && !this.config.title){
+ this.titleTextEl.innerHTML = (typeof title != 'undefined' && title.length > 0 ? title : " ");
+ }
+ },
+
+ setActivePanel : function(panel){
+ panel = this.getPanel(panel);
+ if(this.activePanel && this.activePanel != panel){
+ this.activePanel.setActiveState(false);
+ }
+ this.activePanel = panel;
+ panel.setActiveState(true);
+ if(this.panelSize){
+ panel.setSize(this.panelSize.width, this.panelSize.height);
+ }
+ this.closeBtn.setVisible(!this.config.closeOnTab && !this.isSlid && panel.isClosable());
+ this.updateTitle(panel.getTitle());
+ this.fireEvent('panelactivated', this, panel);
+ },
+
+ /**
+ * Show the specified panel.
+ * @param {Number/String/ContentPanel} panelId The panels index, id or the panel itself
+ * @return {YAHOO.ext.ContentPanel} The shown panel or null
+ */
+ showPanel : function(panel){
+ if(panel = this.getPanel(panel)){
+ if(this.tabs){
+ this.tabs.activate(panel.getEl().id);
+ }else{
+ this.setActivePanel(panel);
+ }
+ }
+ return panel;
+ },
+
+ /**
+ * Get the active panel for this region.
+ * @return {YAHOO.ext.ContentPanel} The active panel or null
+ */
+ getActivePanel : function(){
+ return this.activePanel;
+ },
+
+ validateVisibility : function(){
+ if(this.panels.getCount() < 1){
+ this.updateTitle(' ');
+ this.closeBtn.hide();
+ this.hide();
+ }else{
+ if(!this.isVisible()){
+ this.show();
+ }
+ }
+ },
+
+ /**
+ * Add the passed ContentPanel(s)
+ * @param {ContentPanel...} panel The ContentPanel(s) to add (you can pass more than one)
+ * @return {YAHOO.ext.ContentPanel} The panel added (if only one was added)
+ */
+ add : function(panel){
+ if(arguments.length > 1){
+ for(var i = 0, len = arguments.length; i < len; i++) {
+ this.add(arguments[i]);
+ }
+ return null;
+ }
+ if(this.hasPanel(panel)){
+ this.showPanel(panel);
+ return panel;
+ }
+ panel.setRegion(this);
+ this.panels.add(panel);
+ if(this.panels.getCount() == 1 && !this.config.alwaysShowTabs){
+ this.bodyEl.dom.appendChild(panel.getEl().dom);
+ if(panel.background !== true){
+ this.setActivePanel(panel);
+ }
+ this.fireEvent('paneladded', this, panel);
+ return panel;
+ }
+ if(!this.tabs){
+ this.initTabs();
+ }else{
+ this.initPanelAsTab(panel);
+ }
+ if(panel.background !== true){
+ this.tabs.activate(panel.getEl().id);
+ }
+ this.fireEvent('paneladded', this, panel);
+ return panel;
+ },
+
+ /**
+ * Hides the tab for the specified panel.
+ * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
+ */
+ hidePanel : function(panel){
+ if(this.tabs && (panel = this.getPanel(panel))){
+ this.tabs.hideTab(panel.getEl().id);
+ }
+ },
+
+ /**
+ * Unhides the tab for a previously hidden panel.
+ * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
+ */
+ unhidePanel : function(panel){
+ if(this.tabs && (panel = this.getPanel(panel))){
+ this.tabs.unhideTab(panel.getEl().id);
+ }
+ },
+
+ clearPanels : function(){
+ while(this.panels.getCount() > 0){
+ this.remove(this.panels.first());
+ }
+ },
+
+ /**
+ * Removes the specified panel. If preservePanel is not true (either here or in the config), the panel is destroyed.
+ * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
+ * @param {Boolean} preservePanel Overrides the config preservePanel option
+ * @return {YAHOO.ext.ContentPanel} The panel that was removed
+ */
+ remove : function(panel, preservePanel){
+ panel = this.getPanel(panel);
+ if(!panel){
+ return null;
+ }
+ var e = {};
+ this.fireEvent('beforeremove', this, panel, e);
+ if(e.cancel === true){
+ return null;
+ }
+ preservePanel = (typeof preservePanel != 'undefined' ? preservePanel : (this.config.preservePanels === true || panel.preserve === true));
+ var panelId = panel.getId();
+ this.panels.removeKey(panelId);
+ if(preservePanel){
+ document.body.appendChild(panel.getEl().dom);
+ }
+ if(this.tabs){
+ this.tabs.removeTab(panel.getEl().id);
+ }else if (!preservePanel){
+ this.bodyEl.dom.removeChild(panel.getEl().dom);
+ }
+ if(this.panels.getCount() == 1 && this.tabs && !this.config.alwaysShowTabs){
+ var p = this.panels.first();
+ var tempEl = document.createElement('span'); // temp holder to keep IE from deleting the node
+ tempEl.appendChild(p.getEl().dom);
+ this.bodyEl.update('');
+ this.bodyEl.dom.appendChild(p.getEl().dom);
+ tempEl = null;
+ this.updateTitle(p.getTitle());
+ this.tabs = null;
+ this.bodyEl.setStyle('overflow', this.config.autoScroll ? 'auto' : 'hidden');
+ this.setActivePanel(p);
+ }
+ panel.setRegion(null);
+ if(this.activePanel == panel){
+ this.activePanel = null;
+ }
+ if(this.config.autoDestroy !== false && preservePanel !== true){
+ try{panel.destroy();}catch(e){}
+ }
+ this.fireEvent('panelremoved', this, panel);
+ return panel;
+ },
+
+ /**
+ * Returns the TabPanel component used by this region
+ * @return {YAHOO.ext.TabPanel}
+ */
+ getTabs : function(){
+ return this.tabs;
+ },
+
+ createTool : function(parentEl, className){
+ var btn = YAHOO.ext.DomHelper.append(parentEl, {tag: 'div', cls: 'ylayout-tools-button',
+ children: [{tag: 'div', cls: 'ylayout-tools-button-inner ' + className, html: ' '}]}, true);
+ btn.addClassOnOver('ylayout-tools-button-over');
+ return btn;
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/layout/LayoutStateManager.js b/frontend/beta/js/YUI-extensions/layout/LayoutStateManager.js
new file mode 100644
index 0000000..ea22235
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/layout/LayoutStateManager.js
@@ -0,0 +1,68 @@
+/*
+ * Private internal class for reading and applying state
+ */
+YAHOO.ext.LayoutStateManager = function(layout){
+ // default empty state
+ this.state = {
+ north: {},
+ south: {},
+ east: {},
+ west: {}
+ };
+};
+
+YAHOO.ext.LayoutStateManager.prototype = {
+ init : function(layout, provider){
+ this.provider = provider;
+ var state = provider.get(layout.id+'-layout-state');
+ if(state){
+ var wasUpdating = layout.isUpdating();
+ if(!wasUpdating){
+ layout.beginUpdate();
+ }
+ for(var key in state){
+ if(typeof state[key] != 'function'){
+ var rstate = state[key];
+ var r = layout.getRegion(key);
+ if(r && rstate){
+ if(rstate.size){
+ r.resizeTo(rstate.size);
+ }
+ if(rstate.collapsed == true){
+ r.collapse(true);
+ }else{
+ r.expand(null, true);
+ }
+ }
+ }
+ }
+ if(!wasUpdating){
+ layout.endUpdate();
+ }
+ this.state = state;
+ }
+ this.layout = layout;
+ layout.on('regionresized', this.onRegionResized, this, true);
+ layout.on('regioncollapsed', this.onRegionCollapsed, this, true);
+ layout.on('regionexpanded', this.onRegionExpanded, this, true);
+ },
+
+ storeState : function(){
+ this.provider.set(this.layout.id+'-layout-state', this.state);
+ },
+
+ onRegionResized : function(region, newSize){
+ this.state[region.getPosition()].size = newSize;
+ this.storeState();
+ },
+
+ onRegionCollapsed : function(region){
+ this.state[region.getPosition()].collapsed = true;
+ this.storeState();
+ },
+
+ onRegionExpanded : function(region){
+ this.state[region.getPosition()].collapsed = false;
+ this.storeState();
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/layout/SplitLayoutRegion.js b/frontend/beta/js/YUI-extensions/layout/SplitLayoutRegion.js
new file mode 100644
index 0000000..6b8ce9e
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/layout/SplitLayoutRegion.js
@@ -0,0 +1,282 @@
+/**
+ * @class YAHOO.ext.SplitLayoutRegion
+ * @extends YAHOO.ext.LayoutRegion
+ * Adds a splitbar and other (private) useful functionality to a LayoutRegion
+ */
+YAHOO.ext.SplitLayoutRegion = function(mgr, config, pos, cursor){
+ this.cursor = cursor;
+ YAHOO.ext.SplitLayoutRegion.superclass.constructor.call(this, mgr, config, pos);
+ if(config.split){
+ this.hide();
+ }
+};
+
+YAHOO.extendX(YAHOO.ext.SplitLayoutRegion, YAHOO.ext.LayoutRegion, {
+ applyConfig : function(config){
+ YAHOO.ext.SplitLayoutRegion.superclass.applyConfig.call(this, config);
+ if(config.split){
+ if(!this.split){
+ var splitEl = YAHOO.ext.DomHelper.append(this.mgr.el.dom,
+ {tag: 'div', id: this.el.id + '-split', cls: 'ylayout-split ylayout-split-'+this.position, html: ' '});
+ /** The SplitBar for this region @type YAHOO.ext.SplitBar */
+ this.split = new YAHOO.ext.SplitBar(splitEl, this.el);
+ this.split.onMoved.subscribe(this.onSplitMove, this, true);
+ this.split.useShim = config.useShim === true;
+ YAHOO.util.Dom.setStyle([this.split.el.dom, this.split.proxy], 'cursor', this.cursor);
+ this.split.getMaximumSize = this.getMaxSize.createDelegate(this);
+ }
+ if(typeof config.minSize != 'undefined'){
+ this.split.minSize = config.minSize;
+ }
+ if(typeof config.maxSize != 'undefined'){
+ this.split.maxSize = config.maxSize;
+ }
+ }
+ },
+
+ getMaxSize : function(){
+ var cmax = this.config.maxSize || 10000;
+ var center = this.mgr.getRegion('center');
+ return Math.min(cmax, (this.el.getWidth()+center.getEl().getWidth())-center.getMinWidth());
+ },
+
+ onSplitMove : function(split, newSize){
+ this.fireEvent('resized', this, newSize);
+ },
+
+ /**
+ * Returns the SplitBar for this region.
+ * @return {YAHOO.ext.SplitBar}
+ */
+ getSplitBar : function(){
+ return this.split;
+ },
+
+ hide : function(){
+ if(this.split){
+ this.split.el.setLocation(-2000,-2000);
+ this.split.el.hide();
+ }
+ YAHOO.ext.SplitLayoutRegion.superclass.hide.call(this);
+ },
+
+ show : function(){
+ if(this.split){
+ this.split.el.show();
+ }
+ YAHOO.ext.SplitLayoutRegion.superclass.show.call(this);
+ },
+
+ beforeSlide: function(){
+ if(YAHOO.ext.util.Browser.isGecko){// firefox overflow auto bug workaround
+ this.bodyEl.clip();
+ if(this.tabs) this.tabs.bodyEl.clip();
+ if(this.activePanel){
+ this.activePanel.getEl().clip();
+
+ if(this.activePanel.beforeSlide){
+ this.activePanel.beforeSlide();
+ }
+ }
+ }
+ },
+
+ afterSlide : function(){
+ if(YAHOO.ext.util.Browser.isGecko){// firefox overflow auto bug workaround
+ this.bodyEl.unclip();
+ if(this.tabs) this.tabs.bodyEl.unclip();
+ if(this.activePanel){
+ this.activePanel.getEl().unclip();
+ if(this.activePanel.afterSlide){
+ this.activePanel.afterSlide();
+ }
+ }
+ }
+ },
+
+ slideOut : function(){
+ if(!this.slideEl){
+ this.slideEl = new YAHOO.ext.Actor(
+ YAHOO.ext.DomHelper.append(this.mgr.el.dom, {tag: 'div', cls:'ylayout-slider'}));
+ if(this.config.autoHide !== false){
+ var slideInTask = new YAHOO.ext.util.DelayedTask(this.slideIn, this);
+ this.slideEl.mon('mouseout', function(e){
+ var to = e.getRelatedTarget();
+ if(to && to != this.slideEl.dom && !YAHOO.util.Dom.isAncestor(this.slideEl.dom, to)){
+ slideInTask.delay(500);
+ }
+ }, this, true);
+ this.slideEl.mon('mouseover', function(e){
+ slideInTask.cancel();
+ }, this, true);
+ }
+ }
+ var sl = this.slideEl, c = this.collapsedEl, cm = this.cmargins;
+ this.isSlid = true;
+ this.snapshot = {
+ 'left': this.el.getLeft(true),
+ 'top': this.el.getTop(true),
+ 'colbtn': this.collapseBtn.isVisible(),
+ 'closebtn': this.closeBtn.isVisible()
+ };
+ this.collapseBtn.hide();
+ this.closeBtn.hide();
+ this.el.show();
+ this.el.setLeftTop(0,0);
+ sl.startCapture(true);
+ var size;
+ switch(this.position){
+ case 'west':
+ sl.setLeft(c.getRight(true));
+ sl.setTop(c.getTop(true));
+ size = this.el.getWidth();
+ break;
+ case 'east':
+ sl.setRight(this.mgr.getViewSize().width-c.getLeft(true));
+ sl.setTop(c.getTop(true));
+ size = this.el.getWidth();
+ break;
+ case 'north':
+ sl.setLeft(c.getLeft(true));
+ sl.setTop(c.getBottom(true));
+ size = this.el.getHeight();
+ break;
+ case 'south':
+ sl.setLeft(c.getLeft(true));
+ sl.setBottom(this.mgr.getViewSize().height-c.getTop(true));
+ size = this.el.getHeight();
+ break;
+ }
+ sl.dom.appendChild(this.el.dom);
+ YAHOO.util.Event.on(document.body, 'click', this.slideInIf, this, true);
+ sl.setSize(this.el.getWidth(), this.el.getHeight());
+ this.beforeSlide();
+ if(this.activePanel){
+ this.activePanel.setSize(this.bodyEl.getWidth(), this.bodyEl.getHeight());
+ }
+ sl.slideShow(this.getAnchor(), size, this.slideDuration, null, false);
+ sl.play(function(){
+ this.afterSlide();
+ }.createDelegate(this));
+ },
+
+ slideInIf : function(e){
+ var t = YAHOO.util.Event.getTarget(e);
+ if(!YAHOO.util.Dom.isAncestor(this.el.dom, t)){
+ this.slideIn();
+ }
+ },
+
+ slideIn : function(callback){
+ if(this.isSlid && !this.slideEl.playlist.isPlaying()){
+ YAHOO.util.Event.removeListener(document.body, 'click', this.slideInIf, this, true);
+ this.slideEl.startCapture(true);
+ this.slideEl.slideHide(this.getAnchor(), this.slideDuration, null);
+ this.beforeSlide();
+ this.slideEl.play(function(){
+ this.isSlid = false;
+ this.el.setPositioning(this.snapshot);
+ this.collapseBtn.setVisible(this.snapshot.colbtn);
+ this.closeBtn.setVisible(this.snapshot.closebtn);
+ this.afterSlide();
+ this.mgr.el.dom.appendChild(this.el.dom);
+ if(typeof callback == 'function'){
+ callback();
+ }
+ }.createDelegate(this));
+ }
+ },
+
+ animateExpand : function(){
+ var em = this.margins, cm = this.cmargins;
+ var c = this.collapsedEl, el = this.el;
+ var direction, distance;
+ switch(this.position){
+ case 'west':
+ direction = 'right';
+ el.setLeft(-(el.getWidth() + (em.right+em.left)));
+ el.setTop(c.getTop(true)-cm.top+em.top);
+ distance = el.getWidth() + (em.right+em.left);
+ break;
+ case 'east':
+ direction = 'left';
+ el.setLeft(this.mgr.getViewSize().width + em.left);
+ el.setTop(c.getTop(true)-cm.top+em.top);
+ distance = el.getWidth() + (em.right+em.left);
+ break;
+ case 'north':
+ direction = 'down';
+ el.setLeft(em.left);
+ el.setTop(-(el.getHeight() + (em.top+em.bottom)));
+ distance = el.getHeight() + (em.top+em.bottom);
+ break;
+ case 'south':
+ direction = 'up';
+ el.setLeft(em.left);
+ el.setTop(this.mgr.getViewSize().height + em.top);
+ distance = el.getHeight() + (em.top+em.bottom);
+ break;
+ }
+ this.beforeSlide();
+ el.setStyle('z-index', '100');
+ el.show();
+ c.setLocation(-2000,-2000);
+ c.hide();
+ el.move(direction, distance, true, this.duration, function(){
+ this.afterSlide();
+ el.setStyle('z-index', '');
+ if(this.split){
+ this.split.el.show();
+ }
+ this.fireEvent('invalidated', this);
+ this.fireEvent('expanded', this);
+ }.createDelegate(this), this.config.easing || YAHOO.util.Easing.easeOut);
+ },
+
+ animateCollapse : function(){
+ var em = this.margins, cm = this.cmargins;
+ var c = this.collapsedEl, el = this.el;
+ var direction, distance;
+ switch(this.position){
+ case 'west':
+ direction = 'left';
+ distance = el.getWidth() + (em.right+em.left);
+ break;
+ case 'east':
+ direction = 'right';
+ distance = el.getWidth() + (em.right+em.left);
+ break;
+ case 'north':
+ direction = 'up';
+ distance = el.getHeight() + (em.top+em.bottom);
+ break;
+ case 'south':
+ direction = 'down';
+ distance = el.getHeight() + (em.top+em.bottom);
+ break;
+ }
+ this.el.setStyle('z-index', '100');
+ this.beforeSlide();
+ this.el.move(direction, distance, true, this.duration, function(){
+ this.afterSlide();
+ this.el.setStyle('z-index', '');
+ this.el.setLocation(-20000,-20000);
+ this.el.hide();
+ this.collapsedEl.show();
+ this.fireEvent('collapsed', this);
+ }.createDelegate(this), YAHOO.util.Easing.easeIn);
+ },
+
+ getAnchor : function(){
+ switch(this.position){
+ case 'west':
+ return 'left';
+ case 'east':
+ return 'right';
+ case 'north':
+ return 'top';
+ case 'south':
+ return 'bottom';
+ }
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/AsyncTreeNode.js b/frontend/beta/js/YUI-extensions/tree/AsyncTreeNode.js
new file mode 100644
index 0000000..5d48b00
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/AsyncTreeNode.js
@@ -0,0 +1,58 @@
+YAHOO.ext.tree.AsyncTreeNode = function(config){
+ this.loaded = false;
+ this.loading = false;
+ YAHOO.ext.tree.AsyncTreeNode.superclass.constructor.apply(this, arguments);
+ this.events['beforeload'] = true;
+ this.events['load'] = true;
+};
+YAHOO.extendX(YAHOO.ext.tree.AsyncTreeNode, YAHOO.ext.tree.TreeNode, {
+ expand : function(deep, anim, callback){
+ if(this.loading){ // if an async load is already running, waiting til it's done
+ var timer;
+ var f = function(){
+ if(!this.loading){ // done loading
+ clearInterval(timer);
+ this.expand(deep, anim, callback);
+ }
+ }.createDelegate(this);
+ timer = setInterval(f, 200);
+ }
+ if(!this.loaded){
+ if(this.fireEvent('beforeload', this) === false){
+ return;
+ }
+ this.loading = true;
+ this.ui.beforeLoad(this);
+ var loader = this.loader || this.attributes.loader || this.getOwnerTree().getLoader();
+ if(loader){
+ loader.load(this, this.loadComplete.createDelegate(this, [deep, anim, callback]));
+ return;
+ }
+ }
+ YAHOO.ext.tree.AsyncTreeNode.superclass.expand.call(this, deep, anim, callback);
+ },
+
+ isLoading : function(){
+ return this.loading;
+ },
+
+ loadComplete : function(deep, anim, callback){
+ this.loading = false;
+ this.loaded = true;
+ this.ui.afterLoad(this);
+ this.fireEvent('load', this);
+ this.expand(deep, anim, callback);
+ },
+
+ isLoaded : function(){
+ return this.loaded;
+ },
+
+ hasChildNodes : function(){
+ if(!this.isLeaf() && !this.loaded){
+ return true;
+ }else{
+ return YAHOO.ext.tree.AsyncTreeNode.superclass.hasChildNodes.call(this);
+ }
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeDragZone.js b/frontend/beta/js/YUI-extensions/tree/TreeDragZone.js
new file mode 100644
index 0000000..9b77b3c
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeDragZone.js
@@ -0,0 +1,43 @@
+YAHOO.ext.tree.TreeDragZone = function(tree, config){
+ YAHOO.ext.tree.TreeDragZone.superclass.constructor.call(this, tree.getEl(), config);
+ this.tree = tree;
+};
+
+YAHOO.extendX(YAHOO.ext.tree.TreeDragZone, YAHOO.ext.dd.DragZone, {
+ ddGroup : 'TreeDD',
+
+ onBeforeDrag : function(data, e){
+ var n = data.node;
+ return n && n.draggable && !n.disabled;
+ },
+
+ onInitDrag : function(e){
+ var data = this.dragData;
+ this.tree.getSelectionModel().select(data.node);
+ this.proxy.update('');
+ data.node.ui.appendDDGhost(this.proxy.ghost.dom);
+ this.tree.fireEvent('startdrag', this.tree, data.node, e);
+ },
+
+ getRepairXY : function(e, data){
+ return data.node.ui.getDDRepairXY();
+ },
+
+ onEndDrag : function(data, e){
+ this.tree.fireEvent('enddrag', this.tree, data.node, e);
+ },
+
+ onValidDrop : function(dd, e, id){
+ this.tree.fireEvent('dragdrop', this.tree, this.dragData.node, dd, e);
+ this.hideProxy();
+ },
+
+ beforeInvalidDrop : function(e, id){
+ if(YAHOO.util.Anim){
+ // this scrolls the original position back into view
+ var sm = this.tree.getSelectionModel();
+ sm.clearSelections();
+ sm.select(this.dragData.node);
+ }
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeDropZone.js b/frontend/beta/js/YUI-extensions/tree/TreeDropZone.js
new file mode 100644
index 0000000..91c24e1
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeDropZone.js
@@ -0,0 +1,228 @@
+YAHOO.ext.tree.TreeDropZone = function(tree, config){
+ this.allowParentInsert = false;
+ this.allowContainerDrop = false;
+ this.appendOnly = false;
+ YAHOO.ext.tree.TreeDropZone.superclass.constructor.call(this, tree.container, config);
+ this.tree = tree;
+ this.lastInsertClass = 'ytree-no-status';
+ this.dragOverData = {};
+};
+
+YAHOO.extendX(YAHOO.ext.tree.TreeDropZone, YAHOO.ext.dd.DropZone, {
+ ddGroup : 'TreeDD',
+
+ expandDelay : 1000,
+
+ expandNode : function(node){
+ if(node.hasChildNodes() && !node.isExpanded()){
+ node.expand(false, null, this.triggerCacheRefresh.createDelegate(this));
+ }
+ },
+
+ queueExpand : function(node){
+ this.expandProcId = this.expandNode.defer(this.expandDelay, this, [node]);
+ },
+
+ cancelExpand : function(){
+ if(this.expandProcId){
+ clearTimeout(this.expandProcId);
+ this.expandProcId = false;
+ }
+ },
+
+ isValidDropPoint : function(n, pt, dd, e, data){
+ if(!n || !data){ return false; }
+ var targetNode = n.node;
+ var dropNode = data.node;
+ // default drop rules
+ if(!(targetNode && targetNode.isTarget && pt)){
+ return false;
+ }
+ if(pt == 'append' && targetNode.allowChildren === false){
+ return false;
+ }
+ if((pt == 'above' || pt == 'below') && (targetNode.parentNode && targetNode.parentNode.allowChildren === false)){
+ return false;
+ }
+ if(dropNode && (targetNode == dropNode || dropNode.contains(targetNode))){
+ return false;
+ }
+ // reuse the object
+ var overEvent = this.dragOverData;
+ overEvent.tree = this.tree;
+ overEvent.target = targetNode;
+ overEvent.data = data;
+ overEvent.point = pt;
+ overEvent.source = dd;
+ overEvent.rawEvent = e;
+ overEvent.dropNode = dropNode;
+ overEvent.cancel = false;
+ var result = this.tree.fireEvent('nodedragover', overEvent);
+ return overEvent.cancel === false && result !== false;
+ },
+
+ getDropPoint : function(e, n, dd){
+ var tn = n.node;
+ if(tn.isRoot){
+ return tn.allowChildren !== false ? 'ap-pend' : false; // always append for root
+ }
+ var dragEl = n.ddel;
+ var t = YAHOO.util.Dom.getY(dragEl), b = t + dragEl.offsetHeight;
+ var y = YAHOO.util.Event.getPageY(e);
+ var noAppend = tn.allowChildren === false || tn.isLeaf();
+ if(this.appendOnly || tn.parentNode.allowChildren === false){
+ return noAppend ? false : 'append';
+ }
+ var noBelow = false;
+ if(!this.allowParentInsert){
+ noBelow = tn.hasChildNodes() && tn.isExpanded();
+ }
+ var q = (b - t) / (noAppend ? 2 : 3);
+ if(y >= t && y < t + q){
+ return 'above';
+ }else if(!noBelow && (noAppend || y >= b-q && y <= b)){
+ return 'below';
+ }else{
+ return 'append';
+ }
+ return false;
+ },
+
+ onNodeEnter : function(n, dd, e, data){
+ this.cancelExpand();
+ },
+
+ onNodeOver : function(n, dd, e, data){
+ var pt = this.getDropPoint(e, n, dd);
+ var node = n.node;
+
+ // auto node expand check
+ if(!this.expandProcId && pt == 'append' && node.hasChildNodes() && !n.node.isExpanded()){
+ this.queueExpand(node);
+ }else if(pt != 'append'){
+ this.cancelExpand();
+ }
+
+ // set the insert point style on the target node
+ var returnCls = this.dropNotAllowed;
+ if(this.isValidDropPoint(n, pt, dd, e, data)){
+ if(pt){
+ var el = n.ddel;
+ var cls, returnCls;
+ if(pt == 'above'){
+ returnCls = n.node.isFirst() ? 'ytree-drop-ok-above' : 'ytree-drop-ok-between';
+ cls = 'ytree-drag-insert-above';
+ }else if(pt == 'below'){
+ returnCls = n.node.isLast() ? 'ytree-drop-ok-below' : 'ytree-drop-ok-between';
+ cls = 'ytree-drag-insert-below';
+ }else{
+ returnCls = 'ytree-drop-ok-append';
+ cls = 'ytree-drag-append';
+ }
+ if(this.lastInsertClass != cls){
+ YAHOO.util.Dom.replaceClass(el, this.lastInsertClass, cls);
+ this.lastInsertClass = cls;
+ }
+ }
+ }
+ return returnCls;
+ },
+
+ onNodeOut : function(n, dd, e, data){
+ this.cancelExpand();
+ this.removeDropIndicators(n);
+ },
+
+ onNodeDrop : function(n, dd, e, data){
+ var point = this.getDropPoint(e, n, dd);
+ var targetNode = n.node;
+ targetNode.ui.startDrop();
+ if(!this.isValidDropPoint(n, point, dd, e, data)){
+ targetNode.ui.endDrop();
+ return false;
+ }
+ // first try to find the drop node
+ var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, point, e) : null);
+ var dropEvent = {
+ tree : this.tree,
+ target: targetNode,
+ data: data,
+ point: point,
+ source: dd,
+ rawEvent: e,
+ dropNode: dropNode,
+ cancel: dropNode ? false : true
+ };
+ var retval = this.tree.fireEvent('beforenodedrop', dropEvent);
+ if(retval === false || dropEvent.cancel === true || !dropEvent.dropNode){
+ targetNode.ui.endDrop();
+ return false;
+ }
+ if(point == 'append' && !targetNode.isExpanded()){
+ targetNode.expand(false, null, function(){
+ this.completeDrop(dropEvent);
+ }.createDelegate(this));
+ }else{
+ this.completeDrop(dropEvent);
+ }
+ return true;
+ },
+
+ completeDrop : function(de){
+ var ns = de.dropNode, p = de.point, t = de.target;
+ if(!(ns instanceof Array)){
+ ns = [ns];
+ }
+ var n;
+ for(var i = 0, len = ns.length; i < len; i++){
+ n = ns[i];
+ if(p == 'above'){
+ t.parentNode.insertBefore(n, t);
+ }else if(p == 'below'){
+ t.parentNode.insertBefore(n, t.nextSibling);
+ }else{
+ t.appendChild(n);
+ }
+ }
+ n.select(); // select and highlight the last insert
+ if(this.tree.hlDrop){
+ n.ui.highlight();
+ }
+ t.ui.endDrop();
+ this.tree.fireEvent('nodedrop', de);
+ },
+
+ afterNodeMoved : function(dd, data, e, targetNode, dropNode){
+ if(this.tree.hlDrop){
+ dropNode.select();
+ dropNode.ui.highlight();
+ }
+ this.tree.fireEvent('nodedrop', this.tree, targetNode, data, dd, e);
+ },
+
+ getTree : function(){
+ return this.tree;
+ },
+
+ removeDropIndicators : function(n){
+ if(n && n.ddel){
+ var el = n.ddel;
+ YAHOO.util.Dom.removeClass(el, 'ytree-drag-insert-above');
+ YAHOO.util.Dom.removeClass(el, 'ytree-drag-insert-below');
+ YAHOO.util.Dom.removeClass(el, 'ytree-drag-append');
+ this.lastInsertClass = '_noclass';
+ }
+ },
+
+ beforeDragDrop : function(target, e, id){
+ this.cancelExpand();
+ return true;
+ },
+
+ afterRepair : function(data){
+ if(data){
+ data.node.ui.highlight();
+ }
+ this.hideProxy();
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeFilter.js b/frontend/beta/js/YUI-extensions/tree/TreeFilter.js
new file mode 100644
index 0000000..9eeb274
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeFilter.js
@@ -0,0 +1,105 @@
+/**
+ * This doesn't update the indent (lines) or expand collapse icons of the nodes
+ */
+YAHOO.ext.tree.TreeFilter = function(tree, config){
+ this.tree = tree;
+ this.filtered = {};
+ YAHOO.ext.util.Config.apply(this, config, {
+ clearBlank:false,
+ reverse:false,
+ autoClear:false,
+ remove:false
+ });
+};
+
+YAHOO.ext.tree.TreeFilter.prototype = {
+ /**
+ * Filter the data by a specific attribute.
+ * @param {String/RegExp} value Either string that the attribute value
+ * should start with or a RegExp to test against the attribute
+ * @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
+ * @param {TreeNode} startNode (optional) The node to start the filter at.
+ */
+ filter : function(value, attr, startNode){
+ attr = attr || 'text';
+ var f;
+ if(typeof value == 'string'){
+ var vlen = value.length;
+ // auto clear empty filter
+ if(vlen == 0 && this.clearBlank){
+ this.clearFilter();
+ return;
+ }
+ value = value.toLowerCase();
+ f = function(n){
+ return n.attributes[attr].substr(0, vlen).toLowerCase() == value;
+ }
+ }else if(value.exec){ // regex?
+ f = function(n){
+ return value.test(n.attributes[attr]);
+ }
+ }else{
+ throw 'Illegal filter type, must be string or regex';
+ }
+ this.filterBy(f, null, startNode);
+ },
+
+ /**
+ * Filter by a function. The passed function will be called with each
+ * node in the tree (or from the startNode). If the function returns true, the node is kept
+ * otherwise it is filtered. If a node is filtered, it's children are also filtered.
+ * @param {Function} fn The filter function
+ * @param {Object} scope (optional) The scope of the function (defaults to the current node)
+ */
+ filterBy : function(fn, scope, startNode){
+ startNode = startNode || this.tree.root;
+ if(this.autoClear){
+ this.clearFilter();
+ }
+ var af = this.filtered, rv = this.reverse;
+ var f = function(n){
+ if(n == startNode){
+ return true;
+ }
+ if(af[n.id]){
+ return false;
+ }
+ var m = fn.call(scope || n, n);
+ if(!m || rv){
+ af[n.id] = n;
+ n.ui.hide();
+ return false;
+ }
+ return true;
+ }
+ startNode.cascade(f);
+ if(this.remove){
+ for(var id in af){
+ if(typeof id != 'function'){
+ var n = af[id];
+ if(n && n.parentNode){
+ n.parentNode.removeChild(n);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Clears the current filter. Note: with the "remove" option
+ * set a filter cannot be cleared.
+ */
+ clear : function(){
+ var t = this.tree;
+ var af = this.filtered;
+ for(var id in af){
+ if(typeof id != 'function'){
+ var n = af[id];
+ if(n){
+ n.ui.show();
+ }
+ }
+ }
+ this.filtered = {};
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeLoader.js b/frontend/beta/js/YUI-extensions/tree/TreeLoader.js
new file mode 100644
index 0000000..34989bd
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeLoader.js
@@ -0,0 +1,107 @@
+YAHOO.ext.tree.TreeLoader = function(config){
+ this.baseParams = {};
+ this.requestMethod = 'POST';
+ YAHOO.ext.util.Config.apply(this, config);
+
+ this.events = {
+ 'beforeload' : true,
+ 'load' : true,
+ 'loadexception' : true
+ };
+};
+
+YAHOO.extendX(YAHOO.ext.tree.TreeLoader, YAHOO.ext.util.Observable, {
+ load : function(node, callback){
+ if(node.attributes.children){ // preloaded json children
+ var cs = node.attributes.children;
+ for(var i = 0, len = cs.length; i < len; i++){
+ node.appendChild(this.createNode(cs[i]));
+ }
+ if(typeof callback == 'function'){
+ callback();
+ }
+ }else if(this.dataUrl){
+ this.requestData(node, callback);
+ }
+ },
+
+ getParams: function(node){
+ var buf = [], bp = this.baseParams;
+ for(var key in bp){
+ if(typeof bp[key] != 'function'){
+ buf.push(encodeURIComponent(key), '=', encodeURIComponent(bp[key]), '&');
+ }
+ }
+ buf.push('node=', encodeURIComponent(node.id));
+ return buf.join('');
+ },
+
+ requestData : function(node, callback){
+ if(this.fireEvent('beforeload', this, node, callback) !== false){
+ var params = this.getParams(node);
+ var cb = {
+ success: this.handleResponse,
+ failure: this.handleFailure,
+ scope: this,
+ argument: {callback: callback, node: node}
+ };
+ this.transId = YAHOO.util.Connect.asyncRequest(this.requestMethod, this.dataUrl, cb, params);
+ }else{
+ // if the load is cancelled, make sure we notify
+ // the node that we are done
+ if(typeof callback == 'function'){
+ callback();
+ }
+ }
+ },
+
+ isLoading : function(){
+ return this.transId ? true : false;
+ },
+
+ abort : function(){
+ if(this.isLoading()){
+ YAHOO.util.Connect.abort(this.transId);
+ }
+ },
+
+ createNode : function(attr){
+ if(this.applyLoader !== false){
+ attr.loader = this;
+ }
+ return(attr.leaf ?
+ new YAHOO.ext.tree.TreeNode(attr) :
+ new YAHOO.ext.tree.AsyncTreeNode(attr));
+ },
+
+ processResponse : function(response, node, callback){
+ var json = response.responseText;
+ try {
+ var o = eval('('+json+')');
+ for(var i = 0, len = o.length; i < len; i++){
+ node.appendChild(this.createNode(o[i]));
+ }
+ if(typeof callback == 'function'){
+ callback();
+ }
+ }catch(e){
+ this.handleFailure(response);
+ }
+ },
+
+ handleResponse : function(response){
+ this.transId = false;
+ var a = response.argument;
+ this.processResponse(response, a.node, a.callback);
+ this.fireEvent('load', this, a.node, response);
+ },
+
+ handleFailure : function(response){
+ this.transId = false;
+ var a = response.argument;
+ this.fireEvent('loadexception', this, a.node, response);
+ if(typeof a.callback == 'function'){
+ a.callback();
+ }
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeNode.js b/frontend/beta/js/YUI-extensions/tree/TreeNode.js
new file mode 100644
index 0000000..c676481
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeNode.js
@@ -0,0 +1,300 @@
+/**
+ * @class YAHOO.ext.tree.TreeNode
+ * @extends YAHOO.ext.data.Node
+ * @cfg {Boolean} leaf true if this node is a leaf and does not have or cannot have children
+ * @cfg {Boolean} expanded true to start the node expanded
+ * @cfg {Boolean} draggable false to make this node undraggable if DD is on (default to true)
+ * @cfg {Boolean} isTarget false if this node cannot be drop on
+ * @cfg {Boolean} disabled true to start the node disabled
+ * @constructor
+ * @param {Object} attributes The attributes/config for the node
+ */
+YAHOO.ext.tree.TreeNode = function(attributes){
+ attributes = attributes || {};
+ if(typeof attributes == 'string'){
+ attributes = {text: attributes};
+ }
+ this.el = null;
+ this.childrenRendered = false;
+ this.rendered = false;
+ YAHOO.ext.tree.TreeNode.superclass.constructor.call(this, attributes);
+ this.expanded = attributes.expanded === true;
+ this.isTarget = attributes.isTarget !== false;
+ this.draggable = attributes.draggable !== false && attributes.allowDrag !== false;
+ this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false;
+ this.text = attributes.text;
+ this.disabled = attributes.disabled === true;
+
+ YAHOO.ext.util.Config.apply(this.events, {
+ 'textchange' : true,
+ 'beforeexpand' : true,
+ 'beforecollapse' : true,
+ 'expand' : true,
+ 'disabledchange' : true,
+ 'collapse' : true,
+ 'beforeclick':true,
+ 'click':true,
+ 'dblclick':true,
+ 'contentmenu':true,
+ 'beforechildrenrendered':true
+ });
+
+ var uiClass = this.attributes.uiProvider || YAHOO.ext.tree.TreeNodeUI;
+ this.ui = new uiClass(this);
+};
+YAHOO.extendX(YAHOO.ext.tree.TreeNode, YAHOO.ext.data.Node, {
+ isExpanded : function(){
+ return this.expanded;
+ },
+
+ getUI : function(){
+ return this.ui;
+ },
+
+ setFirstChild : function(node){
+ var of = this.firstChild;
+ YAHOO.ext.tree.TreeNode.superclass.setFirstChild.call(this, node);
+ if(this.childrenRendered && of && node != of){
+ of.renderIndent(true, true);
+ }
+ if(this.rendered){
+ this.renderIndent(true, true);
+ }
+ },
+
+ setLastChild : function(node){
+ var ol = this.lastChild;
+ YAHOO.ext.tree.TreeNode.superclass.setLastChild.call(this, node);
+ if(this.childrenRendered && ol && node != ol){
+ ol.renderIndent(true, true);
+ }
+ if(this.rendered){
+ this.renderIndent(true, true);
+ }
+ },
+
+ // these methods are overridden to provide lazy rendering support
+ appendChild : function(){
+ var node = YAHOO.ext.tree.TreeNode.superclass.appendChild.apply(this, arguments);
+ if(node && this.childrenRendered){
+ node.render();
+ }
+ this.ui.updateExpandIcon();
+ return node;
+ },
+
+ removeChild : function(node){
+ this.ownerTree.getSelectionModel().unselect(node);
+ YAHOO.ext.tree.TreeNode.superclass.removeChild.apply(this, arguments);
+ // if it's been rendered remove dom node
+ if(this.childrenRendered){
+ node.ui.remove();
+ }
+ if(this.childNodes.length < 1){
+ this.collapse(false, false);
+ }else{
+ this.ui.updateExpandIcon();
+ }
+ return node;
+ },
+
+ insertBefore : function(node, refNode){
+ var newNode = YAHOO.ext.tree.TreeNode.superclass.insertBefore.apply(this, arguments);
+ if(newNode && refNode && this.childrenRendered){
+ node.render();
+ }
+ this.ui.updateExpandIcon();
+ return newNode;
+ },
+
+ setText : function(text){
+ var oldText = this.text;
+ this.text = text;
+ this.attributes.text = text;
+ if(this.rendered){ // event without subscribing
+ this.ui.onTextChange(this, text, oldText);
+ }
+ this.fireEvent('textchange', this, text, oldText);
+ },
+
+ select : function(){
+ this.getOwnerTree().getSelectionModel().select(this);
+ },
+
+ unselect : function(){
+ this.getOwnerTree().getSelectionModel().unselect(this);
+ },
+
+ isSelected : function(){
+ return this.getOwnerTree().getSelectionModel().isSelected(node);
+ },
+
+ expand : function(deep, anim, callback){
+ if(!this.expanded){
+ if(this.fireEvent('beforeexpand', this, deep, anim) === false){
+ return;
+ }
+ if(!this.childrenRendered){
+ this.renderChildren();
+ }
+ this.expanded = true;
+ if((this.getOwnerTree().animate && anim !== false) || anim){
+ this.ui.animExpand(function(){
+ this.fireEvent('expand', this);
+ if(typeof callback == 'function'){
+ callback(this);
+ }
+ if(deep === true){
+ this.expandChildNodes(true);
+ }
+ }.createDelegate(this));
+ return;
+ }else{
+ this.ui.expand();
+ this.fireEvent('expand', this);
+ if(typeof callback == 'function'){
+ callback(this);
+ }
+ }
+ }else{
+ if(typeof callback == 'function'){
+ callback(this);
+ }
+ }
+ if(deep === true){
+ this.expandChildNodes(true);
+ }
+ },
+
+ collapse : function(deep, anim){
+ if(this.expanded && (!this.isRoot || (this.isRoot && this.getOwnerTree().rootVisible))){
+ if(this.fireEvent('beforecollapse', this, deep, anim) === false){
+ return;
+ }
+ this.expanded = false;
+ if((this.getOwnerTree().animate && anim !== false) || anim){
+ this.ui.animCollapse(function(){
+ this.fireEvent('collapse', this);
+ if(deep === true){
+ this.collapseChildNodes(true);
+ }
+ }.createDelegate(this));
+ return;
+ }else{
+ this.ui.collapse();
+ this.fireEvent('collapse', this);
+ }
+ }
+ if(deep === true){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ cs[i].collapse(true)
+ }
+ }
+ },
+
+ delayedExpand : function(delay){
+ if(!this.expandProcId){
+ this.expandProcId = this.expand.defer(delay, this);
+ }
+ },
+
+ cancelExpand : function(){
+ if(this.expandProcId){
+ clearTimeout(this.expandProcId);
+ }
+ this.expandProcId = false;
+ },
+
+ toggle : function(){
+ if(this.expanded){
+ this.collapse();
+ }else{
+ this.expand();
+ }
+ },
+
+ ensureVisible : function(){
+ if(this.parentNode){
+ this.parentNode.bubble(function(){
+ this.expand(false, false);
+ });
+ }
+ },
+
+ expandChildNodes : function(deep){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ cs[i].expand(deep);
+ }
+ },
+
+ collapseChildNodes : function(deep){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ cs[i].expand(deep);
+ }
+ },
+
+ disable : function(){
+ this.disabled = true;
+ this.unselect();
+ if(this.rendered && this.ui.onDisableChange){ // event without subscribing
+ this.ui.onDisableChange(this, true);
+ }
+ this.fireEvent('disabledchange', this, true);
+ },
+
+ enable : function(){
+ this.disabled = false;
+ if(this.rendered && this.ui.onDisableChange){ // event without subscribing
+ this.ui.onDisableChange(this, false);
+ }
+ this.fireEvent('disabledchange', this, false);
+ },
+
+ renderChildren : function(suppressEvent){
+ if(suppressEvent !== false){
+ this.fireEvent('beforechildrenrendered', this);
+ }
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++){
+ cs[i].render(true);
+ }
+ this.childrenRendered = true;
+ },
+
+ sort : function(fn, scope){
+ YAHOO.ext.tree.TreeNode.superclass.sort.apply(this, arguments);
+ if(this.childrenRendered){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++){
+ cs[i].render(true);
+ }
+ }
+ },
+
+ render : function(bulkRender){
+ this.ui.render(bulkRender);
+ if(!this.rendered){
+ this.rendered = true;
+ if(this.expanded){
+ this.expanded = false;
+ this.expand(false, false);
+ }
+ }
+ },
+
+ renderIndent : function(deep, refresh){
+ if(refresh){
+ this.ui.childIndent = null;
+ }
+ this.ui.renderIndent();
+ if(deep === true && this.childrenRendered){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++){
+ cs[i].renderIndent(true, refresh);
+ }
+ }
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeNodeUI.js b/frontend/beta/js/YUI-extensions/tree/TreeNodeUI.js
new file mode 100644
index 0000000..80927f4
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeNodeUI.js
@@ -0,0 +1,452 @@
+/**
+ * The TreeNode UI implementation is separate from the
+ * tree implementation. Unless you are customizing the tree UI,
+ * you should never have to use this directly.
+ */
+YAHOO.ext.tree.TreeNodeUI = function(node){
+ this.node = node;
+ this.rendered = false;
+ this.animating = false;
+};
+
+YAHOO.ext.tree.TreeNodeUI.prototype = {
+ emptyIcon : Ext.BLANK_IMAGE_URL,
+
+ removeChild : function(node){
+ if(this.rendered){
+ this.ctNode.removeChild(node.ui.getEl());
+ }
+ },
+
+ beforeLoad : function(){
+ YAHOO.util.Dom.addClass(this.elNode, 'ytree-node-loading');
+ },
+
+ afterLoad : function(){
+ YAHOO.util.Dom.removeClass(this.elNode, 'ytree-node-loading');
+ },
+
+ onTextChange : function(node, text, oldText){
+ if(this.rendered){
+ this.textNode.innerHTML = text;
+ }
+ },
+
+ onDisableChange : function(node, state){
+ this.disabled = state;
+ if(state){
+ YAHOO.util.Dom.addClass(this.elNode, 'ytree-node-disabled');
+ }else{
+ YAHOO.util.Dom.removeClass(this.elNode, 'ytree-node-disabled');
+ }
+ },
+
+ onSelectedChange : function(state){
+ if(state){
+ this.focus();
+ YAHOO.util.Dom.addClass(this.elNode, 'ytree-selected');
+ }else{
+ this.blur();
+ YAHOO.util.Dom.removeClass(this.elNode, 'ytree-selected');
+ }
+ },
+
+ onMove : function(tree, node, oldParent, newParent, index, refNode){
+ this.childIndent = null;
+ if(this.rendered){
+ var targetNode = newParent.ui.getContainer();
+ if(!targetNode){//target not rendered
+ this.holder = document.createElement('div');
+ this.holder.appendChild(this.wrap);
+ return;
+ }
+ var insertBefore = refNode ? refNode.ui.getEl() : null;
+ if(insertBefore){
+ targetNode.insertBefore(this.wrap, insertBefore);
+ }else{
+ targetNode.appendChild(this.wrap);
+ }
+ this.node.renderIndent(true);
+ }
+ },
+
+ remove : function(){
+ if(this.rendered){
+ this.holder = document.createElement('div');
+ this.holder.appendChild(this.wrap);
+ }
+ },
+
+ fireEvent : function(){
+ this.node.fireEvent.apply(this.node, arguments);
+ },
+
+ initEvents : function(){
+ this.node.on('move', this.onMove, this, true);
+ //this.node.on('hiddenchange', this.onHiddenChange, this, true);
+
+ // these were optimized out but a custom UI could use them
+ //this.node.on('remove', this.onChildRemoved, this, true);
+ //this.node.on('selectedstatechange', this.onSelectedChange, this, true);
+ //this.node.on('disabledchange', this.onDisableChange, this, true);
+ //this.node.on('textchange', this.onTextChange, this, true);
+
+ var E = YAHOO.util.Event;
+ var a = this.anchor;
+
+ var el = YAHOO.ext.Element.fly(a);
+
+ if(YAHOO.ext.util.Browser.isOpera){ // opera render bug ignores the CSS
+ el.setStyle('text-decoration', 'none');
+ }
+
+ el.mon('click', this.onClick, this, true);
+ el.mon('dblclick', this.onDblClick, this, true);
+ el.mon('contextmenu', this.onContextMenu, this, true);
+
+ //el.on('focus', function(){
+ // this.node.getOwnerTree().getSelectionModel().select(this.node);
+ //}, this, true);
+
+ var icon = YAHOO.ext.Element.fly(this.iconNode);
+ icon.mon('click', this.onClick, this, true);
+ icon.mon('dblclick', this.onDblClick, this, true);
+ icon.mon('contextmenu', this.onContextMenu, this, true);
+ E.on(this.ecNode, 'click', this.ecClick, this, true);
+
+ if(this.node.disabled){
+ YAHOO.util.Dom.addClass(this.elNode, 'ytree-node-disabled');
+ }
+ if(this.node.hidden){
+ YAHOO.util.Dom.addClass(this.elNode, 'ytree-node-disabled');
+ }
+ var dd = this.node.ownerTree.enableDD || this.node.ownerTree.enableDrag || this.node.ownerTree.enableDrop;
+ if(dd && (!this.node.isRoot || this.node.ownerTree.rootVisible)){
+ YAHOO.ext.dd.Registry.register(this.elNode, {
+ node: this.node,
+ handles: [this.iconNode, this.textNode],
+ isHandle: false
+ });
+ }
+ },
+
+ hide : function(){
+ if(this.rendered){
+ this.wrap.style.display = 'none';
+ }
+ },
+
+ show : function(){
+ if(this.rendered){
+ this.wrap.style.display = '';
+ }
+ },
+
+ onContextMenu : function(e){
+ e.preventDefault();
+ this.focus();
+ this.fireEvent('contextmenu', this.node, e);
+ },
+
+ onClick : function(e){
+ if(this.dropping){
+ return;
+ }
+ if(this.fireEvent('beforeclick', this.node, e) !== false){
+ if(!this.disabled && this.node.attributes.href){
+ this.focus();
+ this.fireEvent('click', this.node, e);
+ return;
+ }
+ e.preventDefault();
+ if(this.disabled){
+ return;
+ }
+ this.focus();
+ this.fireEvent('click', this.node, e);
+ }else{
+ e.stopEvent();
+ }
+ },
+
+ onDblClick : function(e){
+ e.preventDefault();
+ if(this.disabled){
+ return;
+ }
+ if(!this.animating && this.node.hasChildNodes()){
+ this.node.toggle();
+ }
+ this.fireEvent('dblclick', this.node, e);
+ },
+
+ ecClick : function(e){
+ if(!this.animating && this.node.hasChildNodes()){
+ this.node.toggle();
+ }
+ },
+
+ startDrop : function(){
+ this.dropping = true;
+ },
+
+ // delayed drop so the click event doesn't get fired on a drop
+ endDrop : function(){
+ setTimeout(function(){
+ this.dropping = false;
+ }.createDelegate(this), 50);
+ },
+
+ expand : function(){
+ this.updateExpandIcon();
+ this.ctNode.style.display = '';
+ },
+
+ focus : function(){
+ try{
+ this.anchor.focus();
+ }catch(e){}
+ },
+
+ blur : function(){
+ try{
+ this.anchor.blur();
+ }catch(e){}
+ },
+
+ animExpand : function(callback){
+ if(this.animating && this.anim){
+ this.anim.stop();
+ }
+ this.animating = true;
+ this.updateExpandIcon();
+ var ct = this.ctNode;
+ var cs = ct.style;
+ cs.position = 'absolute';
+ cs.visibility = 'hidden';
+ cs.display = '';
+ var h = ct.clientHeight;
+ cs.overflow = 'hidden';
+ cs.height = '1px';
+ cs.position = '';
+ cs.visibility = '';
+ var anim = new YAHOO.util.Anim(ct, {
+ height: {to: h}
+ }, this.node.ownerTree.duration || .25, YAHOO.util.Easing.easeOut);
+ anim.onComplete.subscribe(function(){
+ cs.overflow = '';
+ cs.height = '';
+ this.animating = false;
+ this.anim = null;
+ if(typeof callback == 'function'){
+ callback();
+ }
+ }, this, true);
+ this.anim = anim;
+ anim.animate();
+ },
+
+ highlight : function(){
+ var tree = this.node.getOwnerTree();
+ var hlColor = tree.hlColor || 'C3DAF9';
+ var hlBaseColor = tree.hlBaseColor || 'FFFFFF';
+ var anim = new YAHOO.util.ColorAnim(this.wrap, {
+ backgroundColor: {from: hlColor, to: hlBaseColor}
+ }, .75, YAHOO.util.Easing.easeNone);
+ anim.onComplete.subscribe(function(){
+ YAHOO.util.Dom.setStyle(this.wrap, 'background-color', '');
+ }, this, true);
+ anim.animate();
+ },
+
+ collapse : function(){
+ this.updateExpandIcon();
+ this.ctNode.style.display = 'none';
+ },
+
+ animCollapse : function(callback){
+ if(this.animating && this.anim){
+ this.anim.stop();
+ }
+ this.animating = true;
+ this.updateExpandIcon();
+ var ct = this.ctNode;
+ var cs = ct.style;
+ cs.height = ct.offsetHeight +'px';
+ cs.overflow = 'hidden';
+ var anim = new YAHOO.util.Anim(ct, {
+ height: {to: 1}
+ }, this.node.ownerTree.duration || .25, YAHOO.util.Easing.easeOut);
+ anim.onComplete.subscribe(function(){
+ cs.display = 'none';
+ cs.overflow = '';
+ cs.height = '';
+ this.animating = false;
+ this.anim = null;
+ if(typeof callback == 'function'){
+ callback();
+ }
+ }, this, true);
+ this.anim = anim;
+ anim.animate();
+ },
+
+ getContainer : function(){
+ return this.ctNode;
+ },
+
+ getEl : function(){
+ return this.wrap;
+ },
+
+ appendDDGhost : function(ghostNode){
+ ghostNode.appendChild(this.elNode.cloneNode(true));
+ },
+
+ getDDRepairXY : function(){
+ return YAHOO.util.Dom.getXY(this.iconNode);
+ },
+
+ onRender : function(){
+ this.render();
+ },
+
+ render : function(bulkRender){
+ var n = this.node;
+ var targetNode = n.parentNode ?
+ n.parentNode.ui.getContainer() : n.ownerTree.container.dom;
+ if(!this.rendered){
+ this.rendered = true;
+ var a = n.attributes;
+
+ // add some indent caching, this helps performance when rendering a large tree
+ this.indentMarkup = '';
+ if(n.parentNode){
+ this.indentMarkup = n.parentNode.ui.getChildIndent();
+ }
+
+ var buf = ['
+ var layout = new YAHOO.ext.BorderLayout(document.body, {
+ north: {
+ initialSize: 25,
+ titlebar: false
+ },
+ west: {
+ split:true,
+ initialSize: 200,
+ minSize: 175,
+ maxSize: 400,
+ titlebar: true,
+ collapsible: true
+ },
+ east: {
+ split:true,
+ initialSize: 202,
+ minSize: 175,
+ maxSize: 400,
+ titlebar: true,
+ collapsible: true
+ },
+ south: {
+ split:true,
+ initialSize: 100,
+ minSize: 100,
+ maxSize: 200,
+ titlebar: true,
+ collapsible: true
+ },
+ center: {
+ titlebar: true,
+ autoScroll:true,
+ resizeTabs: true,
+ minTabWidth: 50,
+ preferredTabWidth: 150
+ }
+});
+
+// shorthand
+var CP = YAHOO.ext.ContentPanel;
+
+layout.beginUpdate();
+layout.add('north', new CP('north', 'North'));
+layout.add('south', new CP('south', {title: 'South', closable: true}));
+layout.add('west', new CP('west', {title: 'West'}));
+layout.add('east', new CP('autoTabs', {title: 'Auto Tabs', closable: true}));
+layout.add('center', new CP('center1', {title: 'Close Me', closable: true}));
+layout.add('center', new CP('center2', {title: 'Center Panel', closable: false}));
+layout.getRegion('center').showPanel('center1');
+layout.endUpdate();
+
');
+ } else {
+ buf.unshift('
');
+ }
+ }
+ p = p.parentNode;
+ }
+ this.childIndent = buf.join('');
+ }
+ return this.childIndent;
+ },
+
+ renderIndent : function(){
+ if(this.rendered){
+ var indent = '';
+ var p = this.node.parentNode;
+ if(p){
+ indent = p.ui.getChildIndent();
+ }
+ if(this.indentMarkup != indent){ // don't rerender if not required
+ this.indentNode.innerHTML = indent;
+ this.indentMarkup = indent;
+ }
+ this.updateExpandIcon();
+ }
+ }
+};
+
+YAHOO.ext.tree.RootTreeNodeUI = function(){
+ YAHOO.ext.tree.RootTreeNodeUI.superclass.constructor.apply(this, arguments);
+};
+YAHOO.extendX(YAHOO.ext.tree.RootTreeNodeUI, YAHOO.ext.tree.TreeNodeUI);
+YAHOO.ext.tree.RootTreeNodeUI.prototype.render = function(){
+ if(!this.rendered){
+ var targetNode = this.node.ownerTree.container.dom;
+ this.node.expanded = true;
+ targetNode.innerHTML = '';
+ this.wrap = this.ctNode = targetNode.firstChild;
+ }
+};
+YAHOO.ext.tree.RootTreeNodeUI.prototype.collapse = function(){
+};
diff --git a/frontend/beta/js/YUI-extensions/tree/TreePanel.js b/frontend/beta/js/YUI-extensions/tree/TreePanel.js
new file mode 100644
index 0000000..202c0d0
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreePanel.js
@@ -0,0 +1,213 @@
+YAHOO.namespace('ext.tree');
+
+YAHOO.ext.tree.TreePanel = function(el, config){
+ YAHOO.ext.tree.TreePanel.superclass.constructor.call(this);
+ this.el = getEl(el);
+ this.id = this.el.id;
+ YAHOO.ext.util.Config.apply(this, config || {}, {
+ rootVisible : true,
+ lines : true,
+ enableDD : false,
+ hlDrop : true/*,
+ hlColor: null,
+ ddGroup : 'TreeDD'
+ hlBaseColor : 'FFFFFF'*/
+
+ });
+ YAHOO.ext.util.Config.apply(this.events, {
+ 'beforeload' : true,
+ 'load' : true,
+ 'textchange' : true,
+ 'beforeexpand' : true,
+ 'beforecollapse' : true,
+ 'expand' : true,
+ 'collapse' : true,
+ 'disabledchange' : true,
+ 'beforeclick':true,
+ 'click':true,
+ 'dblclick':true,
+ 'contentmenu':true,
+ 'beforechildrenrendered':true,
+ /**
+ * @event startdrag
+ * Fires when a node starts being dragged
+ * @param {YAHOO.ext.tree.TreePanel} this
+ * @param {YAHOO.ext.tree.TreeNode} node
+ * @param {event} e The raw browser event
+ */
+ 'startdrag' : true,
+ /**
+ * @event enddrag
+ * Fires when a drag operation is complete
+ * @param {YAHOO.ext.tree.TreePanel} this
+ * @param {YAHOO.ext.tree.TreeNode} node
+ * @param {event} e The raw browser event
+ */
+ 'enddrag' : true,
+ /**
+ * @event dragdrop
+ * Fires when a dragged node is dropped on a valid DD target
+ * @param {YAHOO.ext.tree.TreePanel} this
+ * @param {YAHOO.ext.tree.TreeNode} node
+ * @param {DD} dd The dd it was dropped on
+ * @param {event} e The raw browser event
+ */
+ 'dragdrop' : true,
+ /**
+ * @event beforenodedrop
+ * Fires when a DD object is dropped on a node in this tree for preprocessing. This event can cancel.
+ * @param {Object} dropEvent
+ */
+ 'beforenodedrop' : true,
+ /**
+ * @event nodedrop
+ * Fires after a DD object is dropped on a node in this tree
+ * @param {Object} dropEvent
+ */
+ 'nodedrop' : true,
+ /**
+ * @event nodedragover
+ * Fires when a tree node is being target
+ * @param {Object} dragOverEvent
+ */
+ 'nodedragover' : true
+ });
+ if(this.singleExpand){
+ this.on('beforeexpand', this.restrictExpand, this, true);
+ }
+ // problem with safari and animation
+ // I am investigating
+ if(YAHOO.ext.util.Browser.isSafari){
+ this.animate = false;
+ }
+};
+YAHOO.extendX(YAHOO.ext.tree.TreePanel, YAHOO.ext.data.Tree, {
+ restrictExpand : function(node){
+ var p = node.parentNode;
+ if(p){
+ if(p.expandedChild && p.expandedChild.parentNode == p){
+ p.expandedChild.collapse();
+ }
+ p.expandedChild = node;
+ }
+ },
+
+ setRootNode : function(node){
+ YAHOO.ext.tree.TreePanel.superclass.setRootNode.call(this, node);
+ if(!this.rootVisible){
+ node.ui = new YAHOO.ext.tree.RootTreeNodeUI(node);
+ }
+ return node;
+ },
+
+ getEl : function(){
+ return this.el;
+ },
+
+ getLoader : function(){
+ return this.loader;
+ },
+
+ expandAll : function(){
+ this.root.expand(true);
+ },
+
+ collapseAll : function(){
+ this.root.collapse(true);
+ },
+
+ getSelectionModel : function(){
+ if(!this.selModel){
+ this.selModel = new YAHOO.ext.tree.DefaultSelectionModel();
+ }
+ return this.selModel;
+ },
+
+ expandPath : function(path, attr, callback){
+ attr = attr || 'id';
+ var keys = path.split(this.pathSeparator);
+ var curNode = this.root;
+ if(curNode.attributes[attr] != keys[1]){ // invalid root
+ if(callback){
+ callback(false, null);
+ }
+ return;
+ }
+ var index = 1;
+ var f = function(){
+ if(++index == keys.length){
+ if(callback){
+ callback(true, curNode);
+ }
+ return;
+ }
+ var c = curNode.findChild(attr, keys[index]);
+ if(!c){
+ if(callback){
+ callback(false, curNode);
+ }
+ return;
+ }
+ curNode = c;
+ c.expand(false, false, f);
+ }
+ curNode.expand(false, false, f);
+ },
+
+ selectPath : function(path, attr, callback){
+ attr = attr || 'id';
+ var keys = path.split(this.pathSeparator);
+ var v = keys.pop();
+ if(keys.length > 0){
+ var f = function(success, node){
+ if(success && node){
+ var n = node.findChild(attr, v);
+ if(n){
+ n.select();
+ if(callback){
+ callback(true, n);
+ }
+ }
+ }else{
+ if(callback){
+ callback(false, n);
+ }
+ }
+ };
+ this.expandPath(keys.join(this.pathSeparator), attr, f);
+ }else{
+ this.root.select();
+ if(callback){
+ callback(true, this.root);
+ }
+ }
+ },
+
+ render : function(){
+ this.container = this.el.createChild({tag:'ul',
+ cls:'ytree-root-ct ' +
+ (this.lines ? 'ytree-lines' : 'ytree-no-lines')});
+
+ if(this.containerScroll){
+ YAHOO.ext.dd.ScrollManager.register(this.el);
+ }
+
+ if((this.enableDD || this.enableDrop) && !this.dropZone){
+ this.dropZone = new YAHOO.ext.tree.TreeDropZone(this, this.dropConfig || {
+ ddGroup: this.ddGroup || 'TreeDD'
+ });
+ }
+ if((this.enableDD || this.enableDrag) && !this.dragZone){
+ this.dragZone = new YAHOO.ext.tree.TreeDragZone(this, this.dragConfig || {
+ ddGroup: this.ddGroup || 'TreeDD',
+ scroll: this.ddScroll
+ });
+ }
+ this.getSelectionModel().init(this);
+ this.root.render();
+ if(!this.rootVisible){
+ this.root.renderChildren();
+ }
+ return this;
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeSelectionModel.js b/frontend/beta/js/YUI-extensions/tree/TreeSelectionModel.js
new file mode 100644
index 0000000..4fed88e
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeSelectionModel.js
@@ -0,0 +1,195 @@
+YAHOO.ext.tree.DefaultSelectionModel = function(){
+ this.selNode = null;
+
+ this.events = {
+ 'selectionchange' : true
+ };
+};
+
+YAHOO.extendX(YAHOO.ext.tree.DefaultSelectionModel, YAHOO.ext.util.Observable, {
+ init : function(tree){
+ this.tree = tree;
+ tree.el.mon('keydown', this.onKeyDown, this, true);
+ tree.on('click', this.onNodeClick, this, true);
+ },
+
+ onNodeClick : function(node, e){
+ this.select(node);
+ },
+
+ select : function(node){
+ if(this.selNode && this.selNode != node){
+ this.selNode.ui.onSelectedChange(false);
+ }
+ this.selNode = node;
+ node.ui.onSelectedChange(true);
+ this.fireEvent('selectionchange', this, node);
+ return node;
+ },
+
+ unselect : function(node){
+ if(this.selNode == node){
+ this.clearSelections();
+ }
+ },
+
+ clearSelections : function(){
+ var n = this.selNode;
+ if(n){
+ n.ui.onSelectedChange(false);
+ this.selNode = null;
+ this.fireEvent('selectionchange', this, null);
+ }
+ return n;
+ },
+
+ getSelectedNode : function(){
+ return this.selNode;
+ },
+
+ isSelected : function(node){
+ return this.selNode == node;
+ },
+
+ onKeyDown : function(e){
+ var s = this.selNode || this.lastSelNode;
+ // undesirable, but required
+ var sm = this;
+ if(!s){
+ return;
+ }
+ var k = e.getKey();
+ //alert(k)
+ switch(k){
+ case e.DOWN:
+ e.preventDefault();
+ if(s.firstChild && s.isExpanded()){
+ this.select(s.firstChild, e);
+ }else if(s.nextSibling){
+ this.select(s.nextSibling, e);
+ }else if(s.parentNode){
+ s.parentNode.bubble(function(){
+ if(this.nextSibling){
+ sm.select(this.nextSibling, e);
+ return false;
+ }
+ });
+ }
+ break;
+ case e.UP:
+ e.preventDefault();
+ var ps = s.previousSibling;
+ if(ps){
+ if(!ps.isExpanded()){
+ this.select(ps, e);
+ }else{
+ var lc = ps.lastChild;
+ while(lc && lc.isExpanded()){
+ lc = lc.lastChild;
+ }
+ this.select(lc, e);
+ }
+ }else if(s.parentNode && (this.tree.rootVisible || !s.parentNode.isRoot)){
+ this.select(s.parentNode, e);
+ }
+ break;
+ case e.RIGHT:
+ e.preventDefault();
+ if(s.hasChildNodes()){
+ if(!s.isExpanded()){
+ s.expand();
+ }else if(s.firstChild){
+ this.select(s.firstChild, e);
+ }
+ }
+ break;
+ case e.LEFT:
+ e.preventDefault();
+ if(s.hasChildNodes() && s.isExpanded()){
+ s.collapse();
+ }else if(s.parentNode && (this.tree.rootVisible || s.parentNode != this.tree.getRootNode())){
+ this.select(s.parentNode, e);
+ }
+ break;
+ };
+ }
+});
+
+YAHOO.ext.tree.MultiSelectionModel = function(){
+ this.selNodes = [];
+ this.selMap = {};
+ this.events = {
+ 'selectionchange' : true
+ };
+};
+
+YAHOO.extendX(YAHOO.ext.tree.MultiSelectionModel, YAHOO.ext.util.Observable, {
+ init : function(tree){
+ this.tree = tree;
+ tree.el.mon('keydown', this.onKeyDown, this, true);
+ tree.on('click', this.onNodeClick, this, true);
+ },
+
+ onNodeClick : function(node, e){
+ this.select(node, e, e.ctrlKey);
+ },
+
+ select : function(node, e, keepExisting){
+ if(keepExisting !== true){
+ this.clearSelections(true);
+ }
+ this.selNodes.push(node);
+ this.selMap[node.id] = node;
+ this.lastSelNode = node;
+ node.ui.onSelectedChange(true);
+ this.fireEvent('selectionchange', this, this.selNodes);
+ return node;
+ },
+
+ unselect : function(node){
+ if(this.selMap[node.id]){
+ node.ui.onSelectedChange(false);
+ var sn = this.selNodes;
+ var index = -1;
+ if(sn.indexOf){
+ index = sn.indexOf(node);
+ }else{
+ for(var i = 0, len = sn.length; i < len; i++){
+ if(sn[i] == node){
+ index = i;
+ break;
+ }
+ }
+ }
+ if(index != -1){
+ this.selNodes.splice(index, 1);
+ }
+ delete this.selMap[node.id];
+ this.fireEvent('selectionchange', this, this.selNodes);
+ }
+ },
+
+ clearSelections : function(suppressEvent){
+ var sn = this.selNodes;
+ if(sn.length > 0){
+ for(var i = 0, len = sn.length; i < len; i++){
+ sn[i].ui.onSelectedChange(false);
+ }
+ this.selNodes = [];
+ this.selMap = {};
+ if(suppressEvent !== true){
+ this.fireEvent('selectionchange', this, this.selNodes);
+ }
+ }
+ },
+
+ isSelected : function(node){
+ return this.selMap[node.id] ? true : false;
+ },
+
+ getSelectedNodes : function(){
+ return this.selNodes;
+ },
+
+ onKeyDown : YAHOO.ext.tree.DefaultSelectionModel.prototype.onKeyDown
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeSorter.js b/frontend/beta/js/YUI-extensions/tree/TreeSorter.js
new file mode 100644
index 0000000..9960703
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeSorter.js
@@ -0,0 +1,49 @@
+YAHOO.ext.tree.TreeSorter = function(tree, config){
+ YAHOO.ext.util.Config.apply(this, config);
+ tree.on('beforechildrenrendered', this.doSort, this, true);
+ tree.on('append', this.updateSort, this, true);
+ tree.on('insert', this.updateSort, this, true);
+
+ var dsc = this.dir && this.dir.toLowerCase() == 'desc';
+ var p = this.property || 'text';
+ var sortType = this.sortType;
+ var fs = this.folderSort;
+ var cs = this.caseSensitive === true;
+
+ this.sortFn = function(n1, n2){
+ if(fs){
+ if(n1.leaf && !n2.leaf){
+ return 1;
+ }
+ if(!n1.leaf && n2.leaf){
+ return -1;
+ }
+ }
+ var v1 = sortType ? sortType(n1) : (cs ? n1[p] : n1[p].toUpperCase());
+ var v2 = sortType ? sortType(n2) : (cs ? n2[p] : n2[p].toUpperCase());
+ if(v1 < v2){
+ return dsc ? +1 : -1;
+ }else if(v1 > v2){
+ return dsc ? -1 : +1;
+ }else{
+ return 0;
+ }
+ };
+};
+
+YAHOO.ext.tree.TreeSorter.prototype = {
+ doSort : function(node){
+ node.sort(this.sortFn);
+ },
+
+ compareNodes : function(n1, n2){
+
+ return (n1.text.toUpperCase() > n2.text.toUpperCase() ? 1 : -1);
+ },
+
+ updateSort : function(tree, node){
+ if(node.childrenRendered){
+ this.doSort.defer(1, this, [node]);
+ }
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/widgets/BasicDialog.js b/frontend/beta/js/YUI-extensions/widgets/BasicDialog.js
new file mode 100644
index 0000000..3912568
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/widgets/BasicDialog.js
@@ -0,0 +1,1046 @@
+/**
+ * @class YAHOO.ext.BasicDialog
+ * @extends YAHOO.ext.util.Observable
+ * Lightweight Dialog Class.
+ *
+ * The code below lists all configuration options along with the default value.
+ * If the default value is what you want you can leave it out:
+ *
+ * @constructor
+ * Create a new BasicDialog.
+ * @param {String/HTMLElement/YAHOO.ext.Element} el The id of or container element
+ * @param {Object} config configuration options
+ */
+YAHOO.ext.BasicDialog = function(el, config){
+ this.el = getEl(el);
+ var dh = YAHOO.ext.DomHelper;
+ if(!this.el && config && config.autoCreate){
+ if(typeof config.autoCreate == 'object'){
+ if(!config.autoCreate.id){
+ config.autoCreate.id = el;
+ }
+ this.el = dh.append(document.body,
+ config.autoCreate, true);
+ }else{
+ this.el = dh.append(document.body,
+ {tag: 'div', id: el}, true);
+ }
+ }
+ el = this.el;
+ el.setDisplayed(true);
+ el.hide = this.hideAction;
+ this.id = el.id;
+ el.addClass('ydlg');
+
+ YAHOO.ext.util.Config.apply(this, config);
+
+ this.proxy = el.createProxy('ydlg-proxy');
+ this.proxy.hide = this.hideAction;
+ this.proxy.setOpacity(.5);
+ this.proxy.hide();
+
+ if(config.width){
+ el.setWidth(config.width);
+ }
+ if(config.height){
+ el.setHeight(config.height);
+ }
+ this.size = el.getSize();
+ if(typeof config.x != 'undefined' && typeof config.y != 'undefined'){
+ this.xy = [config.x,config.y];
+ }else{
+ this.xy = el.getCenterXY(true);
+ }
+ // find the header, body and footer
+ var cn = el.dom.childNodes;
+ for(var i = 0, len = cn.length; i < len; i++) {
+ var node = cn[i];
+ if(node && node.nodeType == 1){
+ if(YAHOO.util.Dom.hasClass(node, 'ydlg-hd')){
+ this.header = getEl(node, true);
+ }else if(YAHOO.util.Dom.hasClass(node, 'ydlg-bd')){
+ this.body = getEl(node, true);
+ }else if(YAHOO.util.Dom.hasClass(node, 'ydlg-ft')){
+ /**
+ * The footer element
+ * @type YAHOO.ext.Element
+ */
+ this.footer = getEl(node, true);
+ }
+ }
+ }
+
+ if(!this.header){
+ /**
+ * The header element
+ * @type YAHOO.ext.Element
+ */
+ this.header = this.body ?
+ dh.insertBefore(this.body.dom, {tag: 'div', cls:'ydlg-hd'}, true) :
+ dh.append(el.dom, {tag: 'div', cls:'ydlg-hd'}, true);
+ }
+ if(this.title){
+ this.header.update(this.title);
+ }
+ // this element allows the dialog to be focused for keyboard event
+ this.focusEl = dh.append(el.dom, {tag: 'a', href:'#', cls:'ydlg-focus', tabIndex:'-1'}, true);
+ this.focusEl.swallowEvent('click', true);
+ if(!this.body){
+ /**
+ * The body element
+ * @type YAHOO.ext.Element
+ */
+ this.body = dh.append(el.dom, {tag: 'div', cls:'ydlg-bd'}, true);
+ }
+ // wrap the header for special rendering
+ var hl = dh.insertBefore(this.header.dom, {tag: 'div', cls:'ydlg-hd-left'});
+ var hr = dh.append(hl, {tag: 'div', cls:'ydlg-hd-right'});
+ hr.appendChild(this.header.dom);
+
+ // wrap the body and footer for special rendering
+ this.bwrap = dh.insertBefore(this.body.dom, {tag: 'div', cls:'ydlg-dlg-body'}, true);
+ this.bwrap.dom.appendChild(this.body.dom);
+ if(this.footer) this.bwrap.dom.appendChild(this.footer.dom);
+
+ this.bg = this.el.createChild({
+ tag: 'div', cls:'ydlg-bg',
+ html: '
+ var dlg = new YAHOO.ext.BasicDialog('element-id', {
+ autoCreate: false, (true to auto create from scratch, or DomHelper Object)
+ title: null, (title to set at config time)
+ width: (css),
+ height: (css),
+ x: 200, //(defaults to center screen if blank)
+ y: 500, //(defaults to center screen if blank)
+ animateTarget: null,// (no animation) This is the id or element to animate from
+ resizable: true,
+ minHeight: 80,
+ minWidth: 200,
+ modal: false,
+ autoScroll: true,
+ closable: true,
+ constraintoviewport: true,
+ draggable: true,
+ autoTabs: false, (if true searches child nodes for elements with class ydlg-tab and converts them to tabs)
+ tabTag: 'div', // the tag name of tab elements
+ proxyDrag: false, (drag a proxy element rather than the dialog itself)
+ fixedcenter: false,
+ shadow: false,
+ buttonAlign: 'right',
+ minButtonWidth: 75,
+ shim: false // true to create an iframe shim to
+ // keep selects from showing through
+ });
+
'
+ });
+ this.btnContainer = tb.dom.firstChild.firstChild.firstChild;
+ }
+ var bconfig = {
+ handler: handler,
+ scope: scope,
+ minWidth: this.minButtonWidth
+ };
+ if(typeof config == 'string'){
+ bconfig.text = config;
+ }else{
+ bconfig.dhconfig = config;
+ }
+ var btn = new YAHOO.ext.Button(
+ this.btnContainer.appendChild(document.createElement('td')),
+ bconfig
+ );
+ this.syncBodyHeight();
+ if(!this.buttons){
+ this.buttons = [];
+ }
+ this.buttons.push(btn);
+ return btn;
+ },
+
+ /**
+ * Sets the default button to be focused when the dialog is displayed
+ * @param {YAHOO.ext.BasicDialog.Button} btn The button object returned by addButton
+ * @return {YAHOO.ext.BasicDialog} this
+ */
+ setDefaultButton : function(btn){
+ this.defaultButton = btn;
+ return this;
+ },
+
+ getHeaderFooterHeight : function(safe){
+ var height = 0;
+ if(this.header){
+ height += this.header.getHeight();
+ }
+ if(this.footer){
+ var fm = this.footer.getMargins();
+ height += (this.footer.getHeight()+fm.top+fm.bottom);
+ }
+ height += this.bwrap.getPadding('tb')+this.bwrap.getBorderWidth('tb');
+ height += this.centerBg.getPadding('tb');
+ return height;
+ },
+
+ syncBodyHeight : function(){
+ var height = this.size.height - this.getHeaderFooterHeight(false);
+ this.body.setHeight(height-this.body.getMargins('tb'));
+ if(this.tabs){
+ this.tabs.syncHeight();
+ }
+ var hh = this.header.getHeight();
+ var h = this.size.height-hh;
+ this.centerBg.setHeight(h);
+ this.bwrap.setLeftTop(this.centerBg.getPadding('l'), hh+this.centerBg.getPadding('t'));
+ this.bwrap.setHeight(h-this.centerBg.getPadding('tb'));
+ this.bwrap.setWidth(this.el.getWidth(true)-this.centerBg.getPadding('lr'));
+ this.body.setWidth(this.bwrap.getWidth(true));
+ },
+
+ /**
+ * Restores the previous state of the dialog if YAHOO.ext.state is configured
+ * @return {YAHOO.ext.BasicDialog} this
+ */
+ restoreState : function(){
+ var box = YAHOO.ext.state.Manager.get(this.stateId || (this.el.id + '-state'));
+ if(box && box.width){
+ this.xy = [box.x, box.y];
+ this.resizeTo(box.width, box.height);
+ }
+ return this;
+ },
+
+ beforeShow : function(){
+ if(this.fixedcenter) {
+ this.xy = this.el.getCenterXY(true);
+ }
+ if(this.modal){
+ YAHOO.util.Dom.addClass(document.body, 'masked');
+ this.mask.setSize(YAHOO.util.Dom.getDocumentWidth(), YAHOO.util.Dom.getDocumentHeight());
+ this.mask.show();
+ }
+ this.constrainXY();
+ },
+
+ animShow : function(){
+ var b = getEl(this.animateTarget, true).getBox();
+ this.proxy.setSize(b.width, b.height);
+ this.proxy.setLocation(b.x, b.y);
+ this.proxy.show();
+ this.proxy.setBounds(this.xy[0], this.xy[1], this.size.width, this.size.height,
+ true, .35, this.showEl.createDelegate(this));
+ },
+
+ /**
+ * Shows the dialog.
+ * @param {String/HTMLElement/YAHOO.ext.Element} animateTarget (optional) Reset the animation target
+ * @return {YAHOO.ext.BasicDialog} this
+ */
+ show : function(animateTarget){
+ if (this.fireEvent('beforeshow', this) === false){
+ return;
+ }
+ if(this.syncHeightBeforeShow){
+ this.syncBodyHeight();
+ }
+ this.animateTarget = animateTarget || this.animateTarget;
+ if(!this.el.isVisible()){
+ this.beforeShow();
+ if(this.animateTarget){
+ this.animShow();
+ }else{
+ this.showEl();
+ }
+ }
+ return this;
+ },
+
+ showEl : function(){
+ this.proxy.hide();
+ this.el.setXY(this.xy);
+ this.el.show();
+ this.adjustAssets(true);
+ this.toFront();
+ this.focus();
+ this.fireEvent('show', this);
+ },
+
+ focus : function(){
+ if(this.defaultButton){
+ this.defaultButton.focus();
+ }else{
+ this.focusEl.focus();
+ }
+ },
+
+ constrainXY : function(){
+ if(this.constraintoviewport !== false){
+ if(!this.viewSize){
+ if(this.container){
+ var s = this.container.getSize();
+ this.viewSize = [s.width, s.height];
+ }else{
+ this.viewSize = [YAHOO.util.Dom.getViewportWidth(),
+ YAHOO.util.Dom.getViewportHeight()];
+ }
+ }
+ var x = this.xy[0], y = this.xy[1];
+ var w = this.size.width, h = this.size.height;
+ var vw = this.viewSize[0], vh = this.viewSize[1];
+ // only move it if it needs it
+ var moved = false;
+ // first validate right/bottom
+ if(x + w > vw){
+ x = vw - w;
+ moved = true;
+ }
+ if(y + h > vh){
+ y = vh - h;
+ moved = true;
+ }
+ // then make sure top/left isn't negative
+ if(x < 0){
+ x = 0;
+ moved = true;
+ }
+ if(y < 0){
+ y = 0;
+ moved = true;
+ }
+ if(moved){
+ // cache xy
+ this.xy = [x, y];
+ if(this.isVisible()){
+ this.el.setLocation(x, y);
+ this.adjustAssets();
+ }
+ }
+ }
+ },
+
+ onDrag : function(){
+ if(!this.proxyDrag){
+ this.xy = this.el.getXY();
+ this.adjustAssets();
+ }
+ },
+
+ adjustAssets : function(doShow){
+ var x = this.xy[0], y = this.xy[1];
+ var w = this.size.width, h = this.size.height;
+ if(doShow === true){
+ if(this.shadow){
+ this.shadow.show();
+ }
+ if(this.shim){
+ this.shim.show();
+ }
+ }
+ if(this.shadow && this.shadow.isVisible()){
+ this.shadow.setBounds(x + this.shadowOffset, y + this.shadowOffset, w, h);
+ }
+ if(this.shim && this.shim.isVisible()){
+ this.shim.setBounds(x, y, w, h);
+ }
+ },
+
+
+ adjustViewport : function(w, h){
+ if(!w || !h){
+ w = YAHOO.util.Dom.getViewportWidth();
+ h = YAHOO.util.Dom.getViewportHeight();
+ }
+ // cache the size
+ this.viewSize = [w, h];
+ if(this.modal && this.mask.isVisible()){
+ this.mask.setSize(w, h); // first make sure the mask isn't causing overflow
+ this.mask.setSize(YAHOO.util.Dom.getDocumentWidth(), YAHOO.util.Dom.getDocumentHeight());
+ }
+ if(this.isVisible()){
+ this.constrainXY();
+ }
+ },
+
+ /**
+ * Destroys this dialog
+ * @param {Boolean} removeEl (optional) true to remove the element from the DOM
+ */
+ destroy : function(removeEl){
+ YAHOO.ext.EventManager.removeResizeListener(this.adjustViewport, this);
+ if(this.tabs){
+ this.tabs.destroy(removeEl);
+ }
+ if(this.shim){
+ this.shim.remove();
+ }
+ if(this.shadow){
+ this.shadow.remove();
+ }
+ if(this.proxy){
+ this.proxy.remove();
+ }
+ if(this.resizer){
+ this.resizer.destroy();
+ }
+ if(this.close){
+ this.close.removeAllListeners();
+ this.close.remove();
+ }
+ if(this.mask){
+ this.mask.remove();
+ }
+ if(this.dd){
+ this.dd.unreg();
+ }
+ if(this.buttons){
+ for(var i = 0, len = this.buttons.length; i < len; i++){
+ this.buttons[i].destroy();
+ }
+ }
+ this.el.removeAllListeners();
+ if(removeEl === true){
+ this.el.update('');
+ this.el.remove();
+ }
+ YAHOO.ext.DialogManager.unregister(this);
+ },
+
+ startMove : function(){
+ if(this.proxyDrag){
+ this.proxy.show();
+ }
+ if(this.constraintoviewport !== false){
+ this.dd.constrainTo(document.body, {right: this.shadowOffset, bottom: this.shadowOffset});
+ }
+ },
+
+ endMove : function(){
+ if(!this.proxyDrag){
+ YAHOO.util.DD.prototype.endDrag.apply(this.dd, arguments);
+ }else{
+ YAHOO.util.DDProxy.prototype.endDrag.apply(this.dd, arguments);
+ this.proxy.hide();
+ }
+ this.refreshSize();
+ this.adjustAssets();
+ this.fireEvent('move', this, this.xy[0], this.xy[1])
+ },
+
+ /**
+ * Brings this dialog to the front of any other visible dialogs
+ * @return {YAHOO.ext.BasicDialog} this
+ */
+ toFront : function(){
+ YAHOO.ext.DialogManager.bringToFront(this);
+ return this;
+ },
+
+ /**
+ * Sends this dialog to the back (under) of any other visible dialogs
+ * @return {YAHOO.ext.BasicDialog} this
+ */
+ toBack : function(){
+ YAHOO.ext.DialogManager.sendToBack(this);
+ return this;
+ },
+
+ /**
+ * Centers this dialog
+ * @return {YAHOO.ext.BasicDialog} this
+ */
+ center : function(){
+ var xy = this.el.getCenterXY(true);
+ this.moveTo(xy[0], xy[1]);
+ return this;
+ },
+
+ /**
+ * Moves the dialog to the specified point
+ * @param {Number} x
+ * @param {Number} y
+ * @return {YAHOO.ext.BasicDialog} this
+ */
+ moveTo : function(x, y){
+ this.xy = [x,y];
+ if(this.isVisible()){
+ this.el.setXY(this.xy);
+ this.adjustAssets();
+ }
+ return this;
+ },
+
+ /**
+ * Returns true if the dialog is visible
+ * @return {Boolean}
+ */
+ isVisible : function(){
+ return this.el.isVisible();
+ },
+
+ animHide : function(callback){
+ var b = getEl(this.animateTarget, true).getBox();
+ this.proxy.show();
+ this.proxy.setBounds(this.xy[0], this.xy[1], this.size.width, this.size.height);
+ this.el.hide();
+ this.proxy.setBounds(b.x, b.y, b.width, b.height, true, .35,
+ this.hideEl.createDelegate(this, [callback]));
+ },
+
+ /**
+ * Hides the dialog.
+ * @param {Function} callback (optional) Function to call when the dialog is hidden
+ * @return {YAHOO.ext.BasicDialog} this
+ */
+ hide : function(callback){
+ if (this.fireEvent('beforehide', this) === false)
+ return;
+
+ if(this.shadow){
+ this.shadow.hide();
+ }
+ if(this.shim) {
+ this.shim.hide();
+ }
+ if(this.animateTarget){
+ this.animHide(callback);
+ }else{
+ this.el.hide();
+ this.hideEl(callback);
+ }
+ return this;
+ },
+
+ hideEl : function(callback){
+ this.proxy.hide();
+ if(this.modal){
+ this.mask.hide();
+ YAHOO.util.Dom.removeClass(document.body, 'masked');
+ }
+ this.fireEvent('hide', this);
+ if(typeof callback == 'function'){
+ callback();
+ }
+ },
+
+ hideAction : function(){
+ this.setLeft('-10000px');
+ this.setTop('-10000px');
+ this.setStyle('visibility', 'hidden');
+ },
+
+ refreshSize : function(){
+ this.size = this.el.getSize();
+ this.xy = this.el.getXY();
+ YAHOO.ext.state.Manager.set(this.stateId || this.el.id + '-state', this.el.getBox());
+ },
+
+ setZIndex : function(index){
+ if(this.modal){
+ this.mask.setStyle('z-index', index);
+ }
+ if(this.shim){
+ this.shim.setStyle('z-index', ++index);
+ }
+ if(this.shadow){
+ this.shadow.setStyle('z-index', ++index);
+ }
+ this.el.setStyle('z-index', ++index);
+ if(this.proxy){
+ this.proxy.setStyle('z-index', ++index);
+ }
+ if(this.resizer){
+ this.resizer.proxy.setStyle('z-index', ++index);
+ }
+
+ this.lastZIndex = index;
+ },
+
+ /**
+ * Returns the element for this dialog
+ * @return {YAHOO.ext.Element}
+ */
+ getEl : function(){
+ return this.el;
+ }
+});
+
+/**
+ * @class YAHOO.ext.DialogManager
+ * Provides global access to BasicDialogs that have been created and
+ * support for z-indexing (layering) multiple open dialogs.
+ */
+YAHOO.ext.DialogManager = function(){
+ var list = {};
+ var accessList = [];
+ var front = null;
+
+ var sortDialogs = function(d1, d2){
+ return (!d1._lastAccess || d1._lastAccess < d2._lastAccess) ? -1 : 1;
+ };
+
+ var orderDialogs = function(){
+ accessList.sort(sortDialogs);
+ var seed = YAHOO.ext.DialogManager.zseed;
+ for(var i = 0, len = accessList.length; i < len; i++){
+ if(accessList[i]){
+ accessList[i].setZIndex(seed + (i*10));
+ }
+ }
+ };
+
+ return {
+ /**
+ * The starting z-index for BasicDialogs - defaults to 10000
+ * @type Number
+ */
+ zseed : 10000,
+
+
+ register : function(dlg){
+ list[dlg.id] = dlg;
+ accessList.push(dlg);
+ },
+
+ unregister : function(dlg){
+ delete list[dlg.id];
+ if(!accessList.indexOf){
+ for(var i = 0, len = accessList.length; i < len; i++){
+ accessList.splice(i, 1);
+ return;
+ }
+ }else{
+ var i = accessList.indexOf(dlg);
+ if(i != -1){
+ accessList.splice(i, 1);
+ }
+ }
+ },
+
+ /**
+ * Gets a registered dialog by id
+ * @param {String/Object} id The id of the dialog or a dialog
+ * @return {YAHOO.ext.BasicDialog}
+ */
+ get : function(id){
+ return typeof id == 'object' ? id : list[id];
+ },
+
+ /**
+ * Brings the specified dialog to the front
+ * @param {String/Object} dlg The id of the dialog or a dialog
+ * @return {YAHOO.ext.BasicDialog}
+ */
+ bringToFront : function(dlg){
+ dlg = this.get(dlg);
+ if(dlg != front){
+ front = dlg;
+ dlg._lastAccess = new Date().getTime();
+ orderDialogs();
+ }
+ return dlg;
+ },
+
+ /**
+ * Sends the specified dialog to the back
+ * @param {String/Object} dlg The id of the dialog or a dialog
+ * @return {YAHOO.ext.BasicDialog}
+ */
+ sendToBack : function(dlg){
+ dlg = this.get(dlg);
+ dlg._lastAccess = -(new Date().getTime());
+ orderDialogs();
+ return dlg;
+ }
+ };
+}();
+
+/**
+ * @class YAHOO.ext.LayoutDialog
+ * @extends YAHOO.ext.BasicDialog
+ * Dialog which provides adjustments for working with a layout in a Dialog.
+ * Add your neccessary layout config options to the dialogs config.
+ * Example Usage (including a nested layout):
+ *
+ * @constructor
+ * @param {String/HTMLElement/YAHOO.ext.Element} el The id of or container element
+ * @param {Object} config configuration options
+ */
+YAHOO.ext.LayoutDialog = function(el, config){
+ config.autoTabs = false;
+ YAHOO.ext.LayoutDialog.superclass.constructor.call(this, el, config);
+ this.body.setStyle({overflow:'hidden', position:'relative'});
+ this.layout = new YAHOO.ext.BorderLayout(this.body.dom, config);
+ this.layout.monitorWindowResize = false;
+ this.el.addClass('ydlg-auto-layout');
+ // fix case when center region overwrites center function
+ this.center = YAHOO.ext.BasicDialog.prototype.center;
+ this.on('show', this.layout.layout, this.layout, true);
+};
+YAHOO.extendX(YAHOO.ext.LayoutDialog, YAHOO.ext.BasicDialog, {
+ /**
+ * Ends update of the layout if(!dialog){
+ dialog = new YAHOO.ext.LayoutDialog("download-dlg", {
+ modal: true,
+ width:600,
+ height:450,
+ shadow:true,
+ minWidth:500,
+ minHeight:350,
+ autoTabs:true,
+ proxyDrag:true,
+ // layout config merges with the dialog config
+ center:{
+ tabPosition: 'top',
+ alwaysShowTabs: true
+ }
+ });
+ dialog.addKeyListener(27, dialog.hide, dialog);
+ dialog.setDefaultButton(dialog.addButton('Close', dialog.hide, dialog));
+ dialog.addButton('Build It!', this.getDownload, this);
+
+ // we can even add nested layouts
+ var innerLayout = new YAHOO.ext.BorderLayout('dl-inner', {
+ east: {
+ initialSize: 200,
+ autoScroll:true,
+ split:true
+ },
+ center: {
+ autoScroll:true
+ }
+ });
+ innerLayout.beginUpdate();
+ innerLayout.add('east', new YAHOO.ext.ContentPanel('dl-details'));
+ innerLayout.add('center', new YAHOO.ext.ContentPanel('selection-panel'));
+ innerLayout.endUpdate(true);
+
+ // when doing updates to the top level layout in a dialog, you need to
+ // use dialog.beginUpdate()/endUpdate() instead of layout.beginUpdate()/endUpdate()
+ var layout = dialog.getLayout();
+ dialog.beginUpdate();
+ layout.add('center', new YAHOO.ext.ContentPanel('standard-panel',
+ {title: 'Download the Source', fitToFrame:true}));
+ layout.add('center', new YAHOO.ext.NestedLayoutPanel(innerLayout,
+ {title: 'Build your own yui-ext.js'}));
+ layout.getRegion('center').showPanel(sp);
+ dialog.endUpdate();
and resets display to none. Use standard beginUpdate/endUpdate on the layout.
+ * @deprecated
+ */
+ endUpdate : function(){
+ this.layout.endUpdate();
+ },
+ /**
+ * Begins an update of the layout and sets display to block and visibility to hidden. Use standard beginUpdate/endUpdate on the layout.
+ * @deprecated
+ */
+ beginUpdate : function(){
+ this.layout.beginUpdate();
+ },
+ /**
+ * Get the BorderLayout for this dialog
+ * @return {YAHOO.ext.BorderLayout}
+ */
+ getLayout : function(){
+ return this.layout;
+ },
+ syncBodyHeight : function(){
+ YAHOO.ext.LayoutDialog.superclass.syncBodyHeight.call(this);
+ if(this.layout)this.layout.layout();
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/widgets/Button.js b/frontend/beta/js/YUI-extensions/widgets/Button.js
new file mode 100644
index 0000000..5bf3dc3
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/widgets/Button.js
@@ -0,0 +1,185 @@
+/**
+ * @class YAHOO.ext.Button
+ * @extends YAHOO.ext.util.Observable
+ * Simple Button class
+ * @cfg {String} text The button text
+ * @cfg {Function} handler A function called when the button is clicked (can be used instead of click event)
+ * @cfg {Object} scope The scope of the handler
+ * @cfg {Number} minWidth The minimum width for this button (used to give a set of buttons a common width)
+ * @constructor
+ * Create a new button
+ * @param {String/HTMLElement/Element} renderTo The element to append the button to
+ * @param {Object} config The config object
+ */
+YAHOO.ext.Button = function(renderTo, config){
+ YAHOO.ext.util.Config.apply(this, config);
+ this.events = {
+ /**
+ * @event click
+ * Fires when this button is clicked
+ * @param {Button} this
+ * @param {EventObject} e The click event
+ */
+ 'click' : true
+ };
+ if(renderTo){
+ this.render(renderTo);
+ }
+};
+
+YAHOO.extendX(YAHOO.ext.Button, YAHOO.ext.util.Observable, {
+ render : function(renderTo){
+ var btn;
+ if(!this.dhconfig){
+ if(!YAHOO.ext.Button.buttonTemplate){
+ // hideous table template
+ YAHOO.ext.Button.buttonTemplate = new YAHOO.ext.DomHelper.Template('
');
+ }
+ btn = YAHOO.ext.Button.buttonTemplate.append(
+ getEl(renderTo).dom, [this.text], true);
+ this.tbl = getEl(btn.dom.firstChild, true);
+ }else{
+ btn = YAHOO.ext.DomHelper.append(this.footer.dom, this.dhconfig, true);
+ }
+ this.el = btn;
+ this.autoWidth();
+ btn.addClass('ybtn');
+ btn.mon('click', this.onClick, this, true);
+ btn.on('mouseover', this.onMouseOver, this, true);
+ btn.on('mouseout', this.onMouseOut, this, true);
+ btn.on('mousedown', this.onMouseDown, this, true);
+ btn.on('mouseup', this.onMouseUp, this, true);
+ },
+ /**
+ * Returns the buttons element
+ * @return {YAHOO.ext.Element}
+ */
+ getEl : function(){
+ return this.el;
+ },
+
+ /**
+ * Destroys this Button.
+ */
+ destroy : function(){
+ this.el.removeAllListeners();
+ this.purgeListeners();
+ this.el.update('');
+ this.el.remove();
+ },
+
+ autoWidth : function(){
+ if(this.tbl){
+ this.el.setWidth('auto');
+ this.tbl.setWidth('auto');
+ if(this.minWidth){
+ if(this.tbl.getWidth() < this.minWidth){
+ this.tbl.setWidth(this.minWidth);
+ }
+ }
+ this.el.setWidth(this.tbl.getWidth());
+ }
+ },
+ /**
+ * Sets this buttons click handler
+ * @param {Function} handler The function to call when the button is clicked
+ * @param {Object} scope (optional) Scope for the function passed above
+ */
+ setHandler : function(handler, scope){
+ this.handler = handler;
+ this.scope = scope;
+ },
+
+ /**
+ * Set this buttons text
+ * @param {String} text
+ */
+ setText : function(text){
+ this.text = text;
+ this.el.dom.firstChild.firstChild.firstChild.childNodes[1].innerHTML = text;
+ this.autoWidth();
+ },
+
+ /**
+ * Get the text for this button
+ * @return {String}
+ */
+ getText : function(){
+ return this.text;
+ },
+
+ /**
+ * Show this button
+ */
+ show: function(){
+ this.el.setStyle('display', '');
+ },
+
+ /**
+ * Hide this button
+ */
+ hide: function(){
+ this.el.setStyle('display', 'none');
+ },
+
+ /**
+ * Convenience function for boolean show/hide
+ * @param {Boolean} visible true to show/false to hide
+ */
+ setVisible: function(visible){
+ if(visible) {
+ this.show();
+ }else{
+ this.hide();
+ }
+ },
+
+ /**
+ * Focus the button
+ */
+ focus : function(){
+ this.el.focus();
+ },
+
+ /**
+ * Disable this button
+ */
+ disable : function(){
+ this.el.addClass('ybtn-disabled');
+ this.disabled = true;
+ },
+
+ /**
+ * Enable this button
+ */
+ enable : function(){
+ this.el.removeClass('ybtn-disabled');
+ this.disabled = false;
+ },
+
+ onClick : function(e){
+ e.preventDefault();
+ if(!this.disabled){
+ this.fireEvent('click', this, e);
+ if(this.handler){
+ this.handler.call(this.scope || this, this, e);
+ }
+ }
+ },
+ onMouseOver : function(e){
+ if(!this.disabled){
+ this.el.addClass('ybtn-over');
+ }
+ },
+ onMouseOut : function(e){
+ this.el.removeClass('ybtn-over');
+ },
+ onMouseDown : function(){
+ if(!this.disabled){
+ this.el.addClass('ybtn-click');
+ }
+ },
+ onMouseUp : function(){
+ this.el.removeClass('ybtn-click');
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/widgets/DatePicker.js b/frontend/beta/js/YUI-extensions/widgets/DatePicker.js
new file mode 100644
index 0000000..eb1e06f
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/widgets/DatePicker.js
@@ -0,0 +1,344 @@
+YAHOO.ext.DatePicker = function(id, parentElement){
+ this.id = id;
+ this.selectedDate = new Date();
+ this.visibleDate = new Date();
+ this.element = null;
+ this.shadow = null;
+ this.callback = null;
+ this.buildControl(parentElement || document.body);
+ this.mouseDownHandler = YAHOO.ext.EventManager.wrap(this.handleMouseDown, this, true);
+ this.keyDownHandler = YAHOO.ext.EventManager.wrap(this.handleKeyDown, this, true);
+ this.wheelHandler = YAHOO.ext.EventManager.wrap(this.handleMouseWheel, this, true);
+};
+
+YAHOO.ext.DatePicker.prototype = {
+ show : function(x, y, value, callback){
+ this.hide();
+ this.selectedDate = value;
+ this.visibleDate = value;
+ this.callback = callback;
+ this.refresh();
+ this.element.show();
+ this.element.setXY(this.constrainToViewport ? this.constrainXY(x, y) : [x, y]);
+ this.shadow.show();
+ this.shadow.setRegion(this.element.getRegion());
+ this.element.dom.tabIndex = 1;
+ this.element.focus();
+ YAHOO.util.Event.on(document, "mousedown", this.mouseDownHandler);
+ YAHOO.util.Event.on(document, "keydown", this.keyDownHandler);
+ YAHOO.util.Event.on(document, "mousewheel", this.wheelHandler);
+ YAHOO.util.Event.on(document, "DOMMouseScroll", this.wheelHandler);
+ },
+
+ constrainXY : function(x, y){
+ var w = YAHOO.util.Dom.getViewportWidth();
+ var h = YAHOO.util.Dom.getViewportHeight();
+ var size = this.element.getSize();
+ return [
+ Math.min(w-size.width, x),
+ Math.min(h-size.height, y)
+ ];
+ },
+
+ hide : function(){
+ this.shadow.hide();
+ this.element.hide();
+ YAHOO.util.Event.removeListener(document, "mousedown", this.mouseDownHandler);
+ YAHOO.util.Event.removeListener(document, "keydown", this.keyDownHandler);
+ YAHOO.util.Event.removeListener(document, "mousewheel", this.wheelHandler);
+ YAHOO.util.Event.removeListener(document, "DOMMouseScroll", this.wheelHandler);
+ },
+
+ setSelectedDate : function(date){
+ this.selectedDate = date;
+ },
+
+ getSelectedDate : function(){
+ return this.selectedDate;
+ },
+
+ showPrevMonth : function(){
+ this.visibleDate = this.getPrevMonth(this.visibleDate);
+ this.refresh();
+ },
+
+ showNextMonth : function(){
+ this.visibleDate = this.getNextMonth(this.visibleDate);
+ this.refresh();
+ },
+
+ showPrevYear : function(){
+ var d = this.visibleDate;
+ this.visibleDate = new Date(d.getFullYear()-1, d.getMonth(), d.getDate());
+ this.refresh();
+ },
+
+ showNextYear : function(){
+ var d = this.visibleDate;
+ this.visibleDate = new Date(d.getFullYear()+1, d.getMonth(), d.getDate());
+ this.refresh();
+ },
+
+ handleMouseDown : function(e){
+ var target = e.getTarget();
+ if(target != this.element.dom && !YAHOO.util.Dom.isAncestor(this.element.dom, target)){
+ this.hide();
+ }
+ },
+
+ handleKeyDown : function(e){
+ switch(e.browserEvent.keyCode){
+ case e.LEFT:
+ this.showPrevMonth();
+ e.stopEvent();
+ break;
+ case e.RIGHT:
+ this.showNextMonth();
+ e.stopEvent();
+ break;
+ case e.DOWN:
+ this.showPrevYear();
+ e.stopEvent();
+ break;
+ case e.UP:
+ this.showNextYear();
+ e.stopEvent();
+ break;
+ }
+ },
+
+ handleMouseWheel : function(e){
+ var delta = e.getWheelDelta();
+ if(delta > 0){
+ this.showPrevMonth();
+ e.stopEvent();
+ } else if(delta < 0){
+ this.showNextMonth();
+ e.stopEvent();
+ }
+ },
+
+ handleClick : function(e){
+ var d = this.visibleDate;
+ var t = e.getTarget();
+ if(t && t.className){
+ var cls = t.className.split(' ')[0];
+ switch(cls){
+ case 'active':
+ this.handleSelection(new Date(d.getFullYear(), d.getMonth(), parseInt(t.innerHTML)));
+ break;
+ case 'prevday':
+ var p = this.getPrevMonth(d);
+ this.handleSelection(new Date(p.getFullYear(), p.getMonth(), parseInt(t.innerHTML)));
+ break;
+ case 'nextday':
+ var n = this.getNextMonth(d);
+ this.handleSelection(new Date(n.getFullYear(), n.getMonth(), parseInt(t.innerHTML)));
+ break;
+ case 'ypopcal-today':
+ this.handleSelection(new Date());
+ break;
+ case 'next-month':
+ this.showNextMonth();
+ break;
+ case 'prev-month':
+ this.showPrevMonth();
+ break;
+ }
+ }
+ e.stopEvent();
+ },
+
+ selectToday : function(){
+ this.handleSelection(new Date());
+ },
+
+ handleSelection: function(date){
+ this.selectedDate = date;
+ this.callback(date);
+ this.hide();
+ },
+
+ getPrevMonth : function(d){
+ var m = d.getMonth();var y = d.getFullYear();
+ return (m == 0 ? new Date(--y, 11, 1) : new Date(y, --m, 1));
+ },
+
+ getNextMonth : function(d){
+ var m = d.getMonth();var y = d.getFullYear();
+ return (m == 11 ? new Date(++y, 0, 1) : new Date(y, ++m, 1));
+ },
+
+ getDaysInMonth : function(m, y){
+ return (m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10 || m == 12) ? 31 : (m == 4 || m == 6 || m == 9 || m == 11) ? 30 : this.isLeapYear(y) ? 29 : 28;
+ },
+
+ isLeapYear : function(y){
+ return (((y % 4) == 0) && ((y % 100) != 0) || ((y % 400) == 0));
+ },
+
+ clearTime : function(date){
+ if(date){
+ date.setHours(0);
+ date.setMinutes(0);
+ date.setSeconds(0);
+ date.setMilliseconds(0);
+ }
+ return date;
+ },
+
+ refresh : function(){
+ var d = this.visibleDate;
+ this.buildInnerCal(d);
+ this.calHead.update(this.monthNames[d.getMonth()] + ' ' + d.getFullYear());
+ if(this.element.isVisible()){
+ this.shadow.setRegion(this.element.getRegion());
+ }
+ }
+};
+
+/**
+ * This code is not pretty, but it is fast!
+ * @ignore
+ */
+YAHOO.ext.DatePicker.prototype.buildControl = function(parentElement){
+ var c = document.createElement('div');
+ c.style.position = 'absolute';
+ c.style.visibility = 'hidden';
+ document.body.appendChild(c);
+ var html = '' +
+ ' {0}
' +
+ '
";
+ html += '";
+ var names = this.dayNames;
+ for(var i = 0; i < names.length; i++){
+ html += ' ' + names[i].substr(0, 1) + ' ';
+ }
+ html+= "";
+ for(var i = 0; i < 42; i++) {
+ if(i % 7 == 0 && i != 0){
+ html += ' ';
+ }
+ html += " ";
+ }
+ html += "
');
+ }
+ ts.innerHTML = v;
+ }
+ var ww = wrap.dom.offsetWidth;
+ var wh = wrap.dom.offsetHeight;
+ var w = ts.offsetWidth;
+ var h = ts.offsetHeight;
+ // lots of magic numbers in this block - wtf?
+ // the logic is to prevent the scrollbars from flashing
+ // in firefox. Updates the right element first
+ // so there's never overflow.
+ if(ww > w+4){
+ el.setWidth(w+4);
+ wrap.setWidth(w+8);
+ }else{
+ wrap.setWidth(w+8);
+ el.setWidth(w+4);
+ }
+ if(wh > h+4){
+ el.setHeight(h);
+ wrap.setHeight(h+4);
+ }else{
+ wrap.setHeight(h+4);
+ el.setHeight(h);
+ }
+ },
+
+ moveTo : function(xy){
+ this.wrap.setXY(xy);
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/widgets/MessageBox.js b/frontend/beta/js/YUI-extensions/widgets/MessageBox.js
new file mode 100644
index 0000000..01b168d
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/widgets/MessageBox.js
@@ -0,0 +1,230 @@
+YAHOO.ext.MessageBox = function(){
+ var dlg, opt, mask;
+ var bodyEl, msgEl, textboxEl, textareaEl, progressEl, pp;
+ var buttons, activeTextEl, bwidth;
+
+ var handleButton = function(button){
+ if(typeof opt.fn == 'function'){
+ if(opt.fn.call(opt.scope||window, button, activeTextEl.dom.value) !== false){
+ dlg.hide();
+ }
+ }else{
+ dlg.hide();
+ }
+ };
+ var updateButtons = function(b){
+ var width = 0;
+ if(!b){
+ buttons['ok'].hide();
+ buttons['cancel'].hide();
+ buttons['yes'].hide();
+ buttons['no'].hide();
+ return width;
+ }
+ for(var k in buttons){
+ if(typeof buttons[k] != 'function'){
+ if(b[k]){
+ buttons[k].show();
+ buttons[k].setText(typeof b[k] == 'string' ? b[k] : YAHOO.ext.MessageBox.buttonText[k]);
+ width += buttons[k].el.getWidth()+15;
+ }else{
+ buttons[k].hide();
+ }
+ }
+ }
+ return width;
+ };
+
+ return {
+ getDialog : function(){
+ if(!dlg){
+ dlg = new YAHOO.ext.BasicDialog('mb-dlg', {
+ autoCreate : true,
+ shadow: true,
+ draggable: true,
+ resizable:false,
+ constraintoviewport:true,
+ fixedcenter:true,
+ shim:true,
+ modal: true,
+ width:400, height:100,
+ buttonAlign:'center',
+ closeClick : function(){
+ if(opt && opt.buttons && opt.buttons.no && !opt.buttons.cancel){
+ handleButton('no');
+ }else{
+ handleButton('cancel');
+ }
+ }
+ });
+ dlg.closeClick = function(){
+ alert('wtf');
+ };
+ mask = dlg.mask;
+ dlg.addKeyListener(27, dlg.hide, dlg);
+ buttons = {};
+ buttons['ok'] = dlg.addButton(this.buttonText['ok'], handleButton.createCallback('ok'));
+ buttons['yes'] = dlg.addButton(this.buttonText['yes'], handleButton.createCallback('yes'));
+ buttons['no'] = dlg.addButton(this.buttonText['no'], handleButton.createCallback('no'));
+ buttons['cancel'] = dlg.addButton(this.buttonText['cancel'], handleButton.createCallback('cancel'));
+ bodyEl = dlg.body.createChild({
+ tag:'div',
+ html:'
+ * Here's a Resizable with every possible config option and it's default value:
+
+*
+var resizer = new YAHOO.ext.Resizable('element-id', {
+ resizeChild : false,
+ adjustments : [0, 0],
+ minWidth : 5,
+ minHeight : 5,
+ maxWidth : 10000,
+ maxHeight : 10000,
+ enabled : true,
+ wrap: false, // true to wrap the element
+ width: null, // initial size
+ height: null, // initial size
+ animate : false,
+ duration : .35,
+ dynamic : false,
+ handles : false,
+ multiDirectional : false,
+ disableTrackOver : false,
+ easing : YAHOO.util.Easing ? YAHOO.util.Easing.easeOutStrong : null,
+ widthIncrement : 0,
+ heightIncrement : 0,
+ pinned : false,
+ width : null,
+ height : null,
+ preserveRatio : false,
+ transparent: false,
+ minX: 0,
+ minY: 0,
+ draggable: false
+});
+resizer.on('resize', myHandler);
+
+ * resizer.east.setDisplayed(false);
+ *
+ * Usage:
+ *
+ * @requires YAHOO.ext.Element
+ * @requires YAHOO.util.Dom
+ * @requires YAHOO.util.Event
+ * @requires YAHOO.util.CustomEvent
+ * @requires YAHOO.util.DDProxy
+ * @requires YAHOO.util.Anim (optional) to support animation
+ * @requires YAHOO.util.Easing (optional) to support animation
+ * @constructor
+ * Create a new SplitBar
+ * @param {String/HTMLElement/Element} dragElement The element to be dragged and act as the SplitBar.
+ * @param {String/HTMLElement/Element} resizingElement The element to be resized based on where the SplitBar element is dragged
+ * @param {Number} orientation (optional) Either YAHOO.ext.SplitBar.HORIZONTAL or YAHOO.ext.SplitBar.VERTICAL. (Defaults to HORIZONTAL)
+ * @param {Number} placement (optional) Either YAHOO.ext.SplitBar.LEFT or YAHOO.ext.SplitBar.RIGHT for horizontal or
+ YAHOO.ext.SplitBar.TOP or YAHOO.ext.SplitBar.BOTTOM for vertical. (By default, this is determined automatically by the intial position
+ position of the SplitBar).
+ */
+YAHOO.ext.SplitBar = function(dragElement, resizingElement, orientation, placement, existingProxy){
+
+ /** @private */
+ this.el = YAHOO.ext.Element.get(dragElement, true);
+ this.el.dom.unselectable = 'on';
+ /** @private */
+ this.resizingEl = YAHOO.ext.Element.get(resizingElement, true);
+
+ /**
+ * @private
+ * The orientation of the split. Either YAHOO.ext.SplitBar.HORIZONTAL or YAHOO.ext.SplitBar.VERTICAL. (Defaults to HORIZONTAL)
+ * Note: If this is changed after creating the SplitBar, the placement property must be manually updated
+ * @type Number
+ */
+ this.orientation = orientation || YAHOO.ext.SplitBar.HORIZONTAL;
+
+ /**
+ * The minimum size of the resizing element. (Defaults to 0)
+ * @type Number
+ */
+ this.minSize = 0;
+
+ /**
+ * The maximum size of the resizing element. (Defaults to 2000)
+ * @type Number
+ */
+ this.maxSize = 2000;
+
+ this.onMoved = new YAHOO.util.CustomEvent("SplitBarMoved", this);
+
+ /**
+ * Whether to animate the transition to the new size
+ * @type Boolean
+ */
+ this.animate = false;
+
+ /**
+ * Whether to create a transparent shim that overlays the page when dragging, enables dragging across iframes.
+ * @type Boolean
+ */
+ this.useShim = false;
+
+ /** @private */
+ this.shim = null;
+
+ if(!existingProxy){
+ /** @private */
+ this.proxy = YAHOO.ext.SplitBar.createProxy(this.orientation);
+ }else{
+ this.proxy = getEl(existingProxy).dom;
+ }
+ /** @private */
+ this.dd = new YAHOO.util.DDProxy(this.el.dom.id, "SplitBars", {dragElId : this.proxy.id});
+
+ /** @private */
+ this.dd.b4StartDrag = this.onStartProxyDrag.createDelegate(this);
+
+ /** @private */
+ this.dd.endDrag = this.onEndProxyDrag.createDelegate(this);
+
+ /** @private */
+ this.dragSpecs = {};
+
+ /**
+ * @private The adapter to use to positon and resize elements
+ */
+ this.adapter = new YAHOO.ext.SplitBar.BasicLayoutAdapter();
+ this.adapter.init(this);
+
+ if(this.orientation == YAHOO.ext.SplitBar.HORIZONTAL){
+ /** @private */
+ this.placement = placement || (this.el.getX() > this.resizingEl.getX() ? YAHOO.ext.SplitBar.LEFT : YAHOO.ext.SplitBar.RIGHT);
+ this.el.setStyle('cursor', 'e-resize');
+ }else{
+ /** @private */
+ this.placement = placement || (this.el.getY() > this.resizingEl.getY() ? YAHOO.ext.SplitBar.TOP : YAHOO.ext.SplitBar.BOTTOM);
+ this.el.setStyle('cursor', 'n-resize');
+ }
+
+ this.events = {
+ /**
+ * @event resize
+ * Fires when the splitter is moved (alias for moved)
+ * @param {YAHOO.ext.SplitBar} this
+ * @param {Number} newSize the new width or height
+ */
+ 'resize' : this.onMoved,
+ /**
+ * @event moved
+ * Fires when the splitter is moved
+ * @param {YAHOO.ext.SplitBar} this
+ * @param {Number} newSize the new width or height
+ */
+ 'moved' : this.onMoved,
+ /**
+ * @event beforeresize
+ * Fires before the splitter is dragged
+ * @param {YAHOO.ext.SplitBar} this
+ */
+ 'beforeresize' : new YAHOO.util.CustomEvent('beforeresize')
+ }
+}
+
+YAHOO.extendX(YAHOO.ext.SplitBar, YAHOO.ext.util.Observable, {
+ onStartProxyDrag : function(x, y){
+ this.fireEvent('beforeresize', this);
+ if(this.useShim){
+ if(!this.shim){
+ this.shim = YAHOO.ext.SplitBar.createShim();
+ }
+ this.shim.setVisible(true);
+ }
+ YAHOO.util.Dom.setStyle(this.proxy, 'display', 'block');
+ var size = this.adapter.getElementSize(this);
+ this.activeMinSize = this.getMinimumSize();;
+ this.activeMaxSize = this.getMaximumSize();;
+ var c1 = size - this.activeMinSize;
+ var c2 = Math.max(this.activeMaxSize - size, 0);
+ if(this.orientation == YAHOO.ext.SplitBar.HORIZONTAL){
+ this.dd.resetConstraints();
+ this.dd.setXConstraint(
+ this.placement == YAHOO.ext.SplitBar.LEFT ? c1 : c2,
+ this.placement == YAHOO.ext.SplitBar.LEFT ? c2 : c1
+ );
+ this.dd.setYConstraint(0, 0);
+ }else{
+ this.dd.resetConstraints();
+ this.dd.setXConstraint(0, 0);
+ this.dd.setYConstraint(
+ this.placement == YAHOO.ext.SplitBar.TOP ? c1 : c2,
+ this.placement == YAHOO.ext.SplitBar.TOP ? c2 : c1
+ );
+ }
+ this.dragSpecs.startSize = size;
+ this.dragSpecs.startPoint = [x, y];
+
+ YAHOO.util.DDProxy.prototype.b4StartDrag.call(this.dd, x, y);
+ },
+
+ /**
+ * @private Called after the drag operation by the DDProxy
+ */
+ onEndProxyDrag : function(e){
+ YAHOO.util.Dom.setStyle(this.proxy, 'display', 'none');
+ var endPoint = YAHOO.util.Event.getXY(e);
+ if(this.useShim){
+ this.shim.setVisible(false);
+ }
+ var newSize;
+ if(this.orientation == YAHOO.ext.SplitBar.HORIZONTAL){
+ newSize = this.dragSpecs.startSize +
+ (this.placement == YAHOO.ext.SplitBar.LEFT ?
+ endPoint[0] - this.dragSpecs.startPoint[0] :
+ this.dragSpecs.startPoint[0] - endPoint[0]
+ );
+ }else{
+ newSize = this.dragSpecs.startSize +
+ (this.placement == YAHOO.ext.SplitBar.TOP ?
+ endPoint[1] - this.dragSpecs.startPoint[1] :
+ this.dragSpecs.startPoint[1] - endPoint[1]
+ );
+ }
+ newSize = Math.min(Math.max(newSize, this.activeMinSize), this.activeMaxSize);
+ if(newSize != this.dragSpecs.startSize){
+ this.adapter.setElementSize(this, newSize);
+ this.onMoved.fireDirect(this, newSize);
+ }
+ },
+
+ /**
+ * Get the adapter this SplitBar uses
+ * @return The adapter object
+ */
+ getAdapter : function(){
+ return this.adapter;
+ },
+
+ /**
+ * Set the adapter this SplitBar uses
+ * @param {Object} adapter A SplitBar adapter object
+ */
+ setAdapter : function(adapter){
+ this.adapter = adapter;
+ this.adapter.init(this);
+ },
+
+ /**
+ * Gets the minimum size for the resizing element
+ * @return {Number} The minimum size
+ */
+ getMinimumSize : function(){
+ return this.minSize;
+ },
+
+ /**
+ * Sets the minimum size for the resizing element
+ * @param {Number} minSize The minimum size
+ */
+ setMinimumSize : function(minSize){
+ this.minSize = minSize;
+ },
+
+ /**
+ * Gets the maximum size for the resizing element
+ * @return {Number} The maximum size
+ */
+ getMaximumSize : function(){
+ return this.maxSize;
+ },
+
+ /**
+ * Sets the maximum size for the resizing element
+ * @param {Number} maxSize The maximum size
+ */
+ setMaximumSize : function(maxSize){
+ this.maxSize = maxSize;
+ },
+
+ /**
+ * Sets the initialize size for the resizing element
+ * @param {Number} size The initial size
+ */
+ setCurrentSize : function(size){
+ var oldAnimate = this.animate;
+ this.animate = false;
+ this.adapter.setElementSize(this, size);
+ this.animate = oldAnimate;
+ },
+
+ /**
+ * Destroy this splitbar.
+ * @param {Boolean} removeEl True to remove the element
+ */
+ destroy : function(removeEl){
+ if(this.shim){
+ this.shim.remove();
+ }
+ this.dd.unreg();
+ this.proxy.parentNode.removeChild(this.proxy);
+ if(removeEl){
+ this.el.remove();
+ }
+ }
+});
+
+/**
+ * @private static Create the shim to drag over iframes
+ */
+YAHOO.ext.SplitBar.createShim = function(){
+ var shim = document.createElement('div');
+ shim.unselectable = 'on';
+ YAHOO.util.Dom.generateId(shim, 'split-shim');
+ YAHOO.util.Dom.setStyle(shim, 'width', '100%');
+ YAHOO.util.Dom.setStyle(shim, 'height', '100%');
+ YAHOO.util.Dom.setStyle(shim, 'position', 'absolute');
+ YAHOO.util.Dom.setStyle(shim, 'background', 'white');
+ YAHOO.util.Dom.setStyle(shim, 'z-index', 11000);
+ window.document.body.appendChild(shim);
+ var shimEl = YAHOO.ext.Element.get(shim);
+ shimEl.setOpacity(.01);
+ shimEl.setXY([0, 0]);
+ return shimEl;
+};
+
+/**
+ * @private static Create our own proxy element element. So it will be the same same size on all browsers, we won't use borders. Instead we use a background color.
+ */
+YAHOO.ext.SplitBar.createProxy = function(orientation){
+ var proxy = document.createElement('div');
+ proxy.unselectable = 'on';
+ YAHOO.util.Dom.generateId(proxy, 'split-proxy');
+ YAHOO.util.Dom.setStyle(proxy, 'position', 'absolute');
+ YAHOO.util.Dom.setStyle(proxy, 'visibility', 'hidden');
+ YAHOO.util.Dom.setStyle(proxy, 'z-index', 11001);
+ YAHOO.util.Dom.setStyle(proxy, 'background-color', "#aaa");
+ if(orientation == YAHOO.ext.SplitBar.HORIZONTAL){
+ YAHOO.util.Dom.setStyle(proxy, 'cursor', 'e-resize');
+ }else{
+ YAHOO.util.Dom.setStyle(proxy, 'cursor', 'n-resize');
+ }
+ // the next 2 fix IE abs position div height problem
+ YAHOO.util.Dom.setStyle(proxy, 'line-height', '0px');
+ YAHOO.util.Dom.setStyle(proxy, 'font-size', '0px');
+ window.document.body.appendChild(proxy);
+ return proxy;
+};
+
+/**
+ * @class YAHOO.ext.SplitBar.BasicLayoutAdapter
+ * Default Adapter. It assumes the splitter and resizing element are not positioned
+ * elements and only gets/sets the width of the element. Generally used for table based layouts.
+ */
+YAHOO.ext.SplitBar.BasicLayoutAdapter = function(){
+};
+
+YAHOO.ext.SplitBar.BasicLayoutAdapter.prototype = {
+ // do nothing for now
+ init : function(s){
+
+ },
+ /**
+ * Called before drag operations to get the current size of the resizing element.
+ * @param {YAHOO.ext.SplitBar} s The SplitBar using this adapter
+ */
+ getElementSize : function(s){
+ if(s.orientation == YAHOO.ext.SplitBar.HORIZONTAL){
+ return s.resizingEl.getWidth();
+ }else{
+ return s.resizingEl.getHeight();
+ }
+ },
+
+ /**
+ * Called after drag operations to set the size of the resizing element.
+ * @param {YAHOO.ext.SplitBar} s The SplitBar using this adapter
+ * @param {Number} newSize The new size to set
+ * @param {Function} onComplete A function to be invoke when resizing is complete
+ */
+ setElementSize : function(s, newSize, onComplete){
+ if(s.orientation == YAHOO.ext.SplitBar.HORIZONTAL){
+ if(!YAHOO.util.Anim || !s.animate){
+ s.resizingEl.setWidth(newSize);
+ if(onComplete){
+ onComplete(s, newSize);
+ }
+ }else{
+ s.resizingEl.setWidth(newSize, true, .1, onComplete, YAHOO.util.Easing.easeOut);
+ }
+ }else{
+
+ if(!YAHOO.util.Anim || !s.animate){
+ s.resizingEl.setHeight(newSize);
+ if(onComplete){
+ onComplete(s, newSize);
+ }
+ }else{
+ s.resizingEl.setHeight(newSize, true, .1, onComplete, YAHOO.util.Easing.easeOut);
+ }
+ }
+ }
+};
+
+/**
+ *@class YAHOO.ext.SplitBar.AbsoluteLayoutAdapter
+ * @extends YAHOO.ext.SplitBar.BasicLayoutAdapter
+ * Adapter that moves the splitter element to align with the resized sizing element.
+ * Used with an absolute positioned SplitBar.
+ * @param {String/HTMLElement/Element} container The container that wraps around the absolute positioned content. If it's
+ * document.body, make sure you assign an id to the body element.
+ */
+YAHOO.ext.SplitBar.AbsoluteLayoutAdapter = function(container){
+ this.basic = new YAHOO.ext.SplitBar.BasicLayoutAdapter();
+ this.container = getEl(container);
+}
+
+YAHOO.ext.SplitBar.AbsoluteLayoutAdapter.prototype = {
+ init : function(s){
+ this.basic.init(s);
+ //YAHOO.util.Event.on(window, 'resize', this.moveSplitter.createDelegate(this, [s]));
+ },
+
+ getElementSize : function(s){
+ return this.basic.getElementSize(s);
+ },
+
+ setElementSize : function(s, newSize, onComplete){
+ this.basic.setElementSize(s, newSize, this.moveSplitter.createDelegate(this, [s]));
+ },
+
+ moveSplitter : function(s){
+ var yes = YAHOO.ext.SplitBar;
+ switch(s.placement){
+ case yes.LEFT:
+ s.el.setX(s.resizingEl.getRight());
+ break;
+ case yes.RIGHT:
+ s.el.setStyle('right', (this.container.getWidth() - s.resizingEl.getLeft()) + 'px');
+ break;
+ case yes.TOP:
+ s.el.setY(s.resizingEl.getBottom());
+ break;
+ case yes.BOTTOM:
+ s.el.setY(s.resizingEl.getTop() - s.el.getHeight());
+ break;
+ }
+ }
+};
+
+/**
+ * Orientation constant - Create a vertical SplitBar
+ * @static
+ * @type Number
+ */
+YAHOO.ext.SplitBar.VERTICAL = 1;
+
+/**
+ * Orientation constant - Create a horizontal SplitBar
+ * @static
+ * @type Number
+ */
+YAHOO.ext.SplitBar.HORIZONTAL = 2;
+
+/**
+ * Placement constant - The resizing element is to the left of the splitter element
+ * @static
+ * @type Number
+ */
+YAHOO.ext.SplitBar.LEFT = 1;
+
+/**
+ * Placement constant - The resizing element is to the right of the splitter element
+ * @static
+ * @type Number
+ */
+YAHOO.ext.SplitBar.RIGHT = 2;
+
+/**
+ * Placement constant - The resizing element is positioned above the splitter element
+ * @static
+ * @type Number
+ */
+YAHOO.ext.SplitBar.TOP = 3;
+
+/**
+ * Placement constant - The resizing element is positioned under splitter element
+ * @static
+ * @type Number
+ */
+YAHOO.ext.SplitBar.BOTTOM = 4;
diff --git a/frontend/beta/js/YUI-extensions/widgets/TabPanel.js b/frontend/beta/js/YUI-extensions/widgets/TabPanel.js
new file mode 100644
index 0000000..25fd142
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/widgets/TabPanel.js
@@ -0,0 +1,756 @@
+/**
+ * @class YAHOO.ext.TabPanel
+ * @extends YAHOO.ext.util.Observable
+ * Creates a lightweight TabPanel component using Yahoo! UI.
+ *
+var split = new YAHOO.ext.SplitBar('elementToDrag', 'elementToSize',
+ YAHOO.ext.SplitBar.HORIZONTAL, YAHOO.ext.SplitBar.LEFT);
+split.setAdapter(new YAHOO.ext.SplitBar.AbsoluteLayoutAdapter("container"));
+split.minSize = 100;
+split.maxSize = 600;
+split.animate = true;
+split.onMoved.subscribe(splitterMoved);
+
+ * Usage:
+ *
+ * @requires YAHOO.ext.Element
+ * @requires YAHOO.ext.UpdateManager
+ * @requires YAHOO.util.Dom
+ * @requires YAHOO.util.Event
+ * @requires YAHOO.util.CustomEvent
+ * @requires YAHOO.util.Connect (optional)
+ * @constructor
+ * Create new TabPanel.
+ * @param {String/HTMLElement/Element} container The id, DOM element or YAHOO.ext.Element container where this TabPanel is to be rendered.
+ * @param {Boolean} config Config object to set any properties for this TabPanel or true to render the tabs on the bottom.
+ */
+YAHOO.ext.TabPanel = function(container, config){
+ /**
+ * The container element for this TabPanel.
+ * @type YAHOO.ext.Element
+ */
+ this.el = getEl(container, true);
+ /** The position of the tabs. Can be 'top' or 'bottom' @type String */
+ this.tabPosition = 'top';
+ this.currentTabWidth = 0;
+ /** The minimum width of a tab (ignored if resizeTabs is not true). @type Number */
+ this.minTabWidth = 40;
+ /** The maximum width of a tab (ignored if resizeTabs is not true). @type Number */
+ this.maxTabWidth = 250;
+ /** The preferred (default) width of a tab (ignored if resizeTabs is not true). @type Number */
+ this.preferredTabWidth = 175;
+ /** Set this to true to enable dynamic tab resizing. @type Boolean */
+ this.resizeTabs = false;
+ /** Set this to true to turn on window resizing monitoring (ignored if resizeTabs is not true). @type Boolean */
+ this.monitorResize = true;
+
+ if(config){
+ if(typeof config == 'boolean'){
+ this.tabPosition = config ? 'bottom' : 'top';
+ }else{
+ YAHOO.ext.util.Config.apply(this, config);
+ }
+ }
+ if(this.tabPosition == 'bottom'){
+ this.bodyEl = getEl(this.createBody(this.el.dom));
+ this.el.addClass('ytabs-bottom');
+ }
+ this.stripWrap = getEl(this.createStrip(this.el.dom), true);
+ this.stripEl = getEl(this.createStripList(this.stripWrap.dom), true);
+ this.stripBody = getEl(this.stripWrap.dom.firstChild.firstChild, true);
+ if(YAHOO.ext.util.Browser.isIE){
+ YAHOO.util.Dom.setStyle(this.stripWrap.dom.firstChild, 'overflow-x', 'hidden');
+ }
+ if(this.tabPosition != 'bottom'){
+ /** The body element that contains TabPaneItem bodies.
+ * @type YAHOO.ext.Element
+ */
+ this.bodyEl = getEl(this.createBody(this.el.dom));
+ this.el.addClass('ytabs-top');
+ }
+ this.items = [];
+
+ this.bodyEl.setStyle('position', 'relative');
+
+ // add indexOf to array if it isn't present
+ if(!this.items.indexOf){
+ this.items.indexOf = function(o){
+ for(var i = 0, len = this.length; i < len; i++){
+ if(this[i] == o) return i;
+ }
+ return -1;
+ }
+ }
+ this.active = null;
+ this.onTabChange = new YAHOO.util.CustomEvent('TabItem.onTabChange');
+ this.activateDelegate = this.activate.createDelegate(this);
+
+ this.events = {
+ /**
+ * @event tabchange
+ * Fires when the active tab changes
+ * @param {YAHOO.ext.TabPanel} this
+ * @param {YAHOO.ext.TabPanelItem} activePanel The new active tab
+ */
+ 'tabchange': this.onTabChange,
+ /**
+ * @event beforetabchange
+ * Fires before the active tab changes, set cancel to true on the "e" parameter to cancel the change
+ * @param {YAHOO.ext.TabPanel} this
+ * @param {Object} e Set cancel to true on this object to cancel the tab change
+ * @param {YAHOO.ext.TabPanelItem} tab The tab being changed to
+ */
+ 'beforetabchange' : new YAHOO.util.CustomEvent('beforechange')
+ };
+
+ YAHOO.ext.EventManager.onWindowResize(this.onResize, this, true);
+ this.cpad = this.el.getPadding('lr');
+ this.hiddenCount = 0;
+}
+
+YAHOO.ext.TabPanel.prototype = {
+ fireEvent : YAHOO.ext.util.Observable.prototype.fireEvent,
+ on : YAHOO.ext.util.Observable.prototype.on,
+ addListener : YAHOO.ext.util.Observable.prototype.addListener,
+ delayedListener : YAHOO.ext.util.Observable.prototype.delayedListener,
+ removeListener : YAHOO.ext.util.Observable.prototype.removeListener,
+ purgeListeners : YAHOO.ext.util.Observable.prototype.purgeListeners,
+ /**
+ * Creates a new TabPanelItem by looking for an existing element with the provided id - if it's not found it creates one.
+ * @param {String} id The id of the div to use or create
+ * @param {String} text The text for the tab
+ * @param {String} content (optional) Content to put in the TabPanelItem body
+ * @param {Boolean} closable (optional) True to create a close icon on the tab
+ * @return {YAHOO.ext.TabPanelItem} The created TabPanelItem
+ */
+ addTab : function(id, text, content, closable){
+ var item = new YAHOO.ext.TabPanelItem(this, id, text, closable);
+ this.addTabItem(item);
+ if(content){
+ item.setContent(content);
+ }
+ return item;
+ },
+
+ /**
+ * Returns the TabPanelItem with the specified id/index
+ * @param {String/Number} id The id or index of the TabPanelItem to fetch.
+ * @return {YAHOO.ext.TabPanelItem}
+ */
+ getTab : function(id){
+ return this.items[id];
+ },
+
+ /**
+ * Hides the TabPanelItem with the specified id/index
+ * @param {String/Number} id The id or index of the TabPanelItem to hide.
+ */
+ hideTab : function(id){
+ var t = this.items[id];
+ if(!t.isHidden()){
+ t.setHidden(true);
+ this.hiddenCount++;
+ this.autoSizeTabs();
+ }
+ },
+
+ /**
+ * "Unhides" the TabPanelItem with the specified id/index
+ * @param {String/Number} id The id or index of the TabPanelItem to unhide.
+ */
+ unhideTab : function(id){
+ var t = this.items[id];
+ if(t.isHidden()){
+ t.setHidden(false);
+ this.hiddenCount--;
+ this.autoSizeTabs();
+ }
+ },
+
+ /**
+ * Add an existing TabPanelItem.
+ * @param {YAHOO.ext.TabPanelItem} item The TabPanelItem to add
+ */
+ addTabItem : function(item){
+ this.items[item.id] = item;
+ this.items.push(item);
+ if(this.resizeTabs){
+ item.setWidth(this.currentTabWidth || this.preferredTabWidth)
+ this.autoSizeTabs();
+ }else{
+ item.autoSize();
+ }
+ },
+
+ /**
+ * Remove a TabPanelItem.
+ * @param {String/Number} id The id or index of the TabPanelItem to remove.
+ */
+ removeTab : function(id){
+ var items = this.items;
+ var tab = items[id];
+ if(!tab) return;
+ var index = items.indexOf(tab);
+ if(this.active == tab && items.length > 1){
+ var newTab = this.getNextAvailable(index);
+ if(newTab)newTab.activate();
+ }
+ this.stripEl.dom.removeChild(tab.pnode.dom);
+ if(tab.bodyEl.dom.parentNode == this.bodyEl.dom){ // if it was moved already prevent error
+ this.bodyEl.dom.removeChild(tab.bodyEl.dom);
+ }
+ items.splice(index, 1);
+ delete this.items[tab.id];
+ tab.fireEvent('close', tab);
+ tab.purgeListeners();
+ this.autoSizeTabs();
+ },
+
+ getNextAvailable : function(start){
+ var items = this.items;
+ var index = start;
+ // look for a next tab that will slide over to
+ // replace the one being removed
+ while(index < items.length){
+ var item = items[++index];
+ if(item && !item.isHidden()){
+ return item;
+ }
+ }
+ // if one isn't found select the previous tab (on the left)
+ var index = start;
+ while(index >= 0){
+ var item = items[--index];
+ if(item && !item.isHidden()){
+ return item;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Disable a TabPanelItem. It cannot be the active tab, if it is this call is ignored..
+ * @param {String/Number} id The id or index of the TabPanelItem to disable.
+ */
+ disableTab : function(id){
+ var tab = this.items[id];
+ if(tab && this.active != tab){
+ tab.disable();
+ }
+ },
+
+ /**
+ * Enable a TabPanelItem that is disabled.
+ * @param {String/Number} id The id or index of the TabPanelItem to enable.
+ */
+ enableTab : function(id){
+ var tab = this.items[id];
+ tab.enable();
+ },
+
+ /**
+ * Activate a TabPanelItem. The currently active will be deactivated.
+ * @param {String/Number} id The id or index of the TabPanelItem to activate.
+ */
+ activate : function(id){
+ var tab = this.items[id];
+ if(tab == this.active){
+ return tab;
+ }
+ var e = {};
+ this.fireEvent('beforetabchange', this, e, tab);
+ if(e.cancel !== true && !tab.disabled){
+ if(this.active){
+ this.active.hide();
+ }
+ this.active = this.items[id];
+ this.active.show();
+ this.onTabChange.fireDirect(this, this.active);
+ }
+ return tab;
+ },
+
+ /**
+ * Get the active TabPanelItem
+ * @return {YAHOO.ext.TabPanelItem} The active TabPanelItem or null if none are active.
+ */
+ getActiveTab : function(){
+ return this.active;
+ },
+
+ /**
+ * Updates the tab body element to fit the height of the container element
+ * for overflow scrolling
+ * @param {Number} targetHeight (optional) Override the starting height from the elements height
+ */
+ syncHeight : function(targetHeight){
+ var height = (targetHeight || this.el.getHeight())-this.el.getBorderWidth('tb')-this.el.getPadding('tb');
+ var bm = this.bodyEl.getMargins();
+ var newHeight = height-(this.stripWrap.getHeight()||0)-(bm.top+bm.bottom);
+ this.bodyEl.setHeight(newHeight);
+ return newHeight;
+ },
+
+ onResize : function(){
+ if(this.monitorResize){
+ this.autoSizeTabs();
+ }
+ },
+
+ /**
+ * Disables tab resizing while tabs are being added (if resizeTabs is false this does nothing)
+ */
+ beginUpdate : function(){
+ this.updating = true;
+ },
+
+ /**
+ * Stops an update and resizes the tabs (if resizeTabs is false this does nothing)
+ */
+ endUpdate : function(){
+ this.updating = false;
+ this.autoSizeTabs();
+ },
+
+ /**
+ * Manual call to resize the tabs (if resizeTabs is false this does nothing)
+ */
+ autoSizeTabs : function(){
+ var count = this.items.length;
+ var vcount = count - this.hiddenCount;
+ if(!this.resizeTabs || count < 1 || vcount < 1 || this.updating) return;
+ var w = Math.max(this.el.getWidth() - this.cpad, 10);
+ var availWidth = Math.floor(w / vcount);
+ var b = this.stripBody;
+ if(b.getWidth() > w){
+ var tabs = this.items;
+ this.setTabWidth(Math.max(availWidth, this.minTabWidth));
+ if(availWidth < this.minTabWidth){
+ /*if(!this.sleft){ // incomplete scrolling code
+ this.createScrollButtons();
+ }
+ this.showScroll();
+ this.stripClip.setWidth(w - (this.sleft.getWidth()+this.sright.getWidth()));*/
+ }
+ }else{
+ if(this.currentTabWidth < this.preferredTabWidth){
+ this.setTabWidth(Math.min(availWidth, this.preferredTabWidth));
+ }
+ }
+ },
+
+ /**
+ * Returns the number of tabs
+ * @return {Number}
+ */
+ getCount : function(){
+ return this.items.length;
+ },
+
+ /**
+ * Resizes all the tabs to the passed width
+ * @param {Number} The new width
+ */
+ setTabWidth : function(width){
+ this.currentTabWidth = width;
+ for(var i = 0, len = this.items.length; i < len; i++) {
+ if(!this.items[i].isHidden())this.items[i].setWidth(width);
+ }
+ },
+
+ /**
+ * Destroys this TabPanel
+ * @param {Boolean} removeEl (optional) True to remove the element from the DOM as well
+ */
+ destroy : function(removeEl){
+ YAHOO.ext.EventManager.removeResizeListener(this.onResize, this);
+ for(var i = 0, len = this.items.length; i < len; i++){
+ this.items[i].purgeListeners();
+ }
+ if(removeEl === true){
+ this.el.update('');
+ this.el.remove();
+ }
+ }
+};
+
+/**
+* @class YAHOO.ext.TabPanelItem
+* @extends YAHOO.ext.util.Observable
+*/
+YAHOO.ext.TabPanelItem = function(tabPanel, id, text, closable){
+ /**
+ * The TabPanel this TabPanelItem belongs to
+ * @type YAHOO.ext.TabPanel
+ */
+ this.tabPanel = tabPanel;
+ /**
+ * The id for this TabPanelItem
+ * @type String
+ */
+ this.id = id;
+ /** @private */
+ this.disabled = false;
+ /** @private */
+ this.text = text;
+ /** @private */
+ this.loaded = false;
+ this.closable = closable;
+
+ /**
+ * The body element for this TabPanelItem
+ * @type YAHOO.ext.Element
+ */
+ this.bodyEl = getEl(tabPanel.createItemBody(tabPanel.bodyEl.dom, id));
+ this.bodyEl.setVisibilityMode(YAHOO.ext.Element.VISIBILITY);
+ this.bodyEl.setStyle('display', 'block');
+ this.bodyEl.setStyle('zoom', '1');
+ this.hideAction();
+
+ var els = tabPanel.createStripElements(tabPanel.stripEl.dom, text, closable);
+ /** @private */
+ this.el = getEl(els.el, true);
+ this.inner = getEl(els.inner, true);
+ this.textEl = getEl(this.el.dom.firstChild.firstChild.firstChild, true);
+ this.pnode = getEl(els.el.parentNode, true);
+ this.el.mon('click', this.onTabClick, this, true);
+ /** @private */
+ if(closable){
+ var c = getEl(els.close, true);
+ c.dom.title = this.closeText;
+ c.addClassOnOver('close-over');
+ c.mon('click', this.closeClick, this, true);
+ }
+
+ // these two are now private and deprecated
+ this.onActivate = new YAHOO.util.CustomEvent('TabItem.onActivate');
+ this.onDeactivate = new YAHOO.util.CustomEvent('TabItem.onDeactivate');
+
+ this.events = {
+ /**
+ * @event activate
+ * Fires when this tab becomes the active tab
+ * @param {YAHOO.ext.TabPanel} tabPanel
+ * @param {YAHOO.ext.TabPanelItem} this
+ */
+ 'activate': this.onActivate,
+ /**
+ * @event beforeclose
+ * Fires before this tab is closed. To cancal the close, set cancel to true on e. (e.cancel = true)
+ * @param {YAHOO.ext.TabPanelItem} this
+ * @param {Object} e Set cancel to true on this object to cancel the close.
+ */
+ 'beforeclose': new YAHOO.util.CustomEvent('beforeclose'),
+ /**
+ * @event close
+ * Fires when this tab is closed
+ * @param {YAHOO.ext.TabPanelItem} this
+ */
+ 'close': new YAHOO.util.CustomEvent('close'),
+ /**
+ * @event deactivate
+ * Fires when this tab is no longer the active tab
+ * @param {YAHOO.ext.TabPanel} tabPanel
+ * @param {YAHOO.ext.TabPanelItem} this
+ */
+ 'deactivate' : this.onDeactivate
+ };
+ this.hidden = false;
+};
+
+YAHOO.ext.TabPanelItem.prototype = {
+ fireEvent : YAHOO.ext.util.Observable.prototype.fireEvent,
+ on : YAHOO.ext.util.Observable.prototype.on,
+ addListener : YAHOO.ext.util.Observable.prototype.addListener,
+ delayedListener : YAHOO.ext.util.Observable.prototype.delayedListener,
+ removeListener : YAHOO.ext.util.Observable.prototype.removeListener,
+ purgeListeners : function(){
+ YAHOO.ext.util.Observable.prototype.purgeListeners.call(this);
+ this.el.removeAllListeners();
+ },
+ /**
+ * Show this TabPanelItem - this does not deactivate the currently active TabPanelItem.
+ */
+ show : function(){
+ this.pnode.addClass('on');
+ this.showAction();
+ if(YAHOO.ext.util.Browser.isOpera){
+ this.tabPanel.stripWrap.repaint();
+ }
+ this.onActivate.fireDirect(this.tabPanel, this);
+ },
+
+ /**
+ * Returns true if this tab is the active tab
+ * @return {Boolean}
+ */
+ isActive : function(){
+ return this.tabPanel.getActiveTab() == this;
+ },
+
+ /**
+ * Hide this TabPanelItem - if you don't activate another TabPanelItem this could look odd.
+ */
+ hide : function(){
+ this.pnode.removeClass('on');
+ this.hideAction();
+ this.onDeactivate.fireDirect(this.tabPanel, this);
+ },
+
+ hideAction : function(){
+ this.bodyEl.setStyle('position', 'absolute');
+ this.bodyEl.setLeft('-20000px');
+ this.bodyEl.setTop('-20000px');
+ this.bodyEl.hide();
+ },
+
+ showAction : function(){
+ this.bodyEl.setStyle('position', 'relative');
+ this.bodyEl.setTop('');
+ this.bodyEl.setLeft('');
+ this.bodyEl.show();
+ this.tabPanel.el.repaint.defer(1);
+ },
+
+ /**
+ * Set the tooltip (title attribute) for the tab
+ * @param {String} tooltip
+ */
+ setTooltip : function(text){
+ this.textEl.dom.title = text;
+ },
+
+ onTabClick : function(e){
+ e.preventDefault();
+ this.tabPanel.activate(this.id);
+ },
+
+ getWidth : function(){
+ return this.inner.getWidth();
+ },
+
+ setWidth : function(width){
+ var iwidth = width - this.pnode.getPadding("lr");
+ this.inner.setWidth(iwidth);
+ this.textEl.setWidth(iwidth-this.inner.getPadding('lr'));
+ this.pnode.setWidth(width);
+ },
+
+ setHidden : function(hidden){
+ this.hidden = hidden;
+ this.pnode.setStyle('display', hidden ? 'none' : '');
+ },
+
+ /**
+ * Returns true if this tab is "hidden"
+ * @return {Boolean}
+ */
+ isHidden : function(){
+ return this.hidden;
+ },
+
+ /**
+ * Returns the text for this tab
+ * @return {String}
+ */
+ getText : function(){
+ return this.text;
+ },
+
+ autoSize : function(){
+ this.el.beginMeasure();
+ this.textEl.setWidth(1);
+ this.setWidth(this.textEl.dom.scrollWidth+this.pnode.getPadding("lr")+this.inner.getPadding('lr'));
+ this.el.endMeasure();
+ },
+
+ /**
+ * Sets the text for the tab (Note: this also sets the tooltip)
+ * @param {String} text
+ */
+ setText : function(text){
+ this.text = text;
+ this.textEl.update(text);
+ this.textEl.dom.title = text;
+ if(!this.tabPanel.resizeTabs){
+ this.autoSize();
+ }
+ },
+ /**
+ * Activate this TabPanelItem - this does deactivate the currently active TabPanelItem.
+ */
+ activate : function(){
+ this.tabPanel.activate(this.id);
+ },
+
+ /**
+ * Disable this TabPanelItem - this call is ignore if this is the active TabPanelItem.
+ */
+ disable : function(){
+ if(this.tabPanel.active != this){
+ this.disabled = true;
+ this.pnode.addClass('disabled');
+ }
+ },
+
+ /**
+ * Enable this TabPanelItem if it was previously disabled.
+ */
+ enable : function(){
+ this.disabled = false;
+ this.pnode.removeClass('disabled');
+ },
+
+ /**
+ * Set the content for this TabPanelItem.
+ * @param {String} content The content
+ * @param {Boolean} loadScripts true to look for and load scripts
+ */
+ setContent : function(content, loadScripts){
+ this.bodyEl.update(content, loadScripts);
+ },
+
+ /**
+ * Get the {@link YAHOO.ext.UpdateManager} for the body of this TabPanelItem. Enables you to perform Ajax updates.
+ * @return {YAHOO.ext.UpdateManager} The UpdateManager
+ */
+ getUpdateManager : function(){
+ return this.bodyEl.getUpdateManager();
+ },
+
+ /**
+ * Set a URL to be used to load the content for this TabPanelItem.
+ * @param {String/Function} url The url to load the content from or a function to call to get the url
+ * @param {String/Object} params (optional) The string params for the update call or an object of the params. See {@link YAHOO.ext.UpdateManager#update} for more details. (Defaults to null)
+ * @param {Boolean} loadOnce (optional) Whether to only load the content once. If this is false it makes the Ajax call every time this TabPanelItem is activated. (Defaults to false)
+ * @return {YAHOO.ext.UpdateManager} The UpdateManager
+ */
+ setUrl : function(url, params, loadOnce){
+ if(this.refreshDelegate){
+ this.onActivate.unsubscribe(this.refreshDelegate);
+ }
+ this.refreshDelegate = this._handleRefresh.createDelegate(this, [url, params, loadOnce]);
+ this.onActivate.subscribe(this.refreshDelegate);
+ return this.bodyEl.getUpdateManager();
+ },
+
+ /** @private */
+ _handleRefresh : function(url, params, loadOnce){
+ if(!loadOnce || !this.loaded){
+ var updater = this.bodyEl.getUpdateManager();
+ updater.update(url, params, this._setLoaded.createDelegate(this));
+ }
+ },
+
+ /**
+ * Force a content refresh from the URL specified in the setUrl() method.
+ * Will fail silently if the setUrl method has not been called.
+ * This does not activate the panel, just updates its content.
+ */
+ refresh : function(){
+ if(this.refreshDelegate){
+ this.loaded = false;
+ this.refreshDelegate();
+ }
+ },
+
+ /** @private */
+ _setLoaded : function(){
+ this.loaded = true;
+ },
+
+ /** @private */
+ closeClick : function(e){
+ var e = {};
+ this.fireEvent('beforeclose', this, e);
+ if(e.cancel !== true){
+ this.tabPanel.removeTab(this.id);
+ }
+ },
+ /**
+ * The text displayed in the tooltip for the close icon.
+ * @type String
+ */
+ closeText : 'Close this tab'
+};
+
+/** @private */
+YAHOO.ext.TabPanel.prototype.createStrip = function(container){
+ var strip = document.createElement('div');
+ strip.className = 'ytab-wrap';
+ container.appendChild(strip);
+ return strip;
+};
+/** @private */
+YAHOO.ext.TabPanel.prototype.createStripList = function(strip){
+ // div wrapper for retard IE
+ strip.innerHTML = '
+ // basic tabs 1, built from existing content
+ var tabs = new YAHOO.ext.TabPanel('tabs1');
+ tabs.addTab('script', "View Script");
+ tabs.addTab('markup', "View Markup");
+ tabs.activate('script');
+
+ // more advanced tabs, built from javascript
+ var jtabs = new YAHOO.ext.TabPanel('jtabs');
+ jtabs.addTab('jtabs-1', "Normal Tab", "My content was added during construction.");
+
+ // set up the UpdateManager
+ var tab2 = jtabs.addTab('jtabs-2', "Ajax Tab 1");
+ var updater = tab2.getUpdateManager();
+ updater.setDefaultUrl('ajax1.htm');
+ tab2.onActivate.subscribe(updater.refresh, updater, true);
+
+ // Use setUrl for Ajax loading
+ var tab3 = jtabs.addTab('jtabs-3', "Ajax Tab 2");
+ tab3.setUrl('ajax2.htm', null, true);
+
+ // Disabled tab
+ var tab4 = jtabs.addTab('tabs1-5', "Disabled Tab", "Can't see me cause I'm disabled");
+ tab4.disable();
+
+ jtabs.activate('jtabs-1');
+}
+ *
+ * Create a data model bound view:
+
+For an example of creating a JSON/UpdateManager view, see {@link YAHOO.ext.JsonView}.
+ *
+var dataModel = new YAHOO.ext.grid.XMLDataModel(...);
+var view = new YAHOO.ext.View('my-element',
+ '<div id="{0}">{2} - {1}</div>', // auto create template
+ dataModel, {
+ singleSelect: true,
+ selectedClass: 'ydataview-selected'
+ });
+
+// listen for node click?
+view.on('click', function(vw, index, node, e){
+ alert('Node "' + node.id + '" at index: ' + index + ' was clicked.');
+});
+
+// load XML data
+dataModel.load('foobar.xml');
+
+ * Note: The root of your template must be a single node. Table/row implementations may work but are not supported due to
+ * IE's limited insertion support with tables and Opera's faulty event bubbling.
+ * @constructor
+ * Create a new View
+ * @param {String/HTMLElement/Element} container The container element where the view is to be rendered.
+ * @param {String/DomHelper.Template} tpl The rendering template or a string to create a template with
+ * @param {DataModel} dataModel The bound data model
+ * @param {Object} config The config object
+*/
+YAHOO.ext.View = function(container, tpl, dataModel, config){
+ this.el = getEl(container, true);
+ this.nodes = this.el.dom.childNodes;
+ if(typeof tpl == 'string'){
+ tpl = new YAHOO.ext.Template(tpl);
+ }
+ tpl.compile();
+ /**
+ * The template used by this View
+ * @type {YAHOO.ext.DomHelper.Template}
+ */
+ this.tpl = tpl;
+ this.setDataModel(dataModel);
+ var CE = YAHOO.util.CustomEvent;
+ /** @private */
+ this.events = {
+ /**
+ * @event beforeclick
+ * Fires before a click is processed. Returns false to cancel the default action.
+ * @param {YAHOO.ext.View} this
+ * @param {Number} index The index of the target node
+ * @param {HTMLElement} node The target node
+ * @param {YAHOO.ext.EventObject} e The raw event object
+ */
+ 'beforeclick' : true,
+ /**
+ * @event click
+ * Fires when a template node is clicked.
+ * @param {YAHOO.ext.View} this
+ * @param {Number} index The index of the target node
+ * @param {HTMLElement} node The target node
+ * @param {YAHOO.ext.EventObject} e The raw event object
+ */
+ 'click' : true,
+ /**
+ * @event dblclick
+ * Fires when a template node is double clicked.
+ * @param {YAHOO.ext.View} this
+ * @param {Number} index The index of the target node
+ * @param {HTMLElement} node The target node
+ * @param {YAHOO.ext.EventObject} e The raw event object
+ */
+ 'dblclick' : true,
+ /**
+ * @event contextmenu
+ * Fires when a template node is right clicked.
+ * @param {YAHOO.ext.View} this
+ * @param {Number} index The index of the target node
+ * @param {HTMLElement} node The target node
+ * @param {YAHOO.ext.EventObject} e The raw event object
+ */
+ 'contextmenu' : true,
+ /**
+ * @event selectionchange
+ * Fires when the selected nodes change.
+ * @param {YAHOO.ext.View} this
+ * @param {Array} selections Array of the selected nodes
+ */
+ 'selectionchange' : true,
+
+ /**
+ * @event beforeselect
+ * Fires before a selection is made. If any handlers return false, the selection is cancelled.
+ * @param {YAHOO.ext.View} this
+ * @param {HTMLElement} node The node to be selected
+ * @param {Array} selections Array of currently selected nodes
+ */
+ 'beforeselect' : true
+ };
+ this.el.mon("click", this.onClick, this, true);
+ this.el.mon("dblclick", this.onDblClick, this, true);
+ this.el.mon("contextmenu", this.onContextMenu, this, true);
+
+ /**
+ * The css class to add to selected nodes
+ * @type {YAHOO.ext.DomHelper.Template}
+ */
+ this.selectedClass = 'ydataview-selected';
+
+ this.emptyText = '';
+
+ this.selections = [];
+
+ this.lastSelection = null;
+
+ /**
+ * The root property in the loaded json object that contains the data
+ * @type {String}
+ */
+ this.jsonRoot = null;
+ YAHOO.ext.util.Config.apply(this, config);
+ if(this.renderUpdates || this.jsonRoot){
+ var um = this.el.getUpdateManager();
+ um.setRenderer(this);
+ }
+};
+
+YAHOO.extendX(YAHOO.ext.View, YAHOO.ext.util.Observable, {
+ /**
+ * Returns the element this view is bound to.
+ * @return {YAHOO.ext.Element}
+ */
+ getEl : function(){
+ return this.el;
+ },
+
+ render : function(el, response){
+ this.clearSelections();
+ this.el.update('');
+ var o;
+ try{
+ o = YAHOO.ext.util.JSON.decode(response.responseText);
+ if(this.jsonRoot){
+ o = eval('o.' + this.jsonRoot);
+ }
+ }catch(e){}
+ /**
+ * The current json data or null
+ */
+ this.jsonData = o;
+ this.beforeRender();
+ this.refresh();
+ },
+
+ beforeRender : function(){
+
+ },
+
+ /**
+ * Refreshes the view.
+ */
+ refresh : function(){
+ this.clearSelections();
+ this.el.update('');
+ this.html = [];
+ if(this.renderUpdates || this.jsonRoot){
+ var o = this.jsonData;
+ if(o){
+ for(var i = 0, len = o.length; i < len; i++) {
+ this.renderEach(o[i]);
+ }
+ }
+ }else{
+ this.dataModel.each(this.renderEach, this);
+ }
+ var strHtml;
+ if(this.html.length > 0){
+ strHtml = this.html.join('');
+ }else{
+ strHtml = this.emptyText;
+ }
+ this.el.update(strHtml);
+ this.html = null;
+ this.nodes = this.el.dom.childNodes;
+ this.updateIndexes(0);
+ },
+
+ /**
+ * Function to override to reformat the data that is sent to
+ * the template for each node.
+ * @param {Array/Object} data The raw data (array of colData for a data model bound view or
+ * a JSON object for an UpdateManager bound view).
+ * @param {Number} index The index of the data within the data model
+ */
+ prepareData : function(data, index){
+ return data;
+ },
+
+ renderEach : function(data){
+ this.html[this.html.length] = this.tpl.applyTemplate(this.prepareData(data));
+ },
+
+ /**
+ * Refresh an individual node.
+ * @param {Number} index
+ */
+ refreshNode : function(index){
+ this.refreshNodes(index, index);
+ },
+
+ refreshNodes : function(dm, startIndex, endIndex){
+ this.clearSelections();
+ var dm = this.dataModel;
+ var ns = this.nodes;
+ for(var i = startIndex; i <= endIndex; i++){
+ var d = this.prepareData(dm.getRow(i), i);
+ if(i < ns.length-1){
+ var old = ns[i];
+ this.tpl.insertBefore(old, d);
+ this.el.dom.removeChild(old);
+ }else{
+ this.tpl.append(this.el.dom, d);
+ }
+ }
+ this.updateIndexes(startIndex, endIndex);
+ },
+
+ deleteNodes : function(dm, startIndex, endIndex){
+ this.clearSelections();
+ if(startIndex == 0 && endIndex >= this.nodes.length-1){
+ this.el.update('');
+ }else{
+ var el = this.el.dom;
+ for(var i = startIndex; i <= endIndex; i++){
+ el.removeChild(this.nodes[startIndex]);
+ }
+ this.updateIndexes(startIndex);
+ }
+ },
+
+ insertNodes : function(dm, startIndex, endIndex){
+ if(this.nodes.length == 0){
+ this.refresh();
+ }else{
+ this.clearSelections();
+ var t = this.tpl;
+ var before = this.nodes[startIndex];
+ var dm = this.dataModel;
+ if(before){
+ for(var i = startIndex; i <= endIndex; i++){
+ t.insertBefore(before, this.prepareData(dm.getRow(i), i));
+ }
+ }else{
+ var el = this.el.dom;
+ for(var i = startIndex; i <= endIndex; i++){
+ t.append(el, this.prepareData(dm.getRow(i), i));
+ }
+ }
+ this.updateIndexes(startIndex);
+ }
+ },
+
+ updateIndexes : function(dm, startIndex, endIndex){
+ var ns = this.nodes;
+ startIndex = startIndex || 0;
+ endIndex = endIndex || ns.length-1;
+ for(var i = startIndex; i <= endIndex; i++){
+ ns[i].nodeIndex = i;
+ }
+ },
+
+ /**
+ * Changes the data model this view uses and refresh the view.
+ * @param {DataModel} dataModel
+ */
+ setDataModel : function(dm){
+ if(!dm) return;
+ this.unplugDataModel(this.dataModel);
+ this.dataModel = dm;
+ dm.on('cellupdated', this.refreshNode, this, true);
+ dm.on('datachanged', this.refresh, this, true);
+ dm.on('rowsdeleted', this.deleteNodes, this, true);
+ dm.on('rowsinserted', this.insertNodes, this, true);
+ dm.on('rowsupdated', this.refreshNodes, this, true);
+ dm.on('rowssorted', this.refresh, this, true);
+ this.refresh();
+ },
+
+ /**
+ * Unplug the data model and stop updates.
+ * @param {DataModel} dataModel
+ */
+ unplugDataModel : function(dm){
+ if(!dm) return;
+ dm.removeListener('cellupdated', this.refreshNode, this);
+ dm.removeListener('datachanged', this.refresh, this);
+ dm.removeListener('rowsdeleted', this.deleteNodes, this);
+ dm.removeListener('rowsinserted', this.insertNodes, this);
+ dm.removeListener('rowsupdated', this.refreshNodes, this);
+ dm.removeListener('rowssorted', this.refresh, this);
+ this.dataModel = null;
+ },
+
+ /**
+ * Returns the template node the passed child belongs to or null if it doesn't belong to one.
+ * @param {HTMLElement} node
+ * @return {HTMLElement} The template node
+ */
+ findItemFromChild : function(node){
+ var el = this.el.dom;
+ if(!node || node.parentNode == el){
+ return node;
+ }
+ var p = node.parentNode;
+ while(p && p != el){
+ if(p.parentNode == el){
+ return p;
+ }
+ p = p.parentNode;
+ }
+ return null;
+ },
+
+ /** @ignore */
+ onClick : function(e){
+ var item = this.findItemFromChild(e.getTarget());
+ if(item){
+ var index = this.indexOf(item);
+ if(this.onItemClick(item, index, e) !== false){
+ this.fireEvent('click', this, index, item, e);
+ }
+ }else{
+ this.clearSelections();
+ }
+ },
+
+ /** @ignore */
+ onContextMenu : function(e){
+ var item = this.findItemFromChild(e.getTarget());
+ if(item){
+ this.fireEvent('contextmenu', this, this.indexOf(item), item, e);
+ }
+ },
+
+ /** @ignore */
+ onDblClick : function(e){
+ var item = this.findItemFromChild(e.getTarget());
+ if(item){
+ this.fireEvent('dblclick', this, this.indexOf(item), item, e);
+ }
+ },
+
+ onItemClick : function(item, index, e){
+ if(this.fireEvent('beforeclick', this, index, item, e) !== false){
+ if(this.multiSelect || this.singleSelect){
+ if(this.multiSelect && e.shiftKey && this.lastSelection){
+ this.select(this.getNodes(this.indexOf(this.lastSelection), index), false);
+ }else{
+ this.select(item, this.multiSelect && e.ctrlKey);
+ this.lastSelection = item;
+ }
+ e.preventDefault();
+ }
+ return true;
+ }else{
+ return false;
+ }
+ },
+
+ /**
+ * Get the number of selected nodes.
+ * @return {Number}
+ */
+ getSelectionCount : function(){
+ return this.selections.length;
+ },
+
+ /**
+ * Get the currently selected nodes.
+ * @return {Array} An array of HTMLElements
+ */
+ getSelectedNodes : function(){
+ return this.selections;
+ },
+
+ /**
+ * Get the indexes of the selected nodes.
+ * @return {Array}
+ */
+ getSelectedIndexes : function(){
+ var indexes = [];
+ for(var i = 0, len = this.selections.length; i < len; i++) {
+ indexes.push(this.selections[i].nodeIndex);
+ }
+ return indexes;
+ },
+
+ /**
+ * Clear all selections
+ * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange event
+ */
+ clearSelections : function(suppressEvent){
+ if(this.multiSelect || this.singleSelect){
+ YAHOO.util.Dom.removeClass(this.selections, this.selectedClass);
+ this.selections = [];
+ if(!suppressEvent){
+ this.fireEvent('selectionchange', this, this.selections);
+ }
+ }
+ },
+
+ /**
+ * Returns true if the passed node is selected
+ * @param {HTMLElement/Number} node The node or node index
+ * @return {Boolean}
+ */
+ isSelected : function(node){
+ node = this.getNode(node);
+ var s = this.selections;
+ if(s.length < 1){
+ return false;
+ }
+ if(s.indexOf){
+ return s.indexOf(node) !== -1;
+ }else{
+ for(var i = 0, len = s.length; i < len; i++){
+ if (s[i] == node){
+ return true;
+ }
+ }
+ return false;
+ }
+ },
+
+ /**
+ * Selects nodes.
+ * @param {Array/HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node, id of a template node or an array of any of those to select
+ * @param {Boolean} keepExisting (optional) true to keep existing selections
+ * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange vent
+ */
+ select : function(nodeInfo, keepExisting, suppressEvent){
+ if(!keepExisting){
+ this.clearSelections(true);
+ }
+ if(nodeInfo instanceof Array){
+ for(var i = 0, len = nodeInfo.length; i < len; i++) {
+ this.select(nodeInfo[i], true, true);
+ }
+ }else{
+ var node = this.getNode(nodeInfo);
+ if(node && !this.isSelected(node)){
+ if(this.fireEvent('beforeselect', this, node, this.selections) !== false){
+ YAHOO.util.Dom.addClass(node, this.selectedClass);
+ this.selections.push(node);
+ }
+ }
+ }
+ if(!suppressEvent){
+ this.fireEvent('selectionchange', this, this.selections);
+ }
+ },
+
+ /**
+ * Gets a template node.
+ * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node
+ * @return {HTMLElement} The node or null if it wasn't found
+ */
+ getNode : function(nodeInfo){
+ if(typeof nodeInfo == 'object'){
+ return nodeInfo;
+ }else if(typeof nodeInfo == 'string'){
+ return document.getElementById(nodeInfo);
+ }else if(typeof nodeInfo == 'number'){
+ return this.nodes[nodeInfo];
+ }
+ return null;
+ },
+
+ /**
+ * Gets a range template nodes.
+ * @param {Number} startIndex
+ * @param {Number} endIndex
+ * @return {Array} An array of nodes
+ */
+ getNodes : function(start, end){
+ var ns = this.nodes;
+ start = start || 0;
+ end = typeof end == 'undefined' ? ns.length-1 : end;
+ var nodes = [];
+ if(start <= end){
+ for(var i = start; i <= end; i++) {
+ nodes.push(ns[i]);
+ }
+ }else{
+ for(var i = start; i >= end; i--) {
+ nodes.push(ns[i]);
+ }
+ }
+ return nodes;
+ },
+
+ /**
+ * Finds the index of the passed node
+ * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node
+ * @return {Number} The index of the node or -1
+ */
+ indexOf : function(node){
+ node = this.getNode(node);
+ if(typeof node.nodeIndex == 'number'){
+ return node.nodeIndex;
+ }
+ var ns = this.nodes;
+ for(var i = 0, len = ns.length; i < len; i++) {
+ if(ns[i] == node){
+ return i;
+ }
+ }
+ return -1;
+ }
+});
+
+/**
+ * @class YAHOO.ext.JsonView
+ * @extends YAHOO.ext.View
+ * Shortcut class to create a JSON + UpdateManager template view. Usage:
+
+ * @constructor
+ * Create a new JsonView
+ * @param {String/HTMLElement/Element} container The container element where the view is to be rendered.
+ * @param {DomHelper.Template} tpl The rendering template
+ * @param {Object} config The config object
+ */
+YAHOO.ext.JsonView = function(container, tpl, config){
+ var cfg = config || {};
+ cfg.renderUpdates = true;
+ YAHOO.ext.JsonView.superclass.constructor.call(this, container, tpl, null, cfg);
+ /**
+ * @event beforerender
+ * Fires before rendering of the downloaded json data.
+ * @param {YAHOO.ext.View} this
+ * @param {Object} data The json data loaded
+ */
+ this.events['beforerender'] = true;
+ /**
+ * @event load
+ * Fires when data is loaded.
+ * @param {YAHOO.ext.View} this
+ * @param {Object} data The json data loaded
+ * @param {Object} response The raw Connect response object
+ */
+ this.events['load'] = true;
+ /**
+ * @event loadexception
+ * Fires when loading fails.
+ * @param {YAHOO.ext.View} this
+ * @param {Object} response The raw Connect response object
+ */
+ this.events['loadexception'] = true;
+ this.el.getUpdateManager().on('update', this.onLoad, this, true);
+ this.el.getUpdateManager().on('failure', this.onLoadException, this, true);
+};
+YAHOO.extendX(YAHOO.ext.JsonView, YAHOO.ext.View, {
+ /**
+ * Performs an async request, loading the JSON from the response. If params are specified it uses POST, otherwise it uses GET.
+ * @param {Object/String/Function} url The url for this request or a function to call to get the url or a config object containing any of the following options:
+
+var view = new YAHOO.ext.JsonView('my-element',
+ '<div id="{id}">{foo} - {bar}</div>', // auto create template
+ { multiSelect: true, jsonRoot: 'data' });
+
+// listen for node click?
+view.on('click', function(vw, index, node, e){
+ alert('Node "' + node.id + '" at index: ' + index + ' was clicked.');
+});
+
+// direct load of JSON data
+view.load('foobar.php');
+
+
+// Example from my blog list
+var tpl = new YAHOO.ext.Template(
+ '<div class="entry">' +
+ '<a class="entry-title" href="{link}">{title}</a>' +
+ '<h4>{date} by {author} | {comments} Comments</h4>{description}' +
+ '</div><hr />'
+);
+
+var moreView = new YAHOO.ext.JsonView('entry-list', tpl, {
+ jsonRoot: 'posts'
+});
+moreView.on('beforerender', this.sortEntries, this, true);
+moreView.load({
+ url:'/blog/get-posts.php',
+ params: 'allposts=true',
+ text:'Loading Blog Entries...'
+});
+
+ * The only required property is url. The optional properties nocache, text and scripts
+ * are shorthand for disableCaching, indicatorText and loadScripts and are used to set their associated property on this UpdateManager instance.
+ * @param {String/Object} params (optional) The parameters to pass as either a url encoded string "param1=1¶m2=2" or an object {param1: 1, param2: 2}
+ * @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess)
+ * @param {Boolean} discardUrl (optional) By default when you execute an update the defaultUrl is changed to the last used url. If true, it will not store the url.
+ */
+ load : function(){
+ var um = this.el.getUpdateManager();
+ um.update.apply(um, arguments);
+ },
+
+ /**
+ * Get the number of records in the current JSON dataset
+ * @return {Number}
+ */
+ getCount : function(){
+ return this.jsonData ? this.jsonData.length : 0;
+ },
+
+ /**
+ * Returns the JSON object for the specified node(s)
+ * @param {HTMLElement/Array} node The node or an array of nodes
+ * @return {Object/Array} If you pass in an array, you get an array back, otherwise
+ * you get the JSON object for the node
+ */
+ getNodeData : function(node){
+ if(node instanceof Array){
+ var data = [];
+ for(var i = 0, len = node.length; i < len; i++){
+ data.push(this.getNodeData(node[i]));
+ }
+ return data;
+ }
+ return this.jsonData[this.indexOf(node)] || null;
+ },
+
+ beforeRender : function(){
+ this.snapshot = this.jsonData;
+ if(this.sortInfo){
+ this.sort.apply(this, this.sortInfo);
+ }
+ this.fireEvent('beforerender', this, this.jsonData);
+ },
+
+ onLoad : function(el, o){
+ this.fireEvent('load', this, this.jsonData, o);
+ },
+
+ onLoadException : function(el, o){
+ this.fireEvent('loadexception', this, o);
+ },
+
+ /**
+ * Filter the data by a specific property.
+ * @param {String} property A property on your JSON objects
+ * @param {String/RegExp} value Either string that the property values
+ * should start with or a RegExp to test against the property
+ */
+ filter : function(property, value){
+ if(this.jsonData){
+ var data = [];
+ var ss = this.snapshot;
+ if(typeof value == 'string'){
+ var vlen = value.length;
+ if(vlen == 0){
+ this.clearFilter();
+ return;
+ }
+ value = value.toLowerCase();
+ for(var i = 0, len = ss.length; i < len; i++){
+ var o = ss[i];
+ if(o[property].substr(0, vlen).toLowerCase() == value){
+ data.push(o);
+ }
+ }
+ }else if(value.exec){ // regex?
+ for(var i = 0, len = ss.length; i < len; i++){
+ var o = ss[i];
+ if(value.test(o[property])){
+ data.push(o);
+ }
+ }
+ }else{
+ return;
+ }
+ this.jsonData = data;
+ this.refresh();
+ }
+ },
+
+ /**
+ * Filter by a function. The passed function will be called with each
+ * object in the current dataset. If the function returns true, the value is kept
+ * otherwise it is filtered.
+ * @param {Function} fn
+ * @param {Object} scope (optional) The scope of the function (defaults to this JsonView)
+ */
+ filterBy : function(fn, scope){
+ if(this.jsonData){
+ var data = [];
+ var ss = this.snapshot;
+ for(var i = 0, len = ss.length; i < len; i++){
+ var o = ss[i];
+ if(fn.call(scope|| this, o)){
+ data.push(o);
+ }
+ }
+ this.jsonData = data;
+ this.refresh();
+ }
+ },
+
+ /**
+ * Clears the current filter.
+ */
+ clearFilter : function(){
+ if(this.snapshot && this.jsonData != this.snapshot){
+ this.jsonData = this.snapshot;
+ this.refresh();
+ }
+ },
+
+
+ /**
+ * Sorts the data for this view and refreshes it.
+ * @param {String} property A property on your JSON objects to sort on
+ * @param {String} direction (optional) desc or asc (defaults to asc)
+ * @param {Function} sortType (optional) A function to call to convert the data to a sortable value.
+ */
+ sort : function(property, dir, sortType){
+ this.sortInfo = Array.prototype.slice.call(arguments, 0);
+ if(this.jsonData){
+ var p = property;
+ var dsc = dir && dir.toLowerCase() == 'desc';
+ var f = function(o1, o2){
+ var v1 = sortType ? sortType(o1[p]) : o1[p];
+ var v2 = sortType ? sortType(o2[p]) : o2[p];;
+ if(v1 < v2){
+ return dsc ? +1 : -1;
+ }else if(v1 > v2){
+ return dsc ? -1 : +1;
+ }else{
+ return 0;
+ }
+ };
+ this.jsonData.sort(f);
+ this.refresh();
+ if(this.jsonData != this.snapshot){
+ this.snapshot.sort(f);
+ }
+ }
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/widgets/Toolbar.js b/frontend/beta/js/YUI-extensions/widgets/Toolbar.js
new file mode 100644
index 0000000..7c14753
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/widgets/Toolbar.js
@@ -0,0 +1,296 @@
+/**
+ * @class YAHOO.ext.Toolbar
+ * Basic Toolbar used by the Grid to create the paging toolbar. This class is reusable but functionality
+ * is limited. Look for more functionality in a future version.
+ * @constructor
+ * @param {String/HTMLElement/Element} container
+ * @param {Array} buttons (optional) array of button configs or elements to add
+ */
+ YAHOO.ext.Toolbar = function(container, buttons){
+ this.el = getEl(container, true);
+ var div = document.createElement('div');
+ div.className = 'ytoolbar';
+ var tb = document.createElement('table');
+ tb.border = 0;
+ tb.cellPadding = 0;
+ tb.cellSpacing = 0;
+ div.appendChild(tb);
+ var tbody = document.createElement('tbody');
+ tb.appendChild(tbody);
+ var tr = document.createElement('tr');
+ tbody.appendChild(tr);
+ this.el.dom.appendChild(div);
+ this.tr = tr;
+ if(buttons){
+ this.add.apply(this, buttons);
+ }
+};
+
+YAHOO.ext.Toolbar.prototype = {
+ /**
+ * Adds element(s) to the toolbar - this function takes a variable number of
+ * arguments of mixed type and adds them to the toolbar...
+ *
+ * @param {Mixed} arg If arg is a ToolbarButton, it is added. If arg is a string, it is wrapped
+ * in a ytb-text element and added unless the text is "separator" in which case a separator
+ * is added. Otherwise, it is assumed the element is an HTMLElement and it is added directly.
+ */
+ add : function(){
+ for(var i = 0; i < arguments.length; i++){
+ var el = arguments[i];
+ var td = document.createElement('td');
+ this.tr.appendChild(td);
+ if(el instanceof YAHOO.ext.ToolbarButton){
+ el.init(td);
+ }else if(el instanceof Array){
+ this.addButton(el);
+ }else if(typeof el == 'string'){
+ var span = document.createElement('span');
+ if(el == 'separator'){
+ span.className = 'ytb-sep';
+ }else{
+ span.innerHTML = el;
+ span.className = 'ytb-text';
+ }
+ td.appendChild(span);
+ }else if(typeof el == 'object' && el.nodeType){ // must be element?
+ td.appendChild(el);
+ }else if(typeof el == 'object'){ // must be button config?
+ this.addButton(el);
+ }
+ }
+ },
+
+ /**
+ * Returns the element for this toolbar
+ * @return {YAHOO.ext.Element}
+ */
+ getEl : function(){
+ return this.el;
+ },
+
+ /**
+ * Adds a separator
+ */
+ addSeparator : function(){
+ var td = document.createElement('td');
+ this.tr.appendChild(td);
+ var span = document.createElement('span');
+ span.className = 'ytb-sep';
+ td.appendChild(span);
+ },
+
+ /**
+ * Add a button (or buttons), see {@link YAHOO.ext.ToolbarButton} for more info on the config
+ * @param {Object/Array} config A button config or array of configs
+ * @return {YAHOO.ext.ToolbarButton/Array}
+ */
+ addButton : function(config){
+ if(config instanceof Array){
+ var buttons = [];
+ for(var i = 0, len = config.length; i < len; i++) {
+ buttons.push(this.addButton(config[i]));
+ }
+ return buttons;
+ }
+ var b = config;
+ if(!(config instanceof YAHOO.ext.ToolbarButton)){
+ b = new YAHOO.ext.ToolbarButton(config);
+ }
+ this.add(b);
+ return b;
+ },
+
+ /**
+ * Adds text to the toolbar
+ * @param {String} text The text to add
+ * @return {HTMLElement} The span element created which you can use to update the text.
+ */
+ addText : function(text){
+ var td = document.createElement('td');
+ this.tr.appendChild(td);
+ var span = document.createElement('span');
+ span.className = 'ytb-text';
+ span.innerHTML = text;
+ td.appendChild(span);
+ return span;
+ },
+
+ /**
+ * Inserts a button (or buttons) at the specified index
+ * @param {Number} index The index where the buttons are to be inserted
+ * @param {Object/Array} config A button config or array of configs
+ * @return {YAHOO.ext.ToolbarButton/Array}
+ */
+ insertButton : function(index, config){
+ if(config instanceof Array){
+ var buttons = [];
+ for(var i = 0, len = config.length; i < len; i++) {
+ buttons.push(this.insertButton(index + i, config[i]));
+ }
+ return buttons;
+ }
+ var b = new YAHOO.ext.ToolbarButton(config);
+ var td = document.createElement('td');
+ var nextSibling = this.tr.childNodes[index];
+ if (nextSibling)
+ this.tr.insertBefore(td, nextSibling);
+ else
+ this.tr.appendChild(td);
+ b.init(td);
+ return b;
+ }
+};
+
+/**
+ * @class YAHOO.ext.ToolbarButton
+ * A toolbar button. The config has the following options:
+ *
+view.load({
+ url: 'your-url.php',
+ params: {param1: 'foo', param2: 'bar'}, // or a URL encoded string
+ callback: yourFunction,
+ scope: yourObject, //(optional scope)
+ discardUrl: false,
+ nocache: false,
+ text: 'Loading...',
+ timeout: 30,
+ scripts: false
+});
+
+ *
myFunction.createCallback(myarg, myarg2)
+ * Will create a function that is bound to those 2 args.
+ * @return {Function} The new function
+*/
+Function.prototype.createCallback = function(/*args...*/){
+ // make args available, in function below
+ var args = arguments;
+ var method = this;
+ return function() {
+ return method.apply(window, args);
+ };
+};
+
+/**
+ * Creates a delegate (callback) that sets the scope to obj.
+ * Call directly on any function. Example: this.myFunction.createDelegate(this)
+ * Will create a function that is automatically scoped to this.
+ * @param {Object} obj (optional) The object for which the scope is set
+ * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
+ * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
+ * if a number the args are inserted at the specified position
+ * @return {Function} The new function
+ */
+Function.prototype.createDelegate = function(obj, args, appendArgs){
+ var method = this;
+ return function() {
+ var callArgs = args || arguments;
+ if(appendArgs === true){
+ callArgs = Array.prototype.slice.call(arguments, 0);
+ callArgs = callArgs.concat(args);
+ }else if(typeof appendArgs == 'number'){
+ callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
+ var applyArgs = [appendArgs, 0].concat(args); // create method call params
+ Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
+ }
+ return method.apply(obj || window, callArgs);
+ };
+};
+
+/**
+ * Calls this function after the number of millseconds specified.
+ * @param {Number} millis The number of milliseconds for the setTimeout call
+ * @param {Object} obj (optional) The object for which the scope is set
+ * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
+ * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
+ * if a number the args are inserted at the specified position
+ * @return {Number} The timeout id that can be used with clearTimeout
+ */
+Function.prototype.defer = function(millis, obj, args, appendArgs){
+ return setTimeout(this.createDelegate(obj, args, appendArgs), millis);
+};
+/**
+ * Create a combined function call sequence of the original function + the passed function.
+ * The resulting function returns the results of the original function.
+ * The passed fcn is called with the parameters of the original function
+ * @param {Function} fcn The function to sequence
+ * @param {Object} scope (optional) The scope of the passed fcn (Defaults to scope of original function or window)
+ * @return {Function} The new function
+ */
+Function.prototype.createSequence = function(fcn, scope){
+ if(typeof fcn != 'function'){
+ return this;
+ }
+ var method = this;
+ return function() {
+ var retval = method.apply(this || window, arguments);
+ fcn.apply(scope || this || window, arguments);
+ return retval;
+ };
+};
+
+/*
+ * IE will leak if this isn't here
+ */
+YAHOO.util.Event.on(window, 'unload', function(){
+ var p = Function.prototype;
+ delete p.createSequence;
+ delete p.defer;
+ delete p.createDelegate;
+ delete p.createCallback;
+ delete p.createInterceptor;
+});
+
+/**
+ * Creates an interceptor function. The passed fcn is called before the original one. If it returns false, the original one is not called.
+ * The resulting function returns the results of the original function.
+ * The passed fcn is called with the parameters of the original function.
+ * @addon
+ * @param {Function} fcn The function to call before the original
+ * @param {Object} scope (optional) The scope of the passed fcn (Defaults to scope of original function or window)
+ * @return {Function} The new function
+ */
+Function.prototype.createInterceptor = function(fcn, scope){
+ if(typeof fcn != 'function'){
+ return this;
+ }
+ var method = this;
+ return function() {
+ fcn.target = this;
+ fcn.method = method;
+ if(fcn.apply(scope || this || window, arguments) === false){
+ return;
+ }
+ return method.apply(this || window, arguments);;
+ };
+};
+
+/**
+ * @class YAHOO.ext.util.Browser
+ * @singleton
+ */
+YAHOO.ext.util.Browser = new function(){
+ var ua = navigator.userAgent.toLowerCase();
+ /** @type Boolean */
+ this.isOpera = (ua.indexOf('opera') > -1);
+ /** @type Boolean */
+ this.isSafari = (ua.indexOf('webkit') > -1);
+ /** @type Boolean */
+ this.isIE = (window.ActiveXObject);
+ /** @type Boolean */
+ this.isIE7 = (ua.indexOf('msie 7') > -1);
+ /** @type Boolean */
+ this.isGecko = !this.isSafari && (ua.indexOf('gecko') > -1);
+
+ if(ua.indexOf("windows") != -1 || ua.indexOf("win32") != -1){
+ /** @type Boolean */
+ this.isWindows = true;
+ }else if(ua.indexOf("macintosh") != -1){
+ /** @type Boolean */
+ this.isMac = true;
+ }
+ if(this.isIE && !this.isIE7){
+ try{
+ document.execCommand("BackgroundImageCache", false, true);
+ }catch(e){}
+ }
+}();
+
+ /**
+ * Enable custom handler signature and event cancelling. Using fireDirect() instead of fire() calls the subscribed event handlers
+ * with the exact parameters passed to fireDirect, instead of the usual (eventType, args[], obj). IMO this is more intuitive
+ * and promotes cleaner code. Also, if an event handler returns false, it is returned by fireDirect and no other handlers will be called.
+ * Example:
+ */
+YAHOO.util.CustomEvent.prototype.fireDirect = function(){
+ var len=this.subscribers.length;
+ for (var i=0; i
+ * if(beforeUpdateEvent.fireDirect(myArg, myArg2) !== false){
+ * // do update
+ * }
+ * For example:
+ *
+ */
+YAHOO.ext.util.Observable = function(){};
+YAHOO.ext.util.Observable.prototype = {
+ /**
+ * Fires the specified event with the passed parameters (minus the event name).
+ * @param {String} eventName
+ * @param {Object...} args Variable number of parameters are passed to handlers
+ * @return {Boolean} returns false if any of the handlers return false otherwise it returns true
+ */
+ fireEvent : function(){
+ var ce = this.events[arguments[0].toLowerCase()];
+ if(typeof ce == 'object'){
+ return ce.fireDirect.apply(ce, Array.prototype.slice.call(arguments, 1));
+ }else{
+ return true;
+ }
+ },
+ /**
+ * Appends an event handler to this component
+ * @param {String} eventName The type of event to listen for
+ * @param {Function} handler The method the event invokes
+ * @param {Object} scope (optional) The scope (this object) for the handler
+ * @param {boolean} override (optional) If true, scope becomes the scope
+ */
+ addListener : function(eventName, fn, scope, override){
+ eventName = eventName.toLowerCase();
+ var ce = this.events[eventName];
+ if(!ce){
+ // added for a better message when subscribing to wrong event
+ throw 'You are trying to listen for an event that does not exist: "' + eventName + '".';
+ }
+ if(typeof ce == 'boolean'){
+ ce = new YAHOO.util.CustomEvent(eventName);
+ this.events[eventName] = ce;
+ }
+ ce.subscribe(fn, scope, override);
+ },
+
+ /**
+ * Appends an event handler to this component that is delayed the specified number of milliseconds. This
+ * is useful for events that modify the DOM and need to wait for the browser to catch up.
+ * @param {String} eventName The type of event to listen for
+ * @param {Function} handler The method the event invokes
+ * @param {Object} scope (optional) The scope (this object) for the handler
+ * @param {Number} delay (optional) The number of milliseconds to delay (defaults to 1 millisecond)
+ * @return {Function} The wrapped function that was created (can be used to remove the listener)
+ */
+ delayedListener : function(eventName, fn, scope, delay){
+ var newFn = function(){
+ setTimeout(fn.createDelegate(scope, arguments), delay || 1);
+ }
+ this.addListener(eventName, newFn);
+ return newFn;
+ },
+
+ /**
+ * Appends an event handler to this component that is buffered. If the event is triggered more than once
+ * in the specified time-frame, only the last one actually fires.
+ * @param {String} eventName The type of event to listen for
+ * @param {Function} handler The method the event invokes
+ * @param {Object} scope (optional) The scope (this object) for the handler
+ * @param {Number} millis (optional) The number of milliseconds to buffer (defaults to 250)
+ * @return {Function} The wrapped function that was created (can be used to remove the listener)
+ */
+ bufferedListener : function(eventName, fn, scope, millis){
+ var task = new YAHOO.ext.util.DelayedTask();
+ var newFn = function(){
+ task.delay(millis || 250, fn, scope, Array.prototype.slice.call(arguments, 0));
+ }
+ this.addListener(eventName, newFn);
+ return newFn;
+ },
+
+ /**
+ * Removes a listener
+ * @param {String} eventName The type of event to listen for
+ * @param {Function} handler The handler to remove
+ * @param {Object} scope (optional) The scope (this object) for the handler
+ */
+ removeListener : function(eventName, fn, scope){
+ var ce = this.events[eventName.toLowerCase()];
+ if(typeof ce == 'object'){
+ ce.unsubscribe(fn, scope);
+ }
+ },
+
+ /**
+ * Removes all listeners for this object
+ */
+ purgeListeners : function(){
+ for(var evt in this.events){
+ if(typeof this.events[evt] == 'object'){
+ this.events[evt].unsubscribeAll();
+ }
+ }
+ }
+};
+/**
+ * Appends an event handler to this element (shorthand for addListener)
+ * @param {String} eventName The type of event to listen for
+ * @param {Function} handler The method the event invokes
+ * @param {Object} scope (optional) The scope (this object) for the handler
+ * @param {boolean} override (optional) If true, scope becomes the scope
+ * @method
+ */
+YAHOO.ext.util.Observable.prototype.on = YAHOO.ext.util.Observable.prototype.addListener;
+
+/**
+ * Starts capture on the specified Observable. All events will be passed
+ * to the supplied function with the event name + standard signature of the event
+ * before the event is fired. If the supplied function returns false,
+ * the event will not fire.
+ * @param {Observable} o The Observable to capture
+ * @param {Function} fn The function to call
+ * @param {Object} scope (optional) The scope (this object) for the fn
+ * @static
+ */
+YAHOO.ext.util.Observable.capture = function(o, fn, scope){
+ o.fireEvent = o.fireEvent.createInterceptor(fn, scope);
+};
+
+/**
+ * Removes all added captures from the Observable.
+ * @param {Observable} o The Observable to release
+ * @static
+ */
+YAHOO.ext.util.Observable.releaseCapture = function(o){
+ o.fireEvent = YAHOO.ext.util.Observable.prototype.fireEvent;
+};
+
+/**
+ * @class YAHOO.ext.util.Config
+ * Class with one useful method
+ * @singleton
+ */
+YAHOO.ext.util.Config = {
+ /**
+ * Copies all the properties of config to obj.
+ * @param {Object} obj The receiver of the properties
+ * @param {Object} config The source of the properties
+ * @param {Object} defaults A different object that will also be applied for default values
+ * @return {Object} returns obj
+ */
+ apply : function(obj, config, defaults){
+ if(defaults){
+ this.apply(obj, defaults);
+ }
+ if(config){
+ for(var prop in config){
+ obj[prop] = config[prop];
+ }
+ }
+ return obj;
+ }
+};
+
+if(!String.escape){
+ String.escape = function(string) {
+ return string.replace(/('|\\)/g, "\\$1");
+ };
+};
+
+String.leftPad = function (val, size, ch) {
+ var result = new String(val);
+ if (ch == null) {
+ ch = " ";
+ }
+ while (result.length < size) {
+ result = ch + result;
+ }
+ return result;
+};
+
+// workaround for Safari anim duration speed problems
+if(YAHOO.util.AnimMgr && YAHOO.ext.util.Browser.isSafari){
+ YAHOO.util.AnimMgr.fps = 500;
+}
+
+// add ability for callbacks instead of events for animations
+if(YAHOO.util.Anim){
+ YAHOO.util.Anim.prototype.animateX = function(callback, scope){
+ var f = function(){
+ this.onComplete.unsubscribe(f);
+ if(typeof callback == 'function'){
+ callback.call(scope || this, this);
+ }
+ };
+ this.onComplete.subscribe(f, this, true);
+ this.animate();
+ };
+}
+
+// workaround for Safari 1.3 not supporting hasOwnProperty
+if(YAHOO.util.Connect && YAHOO.ext.util.Browser.isSafari){
+ YAHOO.util.Connect.setHeader = function(o){
+ for(var prop in this._http_header){
+ // if(this._http_header.hasOwnProperty(prop)){
+ if(typeof this._http_header[prop] != 'function'){
+ o.conn.setRequestHeader(prop, this._http_header[prop]);
+ }
+ }
+ delete this._http_header;
+ this._http_header = {};
+ this._has_http_headers = false;
+ };
+}
+/**
+ * A simple enhancement to drag drop that allows you to constrain the movement of the
+ * DD or DDProxy object to a particular element.
+ var Employee = function(name){
+ this.name = name;
+ this.events = {
+ 'fired' : new YAHOO.util.CustomEvent('fired'),
+ 'quit' : true // lazy initialize the CustomEvent
+ }
+ }
+ YAHOO.extend(Employee, YAHOO.ext.util.Observable);
+
+ *
+ * Usage:
+
+ * Or you can initalize it using the {@link YAHOO.ext.Element} object:
+
+ var dd = new YAHOO.util.DDProxy("dragDiv1", "proxytest",
+ { dragElId: "existingProxyDiv" });
+ dd.startDrag = function(){
+ this.constrainTo('parent-id');
+ };
+
+ */
+if(YAHOO.util.DragDrop){
+ /**
+ * Provides default constraint padding to "constrainTo" elements (defaults to {left: 0, right:0, top:0, bottom:0}).
+ * @type Object
+ */
+ YAHOO.util.DragDrop.prototype.defaultPadding = {left:0, right:0, top:0, bottom:0};
+
+ /**
+ * Initializes the drag drop object's constraints to restrict movement to a certain element.
+ * @param {String/HTMLElement/Element} constrainTo The element to constrain to.
+ * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
+ * and can be either a number for symmetrical padding (4 would be equal to {left:4, right:4, top:4, bottom:4}) or
+ * an object containing the sides to pad. For example: {right:10, bottom:10}
+ * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
+ */
+ YAHOO.util.DragDrop.prototype.constrainTo = function(constrainTo, pad, inContent){
+ if(typeof pad == 'number'){
+ pad = {left: pad, right:pad, top:pad, bottom:pad};
+ }
+ pad = pad || this.defaultPadding;
+ var b = getEl(this.getEl()).getBox();
+ var ce = getEl(constrainTo);
+ var c = ce.dom == document.body ? { x: 0, y: 0,
+ width: YAHOO.util.Dom.getViewportWidth(),
+ height: YAHOO.util.Dom.getViewportHeight()} : ce.getBox(inContent || false);
+ var topSpace = b.y - c.y;
+ var leftSpace = b.x - c.x;
+
+ this.resetConstraints();
+ this.setXConstraint(leftSpace - (pad.left||0), // left
+ c.width - leftSpace - b.width - (pad.right||0) //right
+ );
+ this.setYConstraint(topSpace - (pad.top||0), //top
+ c.height - topSpace - b.height - (pad.bottom||0) //bottom
+ );
+ }
+}
diff --git a/frontend/beta/js/YUI/animation.js b/frontend/beta/js/YUI/animation.js
new file mode 100644
index 0000000..333f946
--- a/dev/null
+++ b/frontend/beta/js/YUI/animation.js
@@ -0,0 +1,1272 @@
+/*
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 0.12.0
+*/
+
+/**
+ * The animation module provides allows effects to be added to HTMLElements.
+ * @module animation
+ */
+
+/**
+ *
+ * Base animation class that provides the interface for building animated effects.
+ *
+ getEl('dragDiv1').initDDProxy('proxytest', {dragElId: "existingProxyDiv"}, {
+ startDrag : function(){
+ this.constrainTo('parent-id');
+ }
+ });
+
var myAnim = new Y.ColorAnim(el, { backgroundColor: { from: '#FF0000', to: '#FFFFFF' } }, 1, Y.Easing.easeOut);
Color values can be specified with either 112233, #112233,
+ * [255,255,255], or rgb(255,255,255)var myAnim = new YAHOO.util.Motion(el, { points: { to: [800, 800] } }, 1, YAHOO.util.Easing.easeOut);
var myAnim = new YAHOO.util.Scroll(el, { scroll: { to: [0, 800] } }, 1, YAHOO.util.Easing.easeOut);
+ *
+ *
+ * @class AutoComplete
+ * @constructor
+ * @param elInput {HTMLElement} DOM element reference of an input field
+ * @param elInput {String} String ID of an input field
+ * @param elContainer {HTMLElement} DOM element reference of an existing DIV
+ * @param elContainer {String} String ID of an existing DIV
+ * @param oDataSource {Object} Instance of YAHOO.widget.DataSource for query/results
+ * @param oConfigs {Object} (optional) Object literal of configuration params
+ */
+YAHOO.widget.AutoComplete = function(elInput,elContainer,oDataSource,oConfigs) {
+ if(elInput && elContainer && oDataSource) {
+ // Validate DataSource
+ if (oDataSource && (oDataSource instanceof YAHOO.widget.DataSource)) {
+ this.dataSource = oDataSource;
+ }
+ else {
+ return;
+ }
+
+ // Validate input element
+ if(YAHOO.util.Dom.inDocument(elInput)) {
+ if(typeof elInput == "string") {
+ this._sName = "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput;
+ this._oTextbox = document.getElementById(elInput);
+ }
+ else {
+ this._sName = (elInput.id) ?
+ "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput.id:
+ "instance" + YAHOO.widget.AutoComplete._nIndex;
+ this._oTextbox = elInput;
+ }
+ }
+ else {
+ return;
+ }
+
+ // Validate container element
+ if(YAHOO.util.Dom.inDocument(elContainer)) {
+ if(typeof elContainer == "string") {
+ this._oContainer = document.getElementById(elContainer);
+ }
+ else {
+ this._oContainer = elContainer;
+ }
+ if(this._oContainer.style.display == "none") {
+ }
+ }
+ else {
+ return;
+ }
+
+ // Set any config params passed in to override defaults
+ if (typeof oConfigs == "object") {
+ for(var sConfig in oConfigs) {
+ if (sConfig) {
+ this[sConfig] = oConfigs[sConfig];
+ }
+ }
+ }
+
+ // Initialization sequence
+ this._initContainer();
+ this._initProps();
+ this._initList();
+ this._initContainerHelpers();
+
+ // Set up events
+ var oSelf = this;
+ var oTextbox = this._oTextbox;
+ // Events are actually for the content module within the container
+ var oContent = this._oContainer._oContent;
+
+ // Dom events
+ YAHOO.util.Event.addListener(oTextbox,"keyup",oSelf._onTextboxKeyUp,oSelf);
+ YAHOO.util.Event.addListener(oTextbox,"keydown",oSelf._onTextboxKeyDown,oSelf);
+ YAHOO.util.Event.addListener(oTextbox,"focus",oSelf._onTextboxFocus,oSelf);
+ YAHOO.util.Event.addListener(oTextbox,"blur",oSelf._onTextboxBlur,oSelf);
+ YAHOO.util.Event.addListener(oContent,"mouseover",oSelf._onContainerMouseover,oSelf);
+ YAHOO.util.Event.addListener(oContent,"mouseout",oSelf._onContainerMouseout,oSelf);
+ YAHOO.util.Event.addListener(oContent,"scroll",oSelf._onContainerScroll,oSelf);
+ YAHOO.util.Event.addListener(oContent,"resize",oSelf._onContainerResize,oSelf);
+ if(oTextbox.form) {
+ YAHOO.util.Event.addListener(oTextbox.form,"submit",oSelf._onFormSubmit,oSelf);
+ }
+ YAHOO.util.Event.addListener(oTextbox,"keypress",oSelf._onTextboxKeyPress,oSelf);
+
+ // Custom events
+ this.textboxFocusEvent = new YAHOO.util.CustomEvent("textboxFocus", this);
+ this.textboxKeyEvent = new YAHOO.util.CustomEvent("textboxKey", this);
+ this.dataRequestEvent = new YAHOO.util.CustomEvent("dataRequest", this);
+ this.dataReturnEvent = new YAHOO.util.CustomEvent("dataReturn", this);
+ this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this);
+ this.containerExpandEvent = new YAHOO.util.CustomEvent("containerExpand", this);
+ this.typeAheadEvent = new YAHOO.util.CustomEvent("typeAhead", this);
+ this.itemMouseOverEvent = new YAHOO.util.CustomEvent("itemMouseOver", this);
+ this.itemMouseOutEvent = new YAHOO.util.CustomEvent("itemMouseOut", this);
+ this.itemArrowToEvent = new YAHOO.util.CustomEvent("itemArrowTo", this);
+ this.itemArrowFromEvent = new YAHOO.util.CustomEvent("itemArrowFrom", this);
+ this.itemSelectEvent = new YAHOO.util.CustomEvent("itemSelect", this);
+ this.unmatchedItemSelectEvent = new YAHOO.util.CustomEvent("unmatchedItemSelect", this);
+ this.selectionEnforceEvent = new YAHOO.util.CustomEvent("selectionEnforce", this);
+ this.containerCollapseEvent = new YAHOO.util.CustomEvent("containerCollapse", this);
+ this.textboxBlurEvent = new YAHOO.util.CustomEvent("textboxBlur", this);
+
+ // Finish up
+ oTextbox.setAttribute("autocomplete","off");
+ YAHOO.widget.AutoComplete._nIndex++;
+ }
+ // Required arguments were not found
+ else {
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The DataSource object that encapsulates the data used for auto completion.
+ * This object should be an inherited object from YAHOO.widget.DataSource.
+ *
+ * @property dataSource
+ * @type Object
+ */
+YAHOO.widget.AutoComplete.prototype.dataSource = null;
+
+/**
+ * Number of characters that must be entered before querying for results. A negative value
+ * effectively turns off the widget. A value of 0 allows queries of null or empty string
+ * values.
+ *
+ * @property minQueryLength
+ * @type Number
+ * @default 1
+ */
+YAHOO.widget.AutoComplete.prototype.minQueryLength = 1;
+
+/**
+ * Maximum number of results to display in results container.
+ *
+ * @property maxResultsDisplayed
+ * @type Number
+ * @default 10
+ */
+YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed = 10;
+
+/**
+ * Number of seconds to delay before submitting a query request. If a query
+ * request is received before a previous one has completed its delay, the
+ * previous request is cancelled and the new request is set to the delay.
+ *
+ * @property queryDelay
+ * @type Number
+ * @default 0.5
+ */
+YAHOO.widget.AutoComplete.prototype.queryDelay = 0.5;
+
+/**
+ * Class name of a highlighted item within results container.
+ *
+ * @property highlighClassName
+ * @type String
+ * @default "yui-ac-highlight"
+ */
+YAHOO.widget.AutoComplete.prototype.highlightClassName = "yui-ac-highlight";
+
+/**
+ * Class name of a pre-highlighted item within results container.
+ *
+ * @property prehighlightClassName
+ * @type String
+ */
+YAHOO.widget.AutoComplete.prototype.prehighlightClassName = null;
+
+/**
+ * Query delimiter. A single character separator for multiple delimited
+ * selections. Multiple delimiter characteres may be defined as an array of
+ * strings. A null value or empty string indicates that query results cannot
+ * be delimited. This feature is not recommended if you need forceSelection to
+ * be true.
+ *
+ * @property delimChar
+ * @type String | String[]
+ */
+YAHOO.widget.AutoComplete.prototype.delimChar = null;
+
+/**
+ * Whether or not the first item in results container should be automatically highlighted
+ * on expand.
+ *
+ * @property autoHighlight
+ * @type Boolean
+ * @default true
+ */
+YAHOO.widget.AutoComplete.prototype.autoHighlight = true;
+
+/**
+ * Whether or not the input field should be automatically updated
+ * with the first query result as the user types, auto-selecting the substring
+ * that the user has not typed.
+ *
+ * @property typeAhead
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.typeAhead = false;
+
+/**
+ * Whether or not to animate the expansion/collapse of the results container in the
+ * horizontal direction.
+ *
+ * @property animHoriz
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.animHoriz = false;
+
+/**
+ * Whether or not to animate the expansion/collapse of the results container in the
+ * vertical direction.
+ *
+ * @property animVert
+ * @type Boolean
+ * @default true
+ */
+YAHOO.widget.AutoComplete.prototype.animVert = true;
+
+/**
+ * Speed of container expand/collapse animation, in seconds..
+ *
+ * @property animSpeed
+ * @type Number
+ * @default 0.3
+ */
+YAHOO.widget.AutoComplete.prototype.animSpeed = 0.3;
+
+/**
+ * Whether or not to force the user's selection to match one of the query
+ * results. Enabling this feature essentially transforms the input field into a
+ * <select> field. This feature is not recommended with delimiter character(s)
+ * defined.
+ *
+ * @property forceSelection
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.forceSelection = false;
+
+/**
+ * Whether or not to allow browsers to cache user-typed input in the input
+ * field. Disabling this feature will prevent the widget from setting the
+ * autocomplete="off" on the input field. When autocomplete="off"
+ * and users click the back button after form submission, user-typed input can
+ * be prefilled by the browser from its cache. This caching of user input may
+ * not be desired for sensitive data, such as credit card numbers, in which
+ * case, implementers should consider setting allowBrowserAutocomplete to false.
+ *
+ * @property allowBrowserAutocomplete
+ * @type Boolean
+ * @default true
+ */
+YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete = true;
+
+/**
+ * Whether or not the results container should always be displayed.
+ * Enabling this feature displays the container when the widget is instantiated
+ * and prevents the toggling of the container to a collapsed state.
+ *
+ * @property alwaysShowContainer
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.alwaysShowContainer = false;
+
+/**
+ * Whether or not to use an iFrame to layer over Windows form elements in
+ * IE. Set to true only when the results container will be on top of a
+ * <select> field in IE and thus exposed to the IE z-index bug (i.e.,
+ * 5.5 < IE < 7).
+ *
+ * @property useIFrame
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.useIFrame = false;
+
+/**
+ * Whether or not the results container should have a shadow.
+ *
+ * @property useShadow
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.useShadow = false;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Public accessor to the unique name of the AutoComplete instance.
+ *
+ * @method toString
+ * @return {String} Unique name of the AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.toString = function() {
+ return "AutoComplete " + this._sName;
+};
+
+ /**
+ * Returns true if container is in an expanded state, false otherwise.
+ *
+ * @method isContainerOpen
+ * @return {Boolean} Returns true if container is in an expanded state, false otherwise.
+ */
+YAHOO.widget.AutoComplete.prototype.isContainerOpen = function() {
+ return this._bContainerOpen;
+};
+
+/**
+ * Public accessor to the internal array of DOM <li> elements that
+ * display query results within the results container.
+ *
+ * @method getListItems
+ * @return {HTMLElement[]} Array of <li> elements within the results container.
+ */
+YAHOO.widget.AutoComplete.prototype.getListItems = function() {
+ return this._aListItems;
+};
+
+/**
+ * Public accessor to the data held in an <li> element of the
+ * results container.
+ *
+ * @method getListItemData
+ * @return {Object | Array} Object or array of result data or null
+ */
+YAHOO.widget.AutoComplete.prototype.getListItemData = function(oListItem) {
+ if(oListItem._oResultData) {
+ return oListItem._oResultData;
+ }
+ else {
+ return false;
+ }
+};
+
+/**
+ * Sets HTML markup for the results container header. This markup will be
+ * inserted within a <div> tag with a class of "ac_hd".
+ *
+ * @method setHeader
+ * @param sHeader {String} HTML markup for results container header.
+ */
+YAHOO.widget.AutoComplete.prototype.setHeader = function(sHeader) {
+ if(sHeader) {
+ if(this._oContainer._oContent._oHeader) {
+ this._oContainer._oContent._oHeader.innerHTML = sHeader;
+ this._oContainer._oContent._oHeader.style.display = "block";
+ }
+ }
+ else {
+ this._oContainer._oContent._oHeader.innerHTML = "";
+ this._oContainer._oContent._oHeader.style.display = "none";
+ }
+};
+
+/**
+ * Sets HTML markup for the results container footer. This markup will be
+ * inserted within a <div> tag with a class of "ac_ft".
+ *
+ * @method setFooter
+ * @param sFooter {String} HTML markup for results container footer.
+ */
+YAHOO.widget.AutoComplete.prototype.setFooter = function(sFooter) {
+ if(sFooter) {
+ if(this._oContainer._oContent._oFooter) {
+ this._oContainer._oContent._oFooter.innerHTML = sFooter;
+ this._oContainer._oContent._oFooter.style.display = "block";
+ }
+ }
+ else {
+ this._oContainer._oContent._oFooter.innerHTML = "";
+ this._oContainer._oContent._oFooter.style.display = "none";
+ }
+};
+
+/**
+ * Sets HTML markup for the results container body. This markup will be
+ * inserted within a <div> tag with a class of "ac_bd".
+ *
+ * @method setBody
+ * @param sHeader {String} HTML markup for results container body.
+ */
+YAHOO.widget.AutoComplete.prototype.setBody = function(sBody) {
+ if(sBody) {
+ if(this._oContainer._oContent._oBody) {
+ this._oContainer._oContent._oBody.innerHTML = sBody;
+ this._oContainer._oContent._oBody.style.display = "block";
+ this._oContainer._oContent.style.display = "block";
+ }
+ }
+ else {
+ this._oContainer._oContent._oBody.innerHTML = "";
+ this._oContainer._oContent.style.display = "none";
+ }
+ this._maxResultsDisplayed = 0;
+};
+
+/**
+ * Overridable method that converts a result item object into HTML markup
+ * for display. Return data values are accessible via the oResultItem object,
+ * and the key return value will always be oResultItem[0]. Markup will be
+ * displayed within <li> element tags in the container.
+ *
+ * @method formatResult
+ * @param oResultItem {Object} Result item representing one query result. Data is held in an array.
+ * @param sQuery {String} The current query string.
+ * @return {String} HTML markup of formatted result data.
+ */
+YAHOO.widget.AutoComplete.prototype.formatResult = function(oResultItem, sQuery) {
+ var sResult = oResultItem[0];
+ if(sResult) {
+ return sResult;
+ }
+ else {
+ return "";
+ }
+};
+
+/**
+ * Overridable method called before container expands allows implementers to access data
+ * and DOM elements.
+ *
+ * @method doBeforeExpandContainer
+ * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
+ */
+YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer = function(oResultItem, sQuery) {
+ return true;
+};
+
+/**
+ * Makes query request to the DataSource.
+ *
+ * @method sendQuery
+ * @param sQuery {String} Query string.
+ */
+YAHOO.widget.AutoComplete.prototype.sendQuery = function(sQuery) {
+ this._sendQuery(sQuery);
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public events
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Fired when the input field receives focus.
+ *
+ * @event textboxFocusEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.textboxFocusEvent = null;
+
+/**
+ * Fired when the input field receives key input.
+ *
+ * @event textboxKeyEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ * @param nKeycode {Number} The keycode number.
+ */
+YAHOO.widget.AutoComplete.prototype.textboxKeyEvent = null;
+
+/**
+ * Fired when the AutoComplete instance makes a query to the DataSource.
+ *
+ * @event dataRequestEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ * @param sQuery {String} The query string.
+ */
+YAHOO.widget.AutoComplete.prototype.dataRequestEvent = null;
+
+/**
+ * Fired when the AutoComplete instance receives query results from the data
+ * source.
+ *
+ * @event dataReturnEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ * @param sQuery {String} The query string.
+ * @param aResults {Array} Results array.
+ */
+YAHOO.widget.AutoComplete.prototype.dataReturnEvent = null;
+
+/**
+ * Fired when the AutoComplete instance does not receive query results from the
+ * DataSource due to an error.
+ *
+ * @event dataErrorEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ * @param sQuery {String} The query string.
+ */
+YAHOO.widget.AutoComplete.prototype.dataErrorEvent = null;
+
+/**
+ * Fired when the results container is expanded.
+ *
+ * @event containerExpandEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.containerExpandEvent = null;
+
+/**
+ * Fired when the input field has been prefilled by the type-ahead
+ * feature.
+ *
+ * @event typeAheadEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ * @param sQuery {String} The query string.
+ * @param sPrefill {String} The prefill string.
+ */
+YAHOO.widget.AutoComplete.prototype.typeAheadEvent = null;
+
+/**
+ * Fired when result item has been moused over.
+ *
+ * @event itemMouseOverEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ * @param elItem {HTMLElement} The <li> element item moused to.
+ */
+YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent = null;
+
+/**
+ * Fired when result item has been moused out.
+ *
+ * @event itemMouseOutEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ * @param elItem {HTMLElement} The <li> element item moused from.
+ */
+YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent = null;
+
+/**
+ * Fired when result item has been arrowed to.
+ *
+ * @event itemArrowToEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ * @param elItem {HTMLElement} The <li> element item arrowed to.
+ */
+YAHOO.widget.AutoComplete.prototype.itemArrowToEvent = null;
+
+/**
+ * Fired when result item has been arrowed away from.
+ *
+ * @event itemArrowFromEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ * @param elItem {HTMLElement} The <li> element item arrowed from.
+ */
+YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent = null;
+
+/**
+ * Fired when an item is selected via mouse click, ENTER key, or TAB key.
+ *
+ * @event itemSelectEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ * @param elItem {HTMLElement} The selected <li> element item.
+ * @param oData {Object} The data returned for the item, either as an object,
+ * or mapped from the schema into an array.
+ */
+YAHOO.widget.AutoComplete.prototype.itemSelectEvent = null;
+
+/**
+ * Fired when a user selection does not match any of the displayed result items.
+ * Note that this event may not behave as expected when delimiter characters
+ * have been defined.
+ *
+ * @event unmatchedItemSelectEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ * @param sQuery {String} The user-typed query string.
+ */
+YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent = null;
+
+/**
+ * Fired if forceSelection is enabled and the user's input has been cleared
+ * because it did not match one of the returned query results.
+ *
+ * @event selectionEnforceEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent = null;
+
+/**
+ * Fired when the results container is collapsed.
+ *
+ * @event containerCollapseEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.containerCollapseEvent = null;
+
+/**
+ * Fired when the input field loses focus.
+ *
+ * @event textboxBlurEvent
+ * @param oSelf {Object} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.textboxBlurEvent = null;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Internal class variable to index multiple AutoComplete instances.
+ *
+ * @property _nIndex
+ * @type Number
+ * @default 0
+ * @private
+ */
+YAHOO.widget.AutoComplete._nIndex = 0;
+
+/**
+ * Name of AutoComplete instance.
+ *
+ * @property _sName
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._sName = null;
+
+/**
+ * Text input field DOM element.
+ *
+ * @property _oTextbox
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._oTextbox = null;
+
+/**
+ * Whether or not the input field is currently in focus. If query results come back
+ * but the user has already moved on, do not proceed with auto complete behavior.
+ *
+ * @property _bFocused
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._bFocused = true;
+
+/**
+ * Animation instance for container expand/collapse.
+ *
+ * @property _oAnim
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._oAnim = null;
+
+/**
+ * Container DOM element.
+ *
+ * @property _oContainer
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._oContainer = null;
+
+/**
+ * Whether or not the results container is currently open.
+ *
+ * @property _bContainerOpen
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._bContainerOpen = false;
+
+/**
+ * Whether or not the mouse is currently over the results
+ * container. This is necessary in order to prevent clicks on container items
+ * from being text input field blur events.
+ *
+ * @property _bOverContainer
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._bOverContainer = false;
+
+/**
+ * Array of <li> elements references that contain query results within the
+ * results container.
+ *
+ * @property _aListItems
+ * @type Array
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._aListItems = null;
+
+/**
+ * Number of <li> elements currently displayed in results container.
+ *
+ * @property _nDisplayedItems
+ * @type Number
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._nDisplayedItems = 0;
+
+/**
+ * Internal count of <li> elements displayed and hidden in results container.
+ *
+ * @property _maxResultsDisplayed
+ * @type Number
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._maxResultsDisplayed = 0;
+
+/**
+ * Current query string
+ *
+ * @property _sCurQuery
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._sCurQuery = null;
+
+/**
+ * Past queries this session (for saving delimited queries).
+ *
+ * @property _sSavedQuery
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._sSavedQuery = null;
+
+/**
+ * Pointer to the currently highlighted <li> element in the container.
+ *
+ * @property _oCurItem
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._oCurItem = null;
+
+/**
+ * Whether or not an item has been selected since the container was populated
+ * with results. Reset to false by _populateList, and set to true when item is
+ * selected.
+ *
+ * @property _bItemSelected
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._bItemSelected = false;
+
+/**
+ * Key code of the last key pressed in textbox.
+ *
+ * @property _nKeyCode
+ * @type Number
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._nKeyCode = null;
+
+/**
+ * Delay timeout ID.
+ *
+ * @property _nDelayID
+ * @type Number
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._nDelayID = -1;
+
+/**
+ * Src to iFrame used when useIFrame = true. Supports implementations over SSL
+ * as well.
+ *
+ * @property _iFrameSrc
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._iFrameSrc = "javascript:false;";
+
+/**
+ * For users typing via certain IMEs, queries must be triggered by intervals,
+ * since key events yet supported across all browsers for all IMEs.
+ *
+ * @property _queryInterval
+ * @type Object
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._queryInterval = null;
+
+/**
+ * Internal tracker to last known textbox value, used to determine whether or not
+ * to trigger a query via interval for certain IME users.
+ *
+ * @event _sLastTextboxValue
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._sLastTextboxValue = null;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Updates and validates latest public config properties.
+ *
+ * @method __initProps
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._initProps = function() {
+ // Correct any invalid values
+ var minQueryLength = this.minQueryLength;
+ if(isNaN(minQueryLength) || (minQueryLength < 1)) {
+ minQueryLength = 1;
+ }
+ var maxResultsDisplayed = this.maxResultsDisplayed;
+ if(isNaN(this.maxResultsDisplayed) || (this.maxResultsDisplayed < 1)) {
+ this.maxResultsDisplayed = 10;
+ }
+ var queryDelay = this.queryDelay;
+ if(isNaN(this.queryDelay) || (this.queryDelay < 0)) {
+ this.queryDelay = 0.5;
+ }
+ var aDelimChar = (this.delimChar) ? this.delimChar : null;
+ if(aDelimChar) {
+ if(typeof aDelimChar == "string") {
+ this.delimChar = [aDelimChar];
+ }
+ else if(aDelimChar.constructor != Array) {
+ this.delimChar = null;
+ }
+ }
+ var animSpeed = this.animSpeed;
+ if((this.animHoriz || this.animVert) && YAHOO.util.Anim) {
+ if(isNaN(animSpeed) || (animSpeed < 0)) {
+ animSpeed = 0.3;
+ }
+ if(!this._oAnim ) {
+ oAnim = new YAHOO.util.Anim(this._oContainer._oContent, {}, this.animSpeed);
+ this._oAnim = oAnim;
+ }
+ else {
+ this._oAnim.duration = animSpeed;
+ }
+ }
+ if(this.forceSelection && this.delimChar) {
+ }
+};
+
+/**
+ * Initializes the results container helpers if they are enabled and do
+ * not exist
+ *
+ * @method _initContainerHelpers
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._initContainerHelpers = function() {
+ if(this.useShadow && !this._oContainer._oShadow) {
+ var oShadow = document.createElement("div");
+ oShadow.className = "yui-ac-shadow";
+ this._oContainer._oShadow = this._oContainer.appendChild(oShadow);
+ }
+ if(this.useIFrame && !this._oContainer._oIFrame) {
+ var oIFrame = document.createElement("iframe");
+ oIFrame.src = this._iFrameSrc;
+ oIFrame.frameBorder = 0;
+ oIFrame.scrolling = "no";
+ oIFrame.style.position = "absolute";
+ oIFrame.style.width = "100%";
+ oIFrame.style.height = "100%";
+ oIFrame.tabIndex = -1;
+ this._oContainer._oIFrame = this._oContainer.appendChild(oIFrame);
+ }
+};
+
+/**
+ * Initializes the results container once at object creation
+ *
+ * @method _initContainer
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._initContainer = function() {
+ if(!this._oContainer._oContent) {
+ // The oContent div helps size the iframe and shadow properly
+ var oContent = document.createElement("div");
+ oContent.className = "yui-ac-content";
+ oContent.style.display = "none";
+ this._oContainer._oContent = this._oContainer.appendChild(oContent);
+
+ var oHeader = document.createElement("div");
+ oHeader.className = "yui-ac-hd";
+ oHeader.style.display = "none";
+ this._oContainer._oContent._oHeader = this._oContainer._oContent.appendChild(oHeader);
+
+ var oBody = document.createElement("div");
+ oBody.className = "yui-ac-bd";
+ this._oContainer._oContent._oBody = this._oContainer._oContent.appendChild(oBody);
+
+ var oFooter = document.createElement("div");
+ oFooter.className = "yui-ac-ft";
+ oFooter.style.display = "none";
+ this._oContainer._oContent._oFooter = this._oContainer._oContent.appendChild(oFooter);
+ }
+ else {
+ }
+};
+
+/**
+ * Clears out contents of container body and creates up to
+ * YAHOO.widget.AutoComplete#maxResultsDisplayed <li> elements in an
+ * <ul> element.
+ *
+ * @method _initList
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._initList = function() {
+ this._aListItems = [];
+ while(this._oContainer._oContent._oBody.hasChildNodes()) {
+ var oldListItems = this.getListItems();
+ if(oldListItems) {
+ for(var oldi = oldListItems.length-1; oldi >= 0; i--) {
+ oldListItems[oldi] = null;
+ }
+ }
+ this._oContainer._oContent._oBody.innerHTML = "";
+ }
+
+ var oList = document.createElement("ul");
+ oList = this._oContainer._oContent._oBody.appendChild(oList);
+ for(var i=0; i
+ * dd = new YAHOO.util.DragDrop("div1", "group1"); + *+ * Since none of the event handlers have been implemented, nothing would + * actually happen if you were to run the code above. Normally you would + * override this class or one of the default implementations, but you can + * also override the methods you want on an instance of the class... + *
+ * dd.onDragDrop = function(e, id) { + * alert("dd was dropped on " + id); + * } + *+ * @namespace YAHOO.util + * @class DragDrop + * @constructor + * @param {String} id of the element that is linked to this instance + * @param {String} sGroup the group of related DragDrop objects + * @param {object} config an object containing configurable attributes + * Valid properties for DragDrop: + * padding, isTarget, maintainOffset, primaryButtonOnly + */ +YAHOO.util.DragDrop = function(id, sGroup, config) { + if (id) { + this.init(id, sGroup, config); + } +}; + +YAHOO.util.DragDrop.prototype = { + + /** + * The id of the element associated with this object. This is what we + * refer to as the "linked element" because the size and position of + * this element is used to determine when the drag and drop objects have + * interacted. + * @property id + * @type String + */ + id: null, + + /** + * Configuration attributes passed into the constructor + * @property config + * @type object + */ + config: null, + + /** + * The id of the element that will be dragged. By default this is same + * as the linked element , but could be changed to another element. Ex: + * YAHOO.util.DDProxy + * @property dragElId + * @type String + * @private + */ + dragElId: null, + + /** + * the id of the element that initiates the drag operation. By default + * this is the linked element, but could be changed to be a child of this + * element. This lets us do things like only starting the drag when the + * header element within the linked html element is clicked. + * @property handleElId + * @type String + * @private + */ + handleElId: null, + + /** + * An associative array of HTML tags that will be ignored if clicked. + * @property invalidHandleTypes + * @type {string: string} + */ + invalidHandleTypes: null, + + /** + * An associative array of ids for elements that will be ignored if clicked + * @property invalidHandleIds + * @type {string: string} + */ + invalidHandleIds: null, + + /** + * An indexted array of css class names for elements that will be ignored + * if clicked. + * @property invalidHandleClasses + * @type string[] + */ + invalidHandleClasses: null, + + /** + * The linked element's absolute X position at the time the drag was + * started + * @property startPageX + * @type int + * @private + */ + startPageX: 0, + + /** + * The linked element's absolute X position at the time the drag was + * started + * @property startPageY + * @type int + * @private + */ + startPageY: 0, + + /** + * The group defines a logical collection of DragDrop objects that are + * related. Instances only get events when interacting with other + * DragDrop object in the same group. This lets us define multiple + * groups using a single DragDrop subclass if we want. + * @property groups + * @type {string: string} + */ + groups: null, + + /** + * Individual drag/drop instances can be locked. This will prevent + * onmousedown start drag. + * @property locked + * @type boolean + * @private + */ + locked: false, + + /** + * Lock this instance + * @method lock + */ + lock: function() { this.locked = true; }, + + /** + * Unlock this instace + * @method unlock + */ + unlock: function() { this.locked = false; }, + + /** + * By default, all insances can be a drop target. This can be disabled by + * setting isTarget to false. + * @method isTarget + * @type boolean + */ + isTarget: true, + + /** + * The padding configured for this drag and drop object for calculating + * the drop zone intersection with this object. + * @method padding + * @type int[] + */ + padding: null, + + /** + * Cached reference to the linked element + * @property _domRef + * @private + */ + _domRef: null, + + /** + * Internal typeof flag + * @property __ygDragDrop + * @private + */ + __ygDragDrop: true, + + /** + * Set to true when horizontal contraints are applied + * @property constrainX + * @type boolean + * @private + */ + constrainX: false, + + /** + * Set to true when vertical contraints are applied + * @property constrainY + * @type boolean + * @private + */ + constrainY: false, + + /** + * The left constraint + * @property minX + * @type int + * @private + */ + minX: 0, + + /** + * The right constraint + * @property maxX + * @type int + * @private + */ + maxX: 0, + + /** + * The up constraint + * @property minY + * @type int + * @type int + * @private + */ + minY: 0, + + /** + * The down constraint + * @property maxY + * @type int + * @private + */ + maxY: 0, + + /** + * Maintain offsets when we resetconstraints. Set to true when you want + * the position of the element relative to its parent to stay the same + * when the page changes + * + * @property maintainOffset + * @type boolean + */ + maintainOffset: false, + + /** + * Array of pixel locations the element will snap to if we specified a + * horizontal graduation/interval. This array is generated automatically + * when you define a tick interval. + * @property xTicks + * @type int[] + */ + xTicks: null, + + /** + * Array of pixel locations the element will snap to if we specified a + * vertical graduation/interval. This array is generated automatically + * when you define a tick interval. + * @property yTicks + * @type int[] + */ + yTicks: null, + + /** + * By default the drag and drop instance will only respond to the primary + * button click (left button for a right-handed mouse). Set to true to + * allow drag and drop to start with any mouse click that is propogated + * by the browser + * @property primaryButtonOnly + * @type boolean + */ + primaryButtonOnly: true, + + /** + * The availabe property is false until the linked dom element is accessible. + * @property available + * @type boolean + */ + available: false, + + /** + * By default, drags can only be initiated if the mousedown occurs in the + * region the linked element is. This is done in part to work around a + * bug in some browsers that mis-report the mousedown if the previous + * mouseup happened outside of the window. This property is set to true + * if outer handles are defined. + * + * @property hasOuterHandles + * @type boolean + * @default false + */ + hasOuterHandles: false, + + /** + * Code that executes immediately before the startDrag event + * @method b4StartDrag + * @private + */ + b4StartDrag: function(x, y) { }, + + /** + * Abstract method called after a drag/drop object is clicked + * and the drag or mousedown time thresholds have beeen met. + * @method startDrag + * @param {int} X click location + * @param {int} Y click location + */ + startDrag: function(x, y) { /* override this */ }, + + /** + * Code that executes immediately before the onDrag event + * @method b4Drag + * @private + */ + b4Drag: function(e) { }, + + /** + * Abstract method called during the onMouseMove event while dragging an + * object. + * @method onDrag + * @param {Event} e the mousemove event + */ + onDrag: function(e) { /* override this */ }, + + /** + * Abstract method called when this element fist begins hovering over + * another DragDrop obj + * @method onDragEnter + * @param {Event} e the mousemove event + * @param {String|DragDrop[]} id In POINT mode, the element + * id this is hovering over. In INTERSECT mode, an array of one or more + * dragdrop items being hovered over. + */ + onDragEnter: function(e, id) { /* override this */ }, + + /** + * Code that executes immediately before the onDragOver event + * @method b4DragOver + * @private + */ + b4DragOver: function(e) { }, + + /** + * Abstract method called when this element is hovering over another + * DragDrop obj + * @method onDragOver + * @param {Event} e the mousemove event + * @param {String|DragDrop[]} id In POINT mode, the element + * id this is hovering over. In INTERSECT mode, an array of dd items + * being hovered over. + */ + onDragOver: function(e, id) { /* override this */ }, + + /** + * Code that executes immediately before the onDragOut event + * @method b4DragOut + * @private + */ + b4DragOut: function(e) { }, + + /** + * Abstract method called when we are no longer hovering over an element + * @method onDragOut + * @param {Event} e the mousemove event + * @param {String|DragDrop[]} id In POINT mode, the element + * id this was hovering over. In INTERSECT mode, an array of dd items + * that the mouse is no longer over. + */ + onDragOut: function(e, id) { /* override this */ }, + + /** + * Code that executes immediately before the onDragDrop event + * @method b4DragDrop + * @private + */ + b4DragDrop: function(e) { }, + + /** + * Abstract method called when this item is dropped on another DragDrop + * obj + * @method onDragDrop + * @param {Event} e the mouseup event + * @param {String|DragDrop[]} id In POINT mode, the element + * id this was dropped on. In INTERSECT mode, an array of dd items this + * was dropped on. + */ + onDragDrop: function(e, id) { /* override this */ }, + + /** + * Abstract method called when this item is dropped on an area with no + * drop target + * @method onInvalidDrop + * @param {Event} e the mouseup event + */ + onInvalidDrop: function(e) { /* override this */ }, + + /** + * Code that executes immediately before the endDrag event + * @method b4EndDrag + * @private + */ + b4EndDrag: function(e) { }, + + /** + * Fired when we are done dragging the object + * @method endDrag + * @param {Event} e the mouseup event + */ + endDrag: function(e) { /* override this */ }, + + /** + * Code executed immediately before the onMouseDown event + * @method b4MouseDown + * @param {Event} e the mousedown event + * @private + */ + b4MouseDown: function(e) { }, + + /** + * Event handler that fires when a drag/drop obj gets a mousedown + * @method onMouseDown + * @param {Event} e the mousedown event + */ + onMouseDown: function(e) { /* override this */ }, + + /** + * Event handler that fires when a drag/drop obj gets a mouseup + * @method onMouseUp + * @param {Event} e the mouseup event + */ + onMouseUp: function(e) { /* override this */ }, + + /** + * Override the onAvailable method to do what is needed after the initial + * position was determined. + * @method onAvailable + */ + onAvailable: function () { + }, + + /** + * Returns a reference to the linked element + * @method getEl + * @return {HTMLElement} the html element + */ + getEl: function() { + if (!this._domRef) { + this._domRef = Dom.get(this.id); + } + + return this._domRef; + }, + + /** + * Returns a reference to the actual element to drag. By default this is + * the same as the html element, but it can be assigned to another + * element. An example of this can be found in YAHOO.util.DDProxy + * @method getDragEl + * @return {HTMLElement} the html element + */ + getDragEl: function() { + return Dom.get(this.dragElId); + }, + + /** + * Sets up the DragDrop object. Must be called in the constructor of any + * YAHOO.util.DragDrop subclass + * @method init + * @param id the id of the linked element + * @param {String} sGroup the group of related items + * @param {object} config configuration attributes + */ + init: function(id, sGroup, config) { + this.initTarget(id, sGroup, config); + Event.on(this.id, "mousedown", this.handleMouseDown, this, true); + // Event.on(this.id, "selectstart", Event.preventDefault); + }, + + /** + * Initializes Targeting functionality only... the object does not + * get a mousedown handler. + * @method initTarget + * @param id the id of the linked element + * @param {String} sGroup the group of related items + * @param {object} config configuration attributes + */ + initTarget: function(id, sGroup, config) { + + // configuration attributes + this.config = config || {}; + + // create a local reference to the drag and drop manager + this.DDM = YAHOO.util.DDM; + // initialize the groups array + this.groups = {}; + + // assume that we have an element reference instead of an id if the + // parameter is not a string + if (typeof id !== "string") { + YAHOO.log("id is not a string, assuming it is an HTMLElement"); + id = Dom.generateId(id); + } + + // set the id + this.id = id; + + // add to an interaction group + this.addToGroup((sGroup) ? sGroup : "default"); + + // We don't want to register this as the handle with the manager + // so we just set the id rather than calling the setter. + this.handleElId = id; + + Event.onAvailable(id, this.handleOnAvailable, this, true); + + + // the linked element is the element that gets dragged by default + this.setDragElId(id); + + // by default, clicked anchors will not start drag operations. + // @TODO what else should be here? Probably form fields. + this.invalidHandleTypes = { A: "A" }; + this.invalidHandleIds = {}; + this.invalidHandleClasses = []; + + this.applyConfig(); + }, + + /** + * Applies the configuration parameters that were passed into the constructor. + * This is supposed to happen at each level through the inheritance chain. So + * a DDProxy implentation will execute apply config on DDProxy, DD, and + * DragDrop in order to get all of the parameters that are available in + * each object. + * @method applyConfig + */ + applyConfig: function() { + + // configurable properties: + // padding, isTarget, maintainOffset, primaryButtonOnly + this.padding = this.config.padding || [0, 0, 0, 0]; + this.isTarget = (this.config.isTarget !== false); + this.maintainOffset = (this.config.maintainOffset); + this.primaryButtonOnly = (this.config.primaryButtonOnly !== false); + + }, + + /** + * Executed when the linked element is available + * @method handleOnAvailable + * @private + */ + handleOnAvailable: function() { + this.available = true; + this.resetConstraints(); + this.onAvailable(); + }, + + /** + * Configures the padding for the target zone in px. Effectively expands + * (or reduces) the virtual object size for targeting calculations. + * Supports css-style shorthand; if only one parameter is passed, all sides + * will have that padding, and if only two are passed, the top and bottom + * will have the first param, the left and right the second. + * @method setPadding + * @param {int} iTop Top pad + * @param {int} iRight Right pad + * @param {int} iBot Bot pad + * @param {int} iLeft Left pad + */ + setPadding: function(iTop, iRight, iBot, iLeft) { + // this.padding = [iLeft, iRight, iTop, iBot]; + if (!iRight && 0 !== iRight) { + this.padding = [iTop, iTop, iTop, iTop]; + } else if (!iBot && 0 !== iBot) { + this.padding = [iTop, iRight, iTop, iRight]; + } else { + this.padding = [iTop, iRight, iBot, iLeft]; + } + }, + + /** + * Stores the initial placement of the linked element. + * @method setInitialPosition + * @param {int} diffX the X offset, default 0 + * @param {int} diffY the Y offset, default 0 + */ + setInitPosition: function(diffX, diffY) { + var el = this.getEl(); + + if (!this.DDM.verifyEl(el)) { + return; + } + + var dx = diffX || 0; + var dy = diffY || 0; + + var p = Dom.getXY( el ); + + this.initPageX = p[0] - dx; + this.initPageY = p[1] - dy; + + this.lastPageX = p[0]; + this.lastPageY = p[1]; + + + this.setStartPosition(p); + }, + + /** + * Sets the start position of the element. This is set when the obj + * is initialized, the reset when a drag is started. + * @method setStartPosition + * @param pos current position (from previous lookup) + * @private + */ + setStartPosition: function(pos) { + var p = pos || Dom.getXY( this.getEl() ); + this.deltaSetXY = null; + + this.startPageX = p[0]; + this.startPageY = p[1]; + }, + + /** + * Add this instance to a group of related drag/drop objects. All + * instances belong to at least one group, and can belong to as many + * groups as needed. + * @method addToGroup + * @param sGroup {string} the name of the group + */ + addToGroup: function(sGroup) { + this.groups[sGroup] = true; + this.DDM.regDragDrop(this, sGroup); + }, + + /** + * Remove's this instance from the supplied interaction group + * @method removeFromGroup + * @param {string} sGroup The group to drop + */ + removeFromGroup: function(sGroup) { + if (this.groups[sGroup]) { + delete this.groups[sGroup]; + } + + this.DDM.removeDDFromGroup(this, sGroup); + }, + + /** + * Allows you to specify that an element other than the linked element + * will be moved with the cursor during a drag + * @method setDragElId + * @param id {string} the id of the element that will be used to initiate the drag + */ + setDragElId: function(id) { + this.dragElId = id; + }, + + /** + * Allows you to specify a child of the linked element that should be + * used to initiate the drag operation. An example of this would be if + * you have a content div with text and links. Clicking anywhere in the + * content area would normally start the drag operation. Use this method + * to specify that an element inside of the content div is the element + * that starts the drag operation. + * @method setHandleElId + * @param id {string} the id of the element that will be used to + * initiate the drag. + */ + setHandleElId: function(id) { + if (typeof id !== "string") { + YAHOO.log("id is not a string, assuming it is an HTMLElement"); + id = Dom.generateId(id); + } + this.handleElId = id; + this.DDM.regHandle(this.id, id); + }, + + /** + * Allows you to set an element outside of the linked element as a drag + * handle + * @method setOuterHandleElId + * @param id the id of the element that will be used to initiate the drag + */ + setOuterHandleElId: function(id) { + if (typeof id !== "string") { + YAHOO.log("id is not a string, assuming it is an HTMLElement"); + id = Dom.generateId(id); + } + Event.on(id, "mousedown", + this.handleMouseDown, this, true); + this.setHandleElId(id); + + this.hasOuterHandles = true; + }, + + /** + * Remove all drag and drop hooks for this element + * @method unreg + */ + unreg: function() { + Event.removeListener(this.id, "mousedown", + this.handleMouseDown); + this._domRef = null; + this.DDM._remove(this); + }, + + /** + * Returns true if this instance is locked, or the drag drop mgr is locked + * (meaning that all drag/drop is disabled on the page.) + * @method isLocked + * @return {boolean} true if this obj or all drag/drop is locked, else + * false + */ + isLocked: function() { + return (this.DDM.isLocked() || this.locked); + }, + + /** + * Fired when this object is clicked + * @method handleMouseDown + * @param {Event} e + * @param {YAHOO.util.DragDrop} oDD the clicked dd object (this dd obj) + * @private + */ + handleMouseDown: function(e, oDD) { + + var button = e.which || e.button; + + if (this.primaryButtonOnly && button > 1) { + return; + } + + if (this.isLocked()) { + return; + } + + this.DDM.refreshCache(this.groups); + // var self = this; + // setTimeout( function() { self.DDM.refreshCache(self.groups); }, 0); + + // Only process the event if we really clicked within the linked + // element. The reason we make this check is that in the case that + // another element was moved between the clicked element and the + // cursor in the time between the mousedown and mouseup events. When + // this happens, the element gets the next mousedown event + // regardless of where on the screen it happened. + var pt = new YAHOO.util.Point(Event.getPageX(e), Event.getPageY(e)); + if (!this.hasOuterHandles && !this.DDM.isOverTarget(pt, this) ) { + } else { + if (this.clickValidator(e)) { + + + // set the initial element position + this.setStartPosition(); + + + this.b4MouseDown(e); + this.onMouseDown(e); + this.DDM.handleMouseDown(e, this); + + this.DDM.stopEvent(e); + } else { + + + } + } + }, + + clickValidator: function(e) { + var target = Event.getTarget(e); + return ( this.isValidHandleChild(target) && + (this.id == this.handleElId || + this.DDM.handleWasClicked(target, this.id)) ); + }, + + /** + * Allows you to specify a tag name that should not start a drag operation + * when clicked. This is designed to facilitate embedding links within a + * drag handle that do something other than start the drag. + * @method addInvalidHandleType + * @param {string} tagName the type of element to exclude + */ + addInvalidHandleType: function(tagName) { + var type = tagName.toUpperCase(); + this.invalidHandleTypes[type] = type; + }, + + /** + * Lets you to specify an element id for a child of a drag handle + * that should not initiate a drag + * @method addInvalidHandleId + * @param {string} id the element id of the element you wish to ignore + */ + addInvalidHandleId: function(id) { + if (typeof id !== "string") { + YAHOO.log("id is not a string, assuming it is an HTMLElement"); + id = Dom.generateId(id); + } + this.invalidHandleIds[id] = id; + }, + + /** + * Lets you specify a css class of elements that will not initiate a drag + * @method addInvalidHandleClass + * @param {string} cssClass the class of the elements you wish to ignore + */ + addInvalidHandleClass: function(cssClass) { + this.invalidHandleClasses.push(cssClass); + }, + + /** + * Unsets an excluded tag name set by addInvalidHandleType + * @method removeInvalidHandleType + * @param {string} tagName the type of element to unexclude + */ + removeInvalidHandleType: function(tagName) { + var type = tagName.toUpperCase(); + // this.invalidHandleTypes[type] = null; + delete this.invalidHandleTypes[type]; + }, + + /** + * Unsets an invalid handle id + * @method removeInvalidHandleId + * @param {string} id the id of the element to re-enable + */ + removeInvalidHandleId: function(id) { + if (typeof id !== "string") { + YAHOO.log("id is not a string, assuming it is an HTMLElement"); + id = Dom.generateId(id); + } + delete this.invalidHandleIds[id]; + }, + + /** + * Unsets an invalid css class + * @method removeInvalidHandleClass + * @param {string} cssClass the class of the element(s) you wish to + * re-enable + */ + removeInvalidHandleClass: function(cssClass) { + for (var i=0, len=this.invalidHandleClasses.length; i
+ * YAHOO.util.DragDropMgr.refreshCache({group1:true, group2:true});
+ *
+ * @TODO this really should be an indexed array. Alternatively this
+ * method could accept both.
+ * @method refreshCache
+ * @param {Object} groups an associative array of groups to refresh
+ * @static
+ */
+ refreshCache: function(groups) {
+ for (var sGroup in groups) {
+ if ("string" != typeof sGroup) {
+ continue;
+ }
+ for (var i in this.ids[sGroup]) {
+ var oDD = this.ids[sGroup][i];
+
+ if (this.isTypeOfDD(oDD)) {
+ // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
+ var loc = this.getLocation(oDD);
+ if (loc) {
+ this.locationCache[oDD.id] = loc;
+ } else {
+ delete this.locationCache[oDD.id];
+ // this will unregister the drag and drop object if
+ // the element is not in a usable state
+ // oDD.unreg();
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * This checks to make sure an element exists and is in the DOM. The
+ * main purpose is to handle cases where innerHTML is used to remove
+ * drag and drop objects from the DOM. IE provides an 'unspecified
+ * error' when trying to access the offsetParent of such an element
+ * @method verifyEl
+ * @param {HTMLElement} el the element to check
+ * @return {boolean} true if the element looks usable
+ * @static
+ */
+ verifyEl: function(el) {
+ try {
+ if (el) {
+ var parent = el.offsetParent;
+ if (parent) {
+ return true;
+ }
+ }
+ } catch(e) {
+ }
+
+ return false;
+ },
+
+ /**
+ * Returns a Region object containing the drag and drop element's position
+ * and size, including the padding configured for it
+ * @method getLocation
+ * @param {DragDrop} oDD the drag and drop object to get the
+ * location for
+ * @return {YAHOO.util.Region} a Region object representing the total area
+ * the element occupies, including any padding
+ * the instance is configured for.
+ * @static
+ */
+ getLocation: function(oDD) {
+ if (! this.isTypeOfDD(oDD)) {
+ return null;
+ }
+
+ var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
+
+ try {
+ pos= YAHOO.util.Dom.getXY(el);
+ } catch (e) { }
+
+ if (!pos) {
+ return null;
+ }
+
+ x1 = pos[0];
+ x2 = x1 + el.offsetWidth;
+ y1 = pos[1];
+ y2 = y1 + el.offsetHeight;
+
+ t = y1 - oDD.padding[0];
+ r = x2 + oDD.padding[1];
+ b = y2 + oDD.padding[2];
+ l = x1 - oDD.padding[3];
+
+ return new YAHOO.util.Region( t, r, b, l );
+ },
+
+ /**
+ * Checks the cursor location to see if it over the target
+ * @method isOverTarget
+ * @param {YAHOO.util.Point} pt The point to evaluate
+ * @param {DragDrop} oTarget the DragDrop object we are inspecting
+ * @return {boolean} true if the mouse is over the target
+ * @private
+ * @static
+ */
+ isOverTarget: function(pt, oTarget, intersect) {
+ // use cache if available
+ var loc = this.locationCache[oTarget.id];
+ if (!loc || !this.useCache) {
+ loc = this.getLocation(oTarget);
+ this.locationCache[oTarget.id] = loc;
+
+ }
+
+ if (!loc) {
+ return false;
+ }
+
+ oTarget.cursorIsOver = loc.contains( pt );
+
+ // DragDrop is using this as a sanity check for the initial mousedown
+ // in this case we are done. In POINT mode, if the drag obj has no
+ // contraints, we are also done. Otherwise we need to evaluate the
+ // location of the target as related to the actual location of the
+ // dragged element.
+ var dc = this.dragCurrent;
+ if (!dc || !dc.getTargetCoord ||
+ (!intersect && !dc.constrainX && !dc.constrainY)) {
+ return oTarget.cursorIsOver;
+ }
+
+ oTarget.overlap = null;
+
+ // Get the current location of the drag element, this is the
+ // location of the mouse event less the delta that represents
+ // where the original mousedown happened on the element. We
+ // need to consider constraints and ticks as well.
+ var pos = dc.getTargetCoord(pt.x, pt.y);
+
+ var el = dc.getDragEl();
+ var curRegion = new YAHOO.util.Region( pos.y,
+ pos.x + el.offsetWidth,
+ pos.y + el.offsetHeight,
+ pos.x );
+
+ var overlap = curRegion.intersect(loc);
+
+ if (overlap) {
+ oTarget.overlap = overlap;
+ return (intersect) ? true : oTarget.cursorIsOver;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * unload event handler
+ * @method _onUnload
+ * @private
+ * @static
+ */
+ _onUnload: function(e, me) {
+ this.unregAll();
+ },
+
+ /**
+ * Cleans up the drag and drop events and objects.
+ * @method unregAll
+ * @private
+ * @static
+ */
+ unregAll: function() {
+
+ if (this.dragCurrent) {
+ this.stopDrag();
+ this.dragCurrent = null;
+ }
+
+ this._execOnAll("unreg", []);
+
+ for (i in this.elementCache) {
+ delete this.elementCache[i];
+ }
+
+ this.elementCache = {};
+ this.ids = {};
+ },
+
+ /**
+ * A cache of DOM elements
+ * @property elementCache
+ * @private
+ * @static
+ */
+ elementCache: {},
+
+ /**
+ * Get the wrapper for the DOM element specified
+ * @method getElWrapper
+ * @param {String} id the id of the element to get
+ * @return {YAHOO.util.DDM.ElementWrapper} the wrapped element
+ * @private
+ * @deprecated This wrapper isn't that useful
+ * @static
+ */
+ getElWrapper: function(id) {
+ var oWrapper = this.elementCache[id];
+ if (!oWrapper || !oWrapper.el) {
+ oWrapper = this.elementCache[id] =
+ new this.ElementWrapper(YAHOO.util.Dom.get(id));
+ }
+ return oWrapper;
+ },
+
+ /**
+ * Returns the actual DOM element
+ * @method getElement
+ * @param {String} id the id of the elment to get
+ * @return {Object} The element
+ * @deprecated use YAHOO.util.Dom.get instead
+ * @static
+ */
+ getElement: function(id) {
+ return YAHOO.util.Dom.get(id);
+ },
+
+ /**
+ * Returns the style property for the DOM element (i.e.,
+ * document.getElById(id).style)
+ * @method getCss
+ * @param {String} id the id of the elment to get
+ * @return {Object} The style property of the element
+ * @deprecated use YAHOO.util.Dom instead
+ * @static
+ */
+ getCss: function(id) {
+ var el = YAHOO.util.Dom.get(id);
+ return (el) ? el.style : null;
+ },
+
+ /**
+ * Inner class for cached elements
+ * @class DragDropMgr.ElementWrapper
+ * @for DragDropMgr
+ * @private
+ * @deprecated
+ */
+ ElementWrapper: function(el) {
+ /**
+ * The element
+ * @property el
+ */
+ this.el = el || null;
+ /**
+ * The element id
+ * @property id
+ */
+ this.id = this.el && el.id;
+ /**
+ * A reference to the style property
+ * @property css
+ */
+ this.css = this.el && el.style;
+ },
+
+ /**
+ * Returns the X position of an html element
+ * @method getPosX
+ * @param el the element for which to get the position
+ * @return {int} the X coordinate
+ * @for DragDropMgr
+ * @deprecated use YAHOO.util.Dom.getX instead
+ * @static
+ */
+ getPosX: function(el) {
+ return YAHOO.util.Dom.getX(el);
+ },
+
+ /**
+ * Returns the Y position of an html element
+ * @method getPosY
+ * @param el the element for which to get the position
+ * @return {int} the Y coordinate
+ * @deprecated use YAHOO.util.Dom.getY instead
+ * @static
+ */
+ getPosY: function(el) {
+ return YAHOO.util.Dom.getY(el);
+ },
+
+ /**
+ * Swap two nodes. In IE, we use the native method, for others we
+ * emulate the IE behavior
+ * @method swapNode
+ * @param n1 the first node to swap
+ * @param n2 the other node to swap
+ * @static
+ */
+ swapNode: function(n1, n2) {
+ if (n1.swapNode) {
+ n1.swapNode(n2);
+ } else {
+ var p = n2.parentNode;
+ var s = n2.nextSibling;
+
+ if (s == n1) {
+ p.insertBefore(n1, n2);
+ } else if (n2 == n1.nextSibling) {
+ p.insertBefore(n2, n1);
+ } else {
+ n1.parentNode.replaceChild(n2, n1);
+ p.insertBefore(n1, s);
+ }
+ }
+ },
+
+ /**
+ * Returns the current scroll position
+ * @method getScroll
+ * @private
+ * @static
+ */
+ getScroll: function () {
+ var t, l, dde=document.documentElement, db=document.body;
+ if (dde && (dde.scrollTop || dde.scrollLeft)) {
+ t = dde.scrollTop;
+ l = dde.scrollLeft;
+ } else if (db) {
+ t = db.scrollTop;
+ l = db.scrollLeft;
+ } else {
+ YAHOO.log("could not get scroll property");
+ }
+ return { top: t, left: l };
+ },
+
+ /**
+ * Returns the specified element style property
+ * @method getStyle
+ * @param {HTMLElement} el the element
+ * @param {string} styleProp the style property
+ * @return {string} The value of the style property
+ * @deprecated use YAHOO.util.Dom.getStyle
+ * @static
+ */
+ getStyle: function(el, styleProp) {
+ return YAHOO.util.Dom.getStyle(el, styleProp);
+ },
+
+ /**
+ * Gets the scrollTop
+ * @method getScrollTop
+ * @return {int} the document's scrollTop
+ * @static
+ */
+ getScrollTop: function () { return this.getScroll().top; },
+
+ /**
+ * Gets the scrollLeft
+ * @method getScrollLeft
+ * @return {int} the document's scrollTop
+ * @static
+ */
+ getScrollLeft: function () { return this.getScroll().left; },
+
+ /**
+ * Sets the x/y position of an element to the location of the
+ * target element.
+ * @method moveToEl
+ * @param {HTMLElement} moveEl The element to move
+ * @param {HTMLElement} targetEl The position reference element
+ * @static
+ */
+ moveToEl: function (moveEl, targetEl) {
+ var aCoord = YAHOO.util.Dom.getXY(targetEl);
+ YAHOO.util.Dom.setXY(moveEl, aCoord);
+ },
+
+ /**
+ * Gets the client height
+ * @method getClientHeight
+ * @return {int} client height in px
+ * @deprecated use YAHOO.util.Dom.getViewportHeight instead
+ * @static
+ */
+ getClientHeight: function() {
+ return YAHOO.util.Dom.getViewportHeight();
+ },
+
+ /**
+ * Gets the client width
+ * @method getClientWidth
+ * @return {int} client width in px
+ * @deprecated use YAHOO.util.Dom.getViewportWidth instead
+ * @static
+ */
+ getClientWidth: function() {
+ return YAHOO.util.Dom.getViewportWidth();
+ },
+
+ /**
+ * Numeric array sort function
+ * @method numericSort
+ * @static
+ */
+ numericSort: function(a, b) { return (a - b); },
+
+ /**
+ * Internal counter
+ * @property _timeoutCount
+ * @private
+ * @static
+ */
+ _timeoutCount: 0,
+
+ /**
+ * Trying to make the load order less important. Without this we get
+ * an error if this file is loaded before the Event Utility.
+ * @method _addListeners
+ * @private
+ * @static
+ */
+ _addListeners: function() {
+ var DDM = YAHOO.util.DDM;
+ if ( YAHOO.util.Event && document ) {
+ DDM._onLoad();
+ } else {
+ if (DDM._timeoutCount > 2000) {
+ } else {
+ setTimeout(DDM._addListeners, 10);
+ if (document && document.body) {
+ DDM._timeoutCount += 1;
+ }
+ }
+ }
+ },
+
+ /**
+ * Recursively searches the immediate parent and all child nodes for
+ * the handle element in order to determine wheter or not it was
+ * clicked.
+ * @method handleWasClicked
+ * @param node the html element to inspect
+ * @static
+ */
+ handleWasClicked: function(node, id) {
+ if (this.isHandle(id, node.id)) {
+ return true;
+ } else {
+ // check to see if this is a text node child of the one we want
+ var p = node.parentNode;
+
+ while (p) {
+ if (this.isHandle(id, p.id)) {
+ return true;
+ } else {
+ p = p.parentNode;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ };
+
+}();
+
+// shorter alias, save a few bytes
+YAHOO.util.DDM = YAHOO.util.DragDropMgr;
+YAHOO.util.DDM._addListeners();
+
+}
+
+/**
+ * A DragDrop implementation where the linked element follows the
+ * mouse cursor during a drag.
+ * @class DD
+ * @extends YAHOO.util.DragDrop
+ * @constructor
+ * @param {String} id the id of the linked element
+ * @param {String} sGroup the group of related DragDrop items
+ * @param {object} config an object containing configurable attributes
+ * Valid properties for DD:
+ * scroll
+ */
+YAHOO.util.DD = function(id, sGroup, config) {
+ if (id) {
+ this.init(id, sGroup, config);
+ }
+};
+
+YAHOO.extend(YAHOO.util.DD, YAHOO.util.DragDrop, {
+
+ /**
+ * When set to true, the utility automatically tries to scroll the browser
+ * window wehn a drag and drop element is dragged near the viewport boundary.
+ * Defaults to true.
+ * @property scroll
+ * @type boolean
+ */
+ scroll: true,
+
+ /**
+ * Sets the pointer offset to the distance between the linked element's top
+ * left corner and the location the element was clicked
+ * @method autoOffset
+ * @param {int} iPageX the X coordinate of the click
+ * @param {int} iPageY the Y coordinate of the click
+ */
+ autoOffset: function(iPageX, iPageY) {
+ var x = iPageX - this.startPageX;
+ var y = iPageY - this.startPageY;
+ this.setDelta(x, y);
+ },
+
+ /**
+ * Sets the pointer offset. You can call this directly to force the
+ * offset to be in a particular location (e.g., pass in 0,0 to set it
+ * to the center of the object, as done in YAHOO.widget.Slider)
+ * @method setDelta
+ * @param {int} iDeltaX the distance from the left
+ * @param {int} iDeltaY the distance from the top
+ */
+ setDelta: function(iDeltaX, iDeltaY) {
+ this.deltaX = iDeltaX;
+ this.deltaY = iDeltaY;
+ },
+
+ /**
+ * Sets the drag element to the location of the mousedown or click event,
+ * maintaining the cursor location relative to the location on the element
+ * that was clicked. Override this if you want to place the element in a
+ * location other than where the cursor is.
+ * @method setDragElPos
+ * @param {int} iPageX the X coordinate of the mousedown or drag event
+ * @param {int} iPageY the Y coordinate of the mousedown or drag event
+ */
+ setDragElPos: function(iPageX, iPageY) {
+ // the first time we do this, we are going to check to make sure
+ // the element has css positioning
+
+ var el = this.getDragEl();
+ this.alignElWithMouse(el, iPageX, iPageY);
+ },
+
+ /**
+ * Sets the element to the location of the mousedown or click event,
+ * maintaining the cursor location relative to the location on the element
+ * that was clicked. Override this if you want to place the element in a
+ * location other than where the cursor is.
+ * @method alignElWithMouse
+ * @param {HTMLElement} el the element to move
+ * @param {int} iPageX the X coordinate of the mousedown or drag event
+ * @param {int} iPageY the Y coordinate of the mousedown or drag event
+ */
+ alignElWithMouse: function(el, iPageX, iPageY) {
+ var oCoord = this.getTargetCoord(iPageX, iPageY);
+
+ if (!this.deltaSetXY) {
+ var aCoord = [oCoord.x, oCoord.y];
+ YAHOO.util.Dom.setXY(el, aCoord);
+ var newLeft = parseInt( YAHOO.util.Dom.getStyle(el, "left"), 10 );
+ var newTop = parseInt( YAHOO.util.Dom.getStyle(el, "top" ), 10 );
+
+ this.deltaSetXY = [ newLeft - oCoord.x, newTop - oCoord.y ];
+ } else {
+ YAHOO.util.Dom.setStyle(el, "left", (oCoord.x + this.deltaSetXY[0]) + "px");
+ YAHOO.util.Dom.setStyle(el, "top", (oCoord.y + this.deltaSetXY[1]) + "px");
+ }
+
+ this.cachePosition(oCoord.x, oCoord.y);
+ this.autoScroll(oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
+ },
+
+ /**
+ * Saves the most recent position so that we can reset the constraints and
+ * tick marks on-demand. We need to know this so that we can calculate the
+ * number of pixels the element is offset from its original position.
+ * @method cachePosition
+ * @param iPageX the current x position (optional, this just makes it so we
+ * don't have to look it up again)
+ * @param iPageY the current y position (optional, this just makes it so we
+ * don't have to look it up again)
+ */
+ cachePosition: function(iPageX, iPageY) {
+ if (iPageX) {
+ this.lastPageX = iPageX;
+ this.lastPageY = iPageY;
+ } else {
+ var aCoord = YAHOO.util.Dom.getXY(this.getEl());
+ this.lastPageX = aCoord[0];
+ this.lastPageY = aCoord[1];
+ }
+ },
+
+ /**
+ * Auto-scroll the window if the dragged object has been moved beyond the
+ * visible window boundary.
+ * @method autoScroll
+ * @param {int} x the drag element's x position
+ * @param {int} y the drag element's y position
+ * @param {int} h the height of the drag element
+ * @param {int} w the width of the drag element
+ * @private
+ */
+ autoScroll: function(x, y, h, w) {
+
+ if (this.scroll) {
+ // The client height
+ var clientH = this.DDM.getClientHeight();
+
+ // The client width
+ var clientW = this.DDM.getClientWidth();
+
+ // The amt scrolled down
+ var st = this.DDM.getScrollTop();
+
+ // The amt scrolled right
+ var sl = this.DDM.getScrollLeft();
+
+ // Location of the bottom of the element
+ var bot = h + y;
+
+ // Location of the right of the element
+ var right = w + x;
+
+ // The distance from the cursor to the bottom of the visible area,
+ // adjusted so that we don't scroll if the cursor is beyond the
+ // element drag constraints
+ var toBot = (clientH + st - y - this.deltaY);
+
+ // The distance from the cursor to the right of the visible area
+ var toRight = (clientW + sl - x - this.deltaX);
+
+
+ // How close to the edge the cursor must be before we scroll
+ // var thresh = (document.all) ? 100 : 40;
+ var thresh = 40;
+
+ // How many pixels to scroll per autoscroll op. This helps to reduce
+ // clunky scrolling. IE is more sensitive about this ... it needs this
+ // value to be higher.
+ var scrAmt = (document.all) ? 80 : 30;
+
+ // Scroll down if we are near the bottom of the visible page and the
+ // obj extends below the crease
+ if ( bot > clientH && toBot < thresh ) {
+ window.scrollTo(sl, st + scrAmt);
+ }
+
+ // Scroll up if the window is scrolled down and the top of the object
+ // goes above the top border
+ if ( y < st && st > 0 && y - st < thresh ) {
+ window.scrollTo(sl, st - scrAmt);
+ }
+
+ // Scroll right if the obj is beyond the right border and the cursor is
+ // near the border.
+ if ( right > clientW && toRight < thresh ) {
+ window.scrollTo(sl + scrAmt, st);
+ }
+
+ // Scroll left if the window has been scrolled to the right and the obj
+ // extends past the left border
+ if ( x < sl && sl > 0 && x - sl < thresh ) {
+ window.scrollTo(sl - scrAmt, st);
+ }
+ }
+ },
+
+ /**
+ * Finds the location the element should be placed if we want to move
+ * it to where the mouse location less the click offset would place us.
+ * @method getTargetCoord
+ * @param {int} iPageX the X coordinate of the click
+ * @param {int} iPageY the Y coordinate of the click
+ * @return an object that contains the coordinates (Object.x and Object.y)
+ * @private
+ */
+ getTargetCoord: function(iPageX, iPageY) {
+
+
+ var x = iPageX - this.deltaX;
+ var y = iPageY - this.deltaY;
+
+ if (this.constrainX) {
+ if (x < this.minX) { x = this.minX; }
+ if (x > this.maxX) { x = this.maxX; }
+ }
+
+ if (this.constrainY) {
+ if (y < this.minY) { y = this.minY; }
+ if (y > this.maxY) { y = this.maxY; }
+ }
+
+ x = this.getTick(x, this.xTicks);
+ y = this.getTick(y, this.yTicks);
+
+
+ return {x:x, y:y};
+ },
+
+ /*
+ * Sets up config options specific to this class. Overrides
+ * YAHOO.util.DragDrop, but all versions of this method through the
+ * inheritance chain are called
+ */
+ applyConfig: function() {
+ YAHOO.util.DD.superclass.applyConfig.call(this);
+ this.scroll = (this.config.scroll !== false);
+ },
+
+ /*
+ * Event that fires prior to the onMouseDown event. Overrides
+ * YAHOO.util.DragDrop.
+ */
+ b4MouseDown: function(e) {
+ // this.resetConstraints();
+ this.autoOffset(YAHOO.util.Event.getPageX(e),
+ YAHOO.util.Event.getPageY(e));
+ },
+
+ /*
+ * Event that fires prior to the onDrag event. Overrides
+ * YAHOO.util.DragDrop.
+ */
+ b4Drag: function(e) {
+ this.setDragElPos(YAHOO.util.Event.getPageX(e),
+ YAHOO.util.Event.getPageY(e));
+ },
+
+ toString: function() {
+ return ("DD " + this.id);
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Debugging ygDragDrop events that can be overridden
+ //////////////////////////////////////////////////////////////////////////
+ /*
+ startDrag: function(x, y) {
+ },
+
+ onDrag: function(e) {
+ },
+
+ onDragEnter: function(e, id) {
+ },
+
+ onDragOver: function(e, id) {
+ },
+
+ onDragOut: function(e, id) {
+ },
+
+ onDragDrop: function(e, id) {
+ },
+
+ endDrag: function(e) {
+ }
+
+ */
+
+});
+/**
+ * A DragDrop implementation that inserts an empty, bordered div into
+ * the document that follows the cursor during drag operations. At the time of
+ * the click, the frame div is resized to the dimensions of the linked html
+ * element, and moved to the exact location of the linked element.
+ *
+ * References to the "frame" element refer to the single proxy element that
+ * was created to be dragged in place of all DDProxy elements on the
+ * page.
+ *
+ * @class DDProxy
+ * @extends YAHOO.util.DD
+ * @constructor
+ * @param {String} id the id of the linked html element
+ * @param {String} sGroup the group of related DragDrop objects
+ * @param {object} config an object containing configurable attributes
+ * Valid properties for DDProxy in addition to those in DragDrop:
+ * resizeFrame, centerFrame, dragElId
+ */
+YAHOO.util.DDProxy = function(id, sGroup, config) {
+ if (id) {
+ this.init(id, sGroup, config);
+ this.initFrame();
+ }
+};
+
+/**
+ * The default drag frame div id
+ * @property YAHOO.util.DDProxy.dragElId
+ * @type String
+ * @static
+ */
+YAHOO.util.DDProxy.dragElId = "ygddfdiv";
+
+YAHOO.extend(YAHOO.util.DDProxy, YAHOO.util.DD, {
+
+ /**
+ * By default we resize the drag frame to be the same size as the element
+ * we want to drag (this is to get the frame effect). We can turn it off
+ * if we want a different behavior.
+ * @property resizeFrame
+ * @type boolean
+ */
+ resizeFrame: true,
+
+ /**
+ * By default the frame is positioned exactly where the drag element is, so
+ * we use the cursor offset provided by YAHOO.util.DD. Another option that works only if
+ * you do not have constraints on the obj is to have the drag frame centered
+ * around the cursor. Set centerFrame to true for this effect.
+ * @property centerFrame
+ * @type boolean
+ */
+ centerFrame: false,
+
+ /**
+ * Creates the proxy element if it does not yet exist
+ * @method createFrame
+ */
+ createFrame: function() {
+ var self = this;
+ var body = document.body;
+
+ if (!body || !body.firstChild) {
+ setTimeout( function() { self.createFrame(); }, 50 );
+ return;
+ }
+
+ var div = this.getDragEl();
+
+ if (!div) {
+ div = document.createElement("div");
+ div.id = this.dragElId;
+ var s = div.style;
+
+ s.position = "absolute";
+ s.visibility = "hidden";
+ s.cursor = "move";
+ s.border = "2px solid #aaa";
+ s.zIndex = 999;
+
+ // appendChild can blow up IE if invoked prior to the window load event
+ // while rendering a table. It is possible there are other scenarios
+ // that would cause this to happen as well.
+ body.insertBefore(div, body.firstChild);
+ }
+ },
+
+ /**
+ * Initialization for the drag frame element. Must be called in the
+ * constructor of all subclasses
+ * @method initFrame
+ */
+ initFrame: function() {
+ this.createFrame();
+ },
+
+ applyConfig: function() {
+ YAHOO.util.DDProxy.superclass.applyConfig.call(this);
+
+ this.resizeFrame = (this.config.resizeFrame !== false);
+ this.centerFrame = (this.config.centerFrame);
+ this.setDragElId(this.config.dragElId || YAHOO.util.DDProxy.dragElId);
+ },
+
+ /**
+ * Resizes the drag frame to the dimensions of the clicked object, positions
+ * it over the object, and finally displays it
+ * @method showFrame
+ * @param {int} iPageX X click position
+ * @param {int} iPageY Y click position
+ * @private
+ */
+ showFrame: function(iPageX, iPageY) {
+ var el = this.getEl();
+ var dragEl = this.getDragEl();
+ var s = dragEl.style;
+
+ this._resizeProxy();
+
+ if (this.centerFrame) {
+ this.setDelta( Math.round(parseInt(s.width, 10)/2),
+ Math.round(parseInt(s.height, 10)/2) );
+ }
+
+ this.setDragElPos(iPageX, iPageY);
+
+ YAHOO.util.Dom.setStyle(dragEl, "visibility", "visible");
+ },
+
+ /**
+ * The proxy is automatically resized to the dimensions of the linked
+ * element when a drag is initiated, unless resizeFrame is set to false
+ * @method _resizeProxy
+ * @private
+ */
+ _resizeProxy: function() {
+ if (this.resizeFrame) {
+ var DOM = YAHOO.util.Dom;
+ var el = this.getEl();
+ var dragEl = this.getDragEl();
+
+ var bt = parseInt( DOM.getStyle(dragEl, "borderTopWidth" ), 10);
+ var br = parseInt( DOM.getStyle(dragEl, "borderRightWidth" ), 10);
+ var bb = parseInt( DOM.getStyle(dragEl, "borderBottomWidth" ), 10);
+ var bl = parseInt( DOM.getStyle(dragEl, "borderLeftWidth" ), 10);
+
+ if (isNaN(bt)) { bt = 0; }
+ if (isNaN(br)) { br = 0; }
+ if (isNaN(bb)) { bb = 0; }
+ if (isNaN(bl)) { bl = 0; }
+
+
+ var newWidth = Math.max(0, el.offsetWidth - br - bl);
+ var newHeight = Math.max(0, el.offsetHeight - bt - bb);
+
+
+ DOM.setStyle( dragEl, "width", newWidth + "px" );
+ DOM.setStyle( dragEl, "height", newHeight + "px" );
+ }
+ },
+
+ // overrides YAHOO.util.DragDrop
+ b4MouseDown: function(e) {
+ var x = YAHOO.util.Event.getPageX(e);
+ var y = YAHOO.util.Event.getPageY(e);
+ this.autoOffset(x, y);
+ this.setDragElPos(x, y);
+ },
+
+ // overrides YAHOO.util.DragDrop
+ b4StartDrag: function(x, y) {
+ // show the drag frame
+ this.showFrame(x, y);
+ },
+
+ // overrides YAHOO.util.DragDrop
+ b4EndDrag: function(e) {
+ YAHOO.util.Dom.setStyle(this.getDragEl(), "visibility", "hidden");
+ },
+
+ // overrides YAHOO.util.DragDrop
+ // By default we try to move the element to the last location of the frame.
+ // This is so that the default behavior mirrors that of YAHOO.util.DD.
+ endDrag: function(e) {
+ var DOM = YAHOO.util.Dom;
+ var lel = this.getEl();
+ var del = this.getDragEl();
+
+ // Show the drag frame briefly so we can get its position
+ // del.style.visibility = "";
+ DOM.setStyle(del, "visibility", "");
+
+ // Hide the linked element before the move to get around a Safari
+ // rendering bug.
+ //lel.style.visibility = "hidden";
+ DOM.setStyle(lel, "visibility", "hidden");
+ YAHOO.util.DDM.moveToEl(lel, del);
+ //del.style.visibility = "hidden";
+ DOM.setStyle(del, "visibility", "hidden");
+ //lel.style.visibility = "";
+ DOM.setStyle(lel, "visibility", "");
+ },
+
+ toString: function() {
+ return ("DDProxy " + this.id);
+ }
+
+});
+/**
+ * A DragDrop implementation that does not move, but can be a drop
+ * target. You would get the same result by simply omitting implementation
+ * for the event callbacks, but this way we reduce the processing cost of the
+ * event listener and the callbacks.
+ * @class DDTarget
+ * @extends YAHOO.util.DragDrop
+ * @constructor
+ * @param {String} id the id of the element that is a drop target
+ * @param {String} sGroup the group of related DragDrop objects
+ * @param {object} config an object containing configurable attributes
+ * Valid properties for DDTarget in addition to those in
+ * DragDrop:
+ * none
+ */
+YAHOO.util.DDTarget = function(id, sGroup, config) {
+ if (id) {
+ this.initTarget(id, sGroup, config);
+ }
+};
+
+// YAHOO.util.DDTarget.prototype = new YAHOO.util.DragDrop();
+YAHOO.extend(YAHOO.util.DDTarget, YAHOO.util.DragDrop, {
+ toString: function() {
+ return ("DDTarget " + this.id);
+ }
+});
diff --git a/frontend/beta/js/YUI/event.js b/frontend/beta/js/YUI/event.js
new file mode 100644
index 0000000..7bfac3b
--- a/dev/null
+++ b/frontend/beta/js/YUI/event.js
@@ -0,0 +1,1738 @@
+/*
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 0.12.0
+*/
+
+/**
+ * The CustomEvent class lets you define events for your application
+ * that can be subscribed to by one or more independent component.
+ *
+ * @param {String} type The type of event, which is passed to the callback
+ * when the event fires
+ * @param {Object} oScope The context the event will fire from. "this" will
+ * refer to this object in the callback. Default value:
+ * the window object. The listener can override this.
+ * @param {boolean} silent pass true to prevent the event from writing to
+ * the log system
+ * @namespace YAHOO.util
+ * @class CustomEvent
+ * @constructor
+ */
+YAHOO.util.CustomEvent = function(type, oScope, silent, signature) {
+
+ /**
+ * The type of event, returned to subscribers when the event fires
+ * @property type
+ * @type string
+ */
+ this.type = type;
+
+ /**
+ * The scope the the event will fire from by default. Defaults to the window
+ * obj
+ * @property scope
+ * @type object
+ */
+ this.scope = oScope || window;
+
+ /**
+ * By default all custom events are logged in the debug build, set silent
+ * to true to disable logging for this event.
+ * @property silent
+ * @type boolean
+ */
+ this.silent = silent;
+
+ /**
+ * Custom events support two styles of arguments provided to the event
+ * subscribers.
+ * ", + sourceAndDetail, + ":
", + msg, + "
"] : + + ["", label, " ", + totalTime, "ms (+", elapsedTime, ") ", + localTime, ": ", + sourceAndDetail, ": ", + msg,"
"]; + + return output.join(""); +}; + +/** + * Converts input chars "<", ">", and "&" to HTML entities. + * + * @method html2Text + * @param sHtml {String} String to convert. + * @private + */ +YAHOO.widget.LogReader.prototype.html2Text = function(sHtml) { + if(sHtml) { + sHtml += ""; + return sHtml.replace(/&/g, "&").replace(//g, ">"); + } + return ""; +}; + +///////////////////////////////////////////////////////////////////////////// +// +// Private member variables +// +///////////////////////////////////////////////////////////////////////////// + +/** + * Internal class member to index multiple log reader instances. + * + * @property _memberName + * @static + * @type Number + * @default 0 + * @private + */ +YAHOO.widget.LogReader._index = 0; + +/** + * Name of LogReader instance. + * + * @property _sName + * @type String + * @private + */ +YAHOO.widget.LogReader.prototype._sName = null; + +/** + * A class member shared by all log readers if a container needs to be + * created during instantiation. Will be null if a container element never needs to + * be created on the fly, such as when the implementer passes in their own element. + * + * @property _elDefaultContainer + * @type HTMLElement + * @private + */ +YAHOO.widget.LogReader._elDefaultContainer = null; + +/** + * Buffer of log message objects for batch output. + * + * @property _buffer + * @type Object[] + * @private + */ +YAHOO.widget.LogReader.prototype._buffer = null; + +/** + * Number of log messages output to console. + * + * @property _consoleMsgCount + * @type Number + * @default 0 + * @private + */ +YAHOO.widget.LogReader.prototype._consoleMsgCount = 0; + +/** + * Date of last output log message. + * + * @property _lastTime + * @type Date + * @private + */ +YAHOO.widget.LogReader.prototype._lastTime = null; + +/** + * Batched output timeout ID. + * + * @property _timeout + * @type Number + * @private + */ +YAHOO.widget.LogReader.prototype._timeout = null; + +/** + * Array of filters for log message categories. + * + * @property _categoryFilters + * @type String[] + * @private + */ +YAHOO.widget.LogReader.prototype._categoryFilters = null; + +/** + * Array of filters for log message sources. + * + * @property _sourceFilters + * @type String[] + * @private + */ +YAHOO.widget.LogReader.prototype._sourceFilters = null; + +/** + * Log reader container element. + * + * @property _elContainer + * @type HTMLElement + * @private + */ +YAHOO.widget.LogReader.prototype._elContainer = null; + +/** + * Log reader header element. + * + * @property _elHd + * @type HTMLElement + * @private + */ +YAHOO.widget.LogReader.prototype._elHd = null; + +/** + * Log reader collapse element. + * + * @property _elCollapse + * @type HTMLElement + * @private + */ +YAHOO.widget.LogReader.prototype._elCollapse = null; + +/** + * Log reader collapse button element. + * + * @property _btnCollapse + * @type HTMLElement + * @private + */ +YAHOO.widget.LogReader.prototype._btnCollapse = null; + +/** + * Log reader title header element. + * + * @property _title + * @type HTMLElement + * @private + */ +YAHOO.widget.LogReader.prototype._title = null; + +/** + * Log reader console element. + * + * @property _elConsole + * @type HTMLElement + * @private + */ +YAHOO.widget.LogReader.prototype._elConsole = null; + +/** + * Log reader footer element. + * + * @property _elFt + * @type HTMLElement + * @private + */ +YAHOO.widget.LogReader.prototype._elFt = null; + +/** + * Log reader buttons container element. + * + * @property _elBtns + * @type HTMLElement + * @private + */ +YAHOO.widget.LogReader.prototype._elBtns = null; + +/** + * Container element for log reader category filter checkboxes. + * + * @property _elCategoryFilters + * @type HTMLElement + * @private + */ +YAHOO.widget.LogReader.prototype._elCategoryFilters = null; + +/** + * Container element for log reader source filter checkboxes. + * + * @property _elSourceFilters + * @type HTMLElement + * @private + */ +YAHOO.widget.LogReader.prototype._elSourceFilters = null; + +/** + * Log reader pause button element. + * + * @property _btnPause + * @type HTMLElement + * @private + */ +YAHOO.widget.LogReader.prototype._btnPause = null; + +/** + * Clear button element. + * + * @property _btnClear + * @type HTMLElement + * @private + */ +YAHOO.widget.LogReader.prototype._btnClear = null; + +///////////////////////////////////////////////////////////////////////////// +// +// Private methods +// +///////////////////////////////////////////////////////////////////////////// + +/** + * Creates the UI for a category filter in the log reader footer element. + * + * @method _createCategoryCheckbox + * @param sCategory {String} Category name. + * @private + */ +YAHOO.widget.LogReader.prototype._createCategoryCheckbox = function(sCategory) { + var oSelf = this; + + if(this._elFt) { + var elParent = this._elCategoryFilters; + var filters = this._categoryFilters; + + var elFilter = elParent.appendChild(document.createElement("span")); + elFilter.className = "yui-log-filtergrp"; + // Append el at the end so IE 5.5 can set "type" attribute + // and THEN set checked property + var chkCategory = document.createElement("input"); + chkCategory.id = "yui-log-filter-" + sCategory + this._sName; + chkCategory.className = "yui-log-filter-" + sCategory; + chkCategory.type = "checkbox"; + chkCategory.category = sCategory; + chkCategory = elFilter.appendChild(chkCategory); + chkCategory.checked = true; + + // Add this checked filter to the internal array of filters + filters.push(sCategory); + // Subscribe to the click event + YAHOO.util.Event.addListener(chkCategory,'click',oSelf._onCheckCategory,oSelf); + + // Create and class the text label + var lblCategory = elFilter.appendChild(document.createElement("label")); + lblCategory.htmlFor = chkCategory.id; + lblCategory.className = sCategory; + lblCategory.innerHTML = sCategory; + } +}; + +/** + * Creates a checkbox in the log reader footer element to filter by source. + * + * @method _createSourceCheckbox + * @param sSource {String} Source name. + * @private + */ +YAHOO.widget.LogReader.prototype._createSourceCheckbox = function(sSource) { + var oSelf = this; + + if(this._elFt) { + var elParent = this._elSourceFilters; + var filters = this._sourceFilters; + + var elFilter = elParent.appendChild(document.createElement("span")); + elFilter.className = "yui-log-filtergrp"; + + // Append el at the end so IE 5.5 can set "type" attribute + // and THEN set checked property + var chkSource = document.createElement("input"); + chkSource.id = "yui-log-filter" + sSource + this._sName; + chkSource.className = "yui-log-filter" + sSource; + chkSource.type = "checkbox"; + chkSource.source = sSource; + chkSource = elFilter.appendChild(chkSource); + chkSource.checked = true; + + // Add this checked filter to the internal array of filters + filters.push(sSource); + // Subscribe to the click event + YAHOO.util.Event.addListener(chkSource,'click',oSelf._onCheckSource,oSelf); + + // Create and class the text label + var lblSource = elFilter.appendChild(document.createElement("label")); + lblSource.htmlFor = chkSource.id; + lblSource.className = sSource; + lblSource.innerHTML = sSource; + } +}; + +/** + * Reprints all log messages in the stack through filters. + * + * @method _filterLogs + * @private + */ +YAHOO.widget.LogReader.prototype._filterLogs = function() { + // Reprint stack with new filters + if (this._elConsole !== null) { + this._clearConsole(); + this._printToConsole(YAHOO.widget.Logger.getStack()); + } +}; + +/** + * Clears all outputted log messages from the console and resets the time of the + * last output log message. + * + * @method _clearConsole + * @private + */ +YAHOO.widget.LogReader.prototype._clearConsole = function() { + // Clear the buffer of any pending messages + this._timeout = null; + this._buffer = []; + this._consoleMsgCount = 0; + + // Reset the rolling timer + this._lastTime = YAHOO.widget.Logger.getStartTime(); + + var elConsole = this._elConsole; + while(elConsole.hasChildNodes()) { + elConsole.removeChild(elConsole.firstChild); + } +}; + +/** + * Sends buffer of log messages to output and clears buffer. + * + * @method _printBuffer + * @private + */ +YAHOO.widget.LogReader.prototype._printBuffer = function() { + this._timeout = null; + + if(this._elConsole !== null) { + var thresholdMax = this.thresholdMax; + thresholdMax = (thresholdMax && !isNaN(thresholdMax)) ? thresholdMax : 500; + if(this._consoleMsgCount < thresholdMax) { + var entries = []; + for (var i=0; itag (for wrapping) + var container = (this.verboseOutput) ? "CODE" : "PRE"; + var oNewElement = (this.newestOnTop) ? + this._elConsole.insertBefore( + document.createElement(container),this._elConsole.firstChild): + this._elConsole.appendChild(document.createElement(container)); + + oNewElement.innerHTML = output; + this._consoleMsgCount++; + this._lastTime = entry.time.getTime(); + } + } +}; + +///////////////////////////////////////////////////////////////////////////// +// +// Private event handlers +// +///////////////////////////////////////////////////////////////////////////// + +/** + * Handles Logger's categoryCreateEvent. + * + * @method _onCategoryCreate + * @param sType {String} The event. + * @param aArgs {Object[]} Data passed from event firer. + * @param oSelf {Object} The LogReader instance. + * @private + */ +YAHOO.widget.LogReader.prototype._onCategoryCreate = function(sType, aArgs, oSelf) { + var category = aArgs[0]; + if(oSelf._elFt) { + oSelf._createCategoryCheckbox(category); + } +}; + +/** + * Handles Logger's sourceCreateEvent. + * + * @method _onSourceCreate + * @param sType {String} The event. + * @param aArgs {Object[]} Data passed from event firer. + * @param oSelf {Object} The LogReader instance. + * @private + */ +YAHOO.widget.LogReader.prototype._onSourceCreate = function(sType, aArgs, oSelf) { + var source = aArgs[0]; + if(oSelf._elFt) { + oSelf._createSourceCheckbox(source); + } +}; + +/** + * Handles check events on the category filter checkboxes. + * + * @method _onCheckCategory + * @param v {HTMLEvent} The click event. + * @param oSelf {Object} The LogReader instance. + * @private + */ +YAHOO.widget.LogReader.prototype._onCheckCategory = function(v, oSelf) { + var newFilter = this.category; + var filtersArray = oSelf._categoryFilters; + + if(!this.checked) { // Remove category from filters + for(var i=0; i0) { + // Substring until first space + sClass = sSource.substring(0,spaceIndex); + // The rest of the source + sDetail = sSource.substring(spaceIndex,sSource.length); + } + else { + sClass = sSource; + } + if(this._isNewSource(sClass)) { + this._createNewSource(sClass); + } + } + + var timestamp = new Date(); + var logEntry = new YAHOO.widget.LogMsg({ + msg: sMsg, + time: timestamp, + category: sCategory, + source: sClass, + sourceDetail: sDetail + }); + + var stack = this._stack; + var maxStackEntries = this.maxStackEntries; + if(maxStackEntries && !isNaN(maxStackEntries) && + (stack.length >= maxStackEntries)) { + stack.shift(); + } + stack.push(logEntry); + this.newLogEvent.fire(logEntry); + + if(this._browserConsoleEnabled) { + this._printToBrowserConsole(logEntry); + } + return true; + } + else { + return false; + } +}; + +/** + * Resets internal stack and startTime, enables Logger, and fires logResetEvent. + * + * @method reset + */ +YAHOO.widget.Logger.reset = function() { + this._stack = []; + this._startTime = new Date().getTime(); + this.loggerEnabled = true; + this.log("Logger reset"); + this.logResetEvent.fire(); +}; + +/** + * Public accessor to internal stack of log message objects. + * + * @method getStack + * @return {Object[]} Array of log message objects. + */ +YAHOO.widget.Logger.getStack = function() { + return this._stack; +}; + +/** + * Public accessor to internal start time. + * + * @method getStartTime + * @return {Date} Internal date of when Logger singleton was initialized. + */ +YAHOO.widget.Logger.getStartTime = function() { + return this._startTime; +}; + +/** + * Disables output to the browser's global console.log() function, which is used + * by the Firebug extension to Firefox as well as Safari. + * + * @method disableBrowserConsole + */ +YAHOO.widget.Logger.disableBrowserConsole = function() { + YAHOO.log("Logger output to the function console.log() has been disabled."); + this._browserConsoleEnabled = false; +}; + +/** + * Enables output to the browser's global console.log() function, which is used + * by the Firebug extension to Firefox as well as Safari. + * + * @method enableBrowserConsole + */ +YAHOO.widget.Logger.enableBrowserConsole = function() { + this._browserConsoleEnabled = true; + YAHOO.log("Logger output to the function console.log() has been enabled."); +}; + +///////////////////////////////////////////////////////////////////////////// +// +// Public events +// +///////////////////////////////////////////////////////////////////////////// + + /** + * Fired when a new category has been created. + * + * @event categoryCreateEvent + * @param sCategory {String} Category name. + */ +YAHOO.widget.Logger.categoryCreateEvent = + new YAHOO.util.CustomEvent("categoryCreate", this, true); + + /** + * Fired when a new source has been named. + * + * @event sourceCreateEvent + * @param sSource {String} Source name. + */ +YAHOO.widget.Logger.sourceCreateEvent = + new YAHOO.util.CustomEvent("sourceCreate", this, true); + + /** + * Fired when a new log message has been created. + * + * @event newLogEvent + * @param sMsg {String} Log message. + */ +YAHOO.widget.Logger.newLogEvent = new YAHOO.util.CustomEvent("newLog", this, true); + +/** + * Fired when the Logger has been reset has been created. + * + * @event logResetEvent + */ +YAHOO.widget.Logger.logResetEvent = new YAHOO.util.CustomEvent("logReset", this, true); + +///////////////////////////////////////////////////////////////////////////// +// +// Private methods +// +///////////////////////////////////////////////////////////////////////////// + +/** + * Creates a new category of log messages and fires categoryCreateEvent. + * + * @method _createNewCategory + * @param sCategory {String} Category name. + * @private + */ +YAHOO.widget.Logger._createNewCategory = function(sCategory) { + this.categories.push(sCategory); + this.categoryCreateEvent.fire(sCategory); +}; + +/** + * Checks to see if a category has already been created. + * + * @method _isNewCategory + * @param sCategory {String} Category name. + * @return {Boolean} Returns true if category is unknown, else returns false. + * @private + */ +YAHOO.widget.Logger._isNewCategory = function(sCategory) { + for(var i=0; i < this.categories.length; i++) { + if(sCategory == this.categories[i]) { + return false; + } + } + return true; +}; + +/** + * Creates a new source of log messages and fires sourceCreateEvent. + * + * @method _createNewSource + * @param sSource {String} Source name. + * @private + */ +YAHOO.widget.Logger._createNewSource = function(sSource) { + this.sources.push(sSource); + this.sourceCreateEvent.fire(sSource); +}; + +/** + * Checks to see if a source already exists. + * + * @method _isNewSource + * @param sSource {String} Source name. + * @return {Boolean} Returns true if source is unknown, else returns false. + * @private + */ +YAHOO.widget.Logger._isNewSource = function(sSource) { + if(sSource) { + for(var i=0; i < this.sources.length; i++) { + if(sSource == this.sources[i]) { + return false; + } + } + return true; + } +}; + +/** + * Outputs a log message to global console.log() function. + * + * @method _printToBrowserConsole + * @param oEntry {Object} Log entry object. + * @private + */ +YAHOO.widget.Logger._printToBrowserConsole = function(oEntry) { + if(window.console && console.log) { + var category = oEntry.category; + var label = oEntry.category.substring(0,4).toUpperCase(); + + var time = oEntry.time; + if (time.toLocaleTimeString) { + var localTime = time.toLocaleTimeString(); + } + else { + localTime = time.toString(); + } + + var msecs = time.getTime(); + var elapsedTime = (YAHOO.widget.Logger._lastTime) ? + (msecs - YAHOO.widget.Logger._lastTime) : 0; + YAHOO.widget.Logger._lastTime = msecs; + + var output = + localTime + " (" + + elapsedTime + "ms): " + + oEntry.source + ": " + + oEntry.msg; + + console.log(output); + } +}; + +///////////////////////////////////////////////////////////////////////////// +// +// Private event handlers +// +///////////////////////////////////////////////////////////////////////////// + +/** + * Handles logging of messages due to window error events. + * + * @method _onWindowError + * @param sMsg {String} The error message. + * @param sUrl {String} URL of the error. + * @param sLine {String} Line number of the error. + * @private + */ +YAHOO.widget.Logger._onWindowError = function(sMsg,sUrl,sLine) { + // Logger is not in scope of this event handler + try { + YAHOO.widget.Logger.log(sMsg+' ('+sUrl+', line '+sLine+')', "window"); + if(YAHOO.widget.Logger._origOnWindowError) { + YAHOO.widget.Logger._origOnWindowError(); + } + } + catch(e) { + return false; + } +}; + +///////////////////////////////////////////////////////////////////////////// +// +// Enable handling of native JavaScript errors +// NB: Not all browsers support the window.onerror event +// +///////////////////////////////////////////////////////////////////////////// + +if(window.onerror) { + // Save any previously defined handler to call + YAHOO.widget.Logger._origOnWindowError = window.onerror; +} +window.onerror = YAHOO.widget.Logger._onWindowError; + +///////////////////////////////////////////////////////////////////////////// +// +// First log +// +///////////////////////////////////////////////////////////////////////////// + +YAHOO.widget.Logger.log("Logger initialized"); + diff --git a/frontend/beta/js/YUI/menu.js b/frontend/beta/js/YUI/menu.js new file mode 100644 index 0000000..50eb0cf --- a/dev/null +++ b/frontend/beta/js/YUI/menu.js @@ -0,0 +1,6780 @@ +/* +Copyright (c) 2006, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.com/yui/license.txt +version: 0.12.0 +*/ + +/** +* @module menu +* @description The Menu Library features a collection of widgets that make +* it easy to add menus to your website or web application. With the Menu +* Library you can create website fly-out menus, customized context menus, or +* application-style menu bars with just a small amount of scripting.
+*+*
+* @title Menu Library +* @namespace YAHOO.widget +* @requires Event, Dom, Container +*/ +(function() { + +var Dom = YAHOO.util.Dom; +var Event = YAHOO.util.Event; + +/** +* Singleton that manages a collection of all menus and menu items. Listens for +* DOM events at the document level and dispatches the events to the +* corresponding menu or menu item. +* +* @namespace YAHOO.widget +* @class MenuManager +* @static +*/ +YAHOO.widget.MenuManager = new function() { + + // Private member variables + + // Flag indicating if the DOM event handlers have been attached + + var m_bInitializedEventHandlers = false; + + // Collection of menus + + var m_oMenus = {}; + + + // Collection of menu items + + var m_oItems = {}; + + // Collection of visible menus + + var m_oVisibleMenus = {}; + + // Logger + + + // Private methods + + /** + * Adds an item to the collection of known menu items. + * @private + * @param {YAHOO.widget.MenuItem} p_oItem Object specifying the MenuItem + * instance to be added. + */ + var addItem = function(p_oItem) { + + var sYUIId = Dom.generateId(); + + if(p_oItem && m_oItems[sYUIId] != p_oItem) { + + p_oItem.element.setAttribute("yuiid", sYUIId); + + m_oItems[sYUIId] = p_oItem; + + p_oItem.destroyEvent.subscribe(onItemDestroy, p_oItem); + + + } + + }; + + /** + * Removes an item from the collection of known menu items. + * @private + * @param {YAHOO.widget.MenuItem} p_oItem Object specifying the MenuItem + * instance to be removed. + */ + var removeItem = function(p_oItem) { + + var sYUIId = p_oItem.element.getAttribute("yuiid"); + + if(sYUIId && m_oItems[sYUIId]) { + + delete m_oItems[sYUIId]; + + + } + + }; + + /** + * Finds the root DIV node of a menu or the root LI node of a menu item. + * @private + * @param {HTMLElement} p_oElement Object specifying + * an HTML element. + */ + var getMenuRootElement = function(p_oElement) { + + var oParentNode; + + if(p_oElement && p_oElement.tagName) { + + switch(p_oElement.tagName.toUpperCase()) { + + case "DIV": + + oParentNode = p_oElement.parentNode; + + // Check if the DIV is the inner "body" node of a menu + + if( + Dom.hasClass(p_oElement, "bd") && + oParentNode && + oParentNode.tagName && + oParentNode.tagName.toUpperCase() == "DIV" + ) { + + return oParentNode; + + } + else { + + return p_oElement; + + } + + break; + + case "LI": + + return p_oElement; + + default: + + oParentNode = p_oElement.parentNode; + + if(oParentNode) { + + return getMenuRootElement(oParentNode); + + } + + break; + + } + + } + + }; + + // Private event handlers + + /** + * Generic, global event handler for all of a menu's DOM-based + * events. This listens for events against the document object. If the + * target of a given event is a member of a menu or menu item's DOM, the + * instance's corresponding Custom Event is fired. + * @private + * @param {Event} p_oEvent Object representing the DOM event object passed + * back by the event utility (YAHOO.util.Event). + */ + var onDOMEvent = function(p_oEvent) { + + // Get the target node of the DOM event + + var oTarget = Event.getTarget(p_oEvent); + + // See if the target of the event was a menu, or a menu item + + var oElement = getMenuRootElement(oTarget); + + var oMenuItem; + var oMenu; + + if(oElement) { + + var sTagName = oElement.tagName.toUpperCase(); + + if(sTagName == "LI") { + + var sYUIId = oElement.getAttribute("yuiid"); + + if(sYUIId) { + + oMenuItem = m_oItems[sYUIId]; + oMenu = oMenuItem.parent; + + } + + } + else if(sTagName == "DIV") { + + if(oElement.id) { + + oMenu = m_oMenus[oElement.id]; + + } + + } + + } + + if(oMenu) { + + // Map of DOM event names to CustomEvent names + + var oEventTypes = { + "click": "clickEvent", + "mousedown": "mouseDownEvent", + "mouseup": "mouseUpEvent", + "mouseover": "mouseOverEvent", + "mouseout": "mouseOutEvent", + "keydown": "keyDownEvent", + "keyup": "keyUpEvent", + "keypress": "keyPressEvent" + }; + + var sCustomEventType = oEventTypes[p_oEvent.type]; + + // Fire the Custom Even that corresponds the current DOM event + + if(oMenuItem && !oMenuItem.cfg.getProperty("disabled")) { + + oMenuItem[sCustomEventType].fire(p_oEvent); + + } + + oMenu[sCustomEventType].fire(p_oEvent, oMenuItem); + + } + else if(p_oEvent.type == "mousedown") { + + /* + If the target of the event wasn't a menu, hide all + dynamically positioned menus + */ + + var oActiveItem; + + for(var i in m_oMenus) { + + if(m_oMenus.hasOwnProperty(i)) { + + oMenu = m_oMenus[i]; + + if( + oMenu.cfg.getProperty("clicktohide") && + oMenu.cfg.getProperty("position") == "dynamic" + ) { + + oMenu.hide(); + + } + else { + + oMenu.clearActiveItem(true); + + } + + } + + } + + } + + }; + + /** + * "destroy" event handler for a menu. + * @private + * @param {String} p_sType String representing the name of the event that + * was fired. + * @param {Array} p_aArgs Array of arguments sent when the event was fired. + * @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that + * fired the event. + */ + var onMenuDestroy = function(p_sType, p_aArgs, p_oMenu) { + + this.removeMenu(p_oMenu); + + }; + + /** + * "destroy" event handler for a MenuItem instance. + * @private + * @param {String} p_sType String representing the name of the event that + * was fired. + * @param {Array} p_aArgs Array of arguments sent when the event was fired. + * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item + * that fired the event. + */ + var onItemDestroy = function(p_sType, p_aArgs, p_oItem) { + + var sYUIId = p_oItem.element.getAttribute("yuiid"); + + if(sYUIId) { + + delete m_oItems[sYUIId]; + + } + + }; + + /** + * Event handler for when the "visible" configuration property + * of a Menu instance changes. + * @private + * @param {String} p_sType String representing the name of the event that + * was fired. + * @param {Array} p_aArgs Array of arguments sent when the event was fired. + * @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that + * fired the event. + */ + var onMenuVisibleConfigChange = function(p_sType, p_aArgs, p_oMenu) { + + var bVisible = p_aArgs[0]; + + if(bVisible) { + + m_oVisibleMenus[p_oMenu.id] = p_oMenu; + + + } + else if(m_oVisibleMenus[p_oMenu.id]) { + + delete m_oVisibleMenus[p_oMenu.id]; + + + } + + }; + + /** + * "itemadded" event handler for a Menu instance. + * @private + * @param {String} p_sType String representing the name of the event that + * was fired. + * @param {Array} p_aArgs Array of arguments sent when the event was fired. + */ + var onItemAdded = function(p_sType, p_aArgs) { + + addItem(p_aArgs[0]); + + }; + + + /** + * "itemremoved" event handler for a Menu instance. + * @private + * @param {String} p_sType String representing the name of the event that + * was fired. + * @param {Array} p_aArgs Array of arguments sent when the event was fired. + */ + var onItemRemoved = function(p_sType, p_aArgs) { + + removeItem(p_aArgs[0]); + + }; + + // Privileged methods + + /** + * @method addMenu + * @description Adds a menu to the collection of known menus. + * @param {YAHOO.widget.Menu} p_oMenu Object specifying the Menu instance + * to be added. + */ + this.addMenu = function(p_oMenu) { + + if(p_oMenu && p_oMenu.id && !m_oMenus[p_oMenu.id]) { + + m_oMenus[p_oMenu.id] = p_oMenu; + + + if(!m_bInitializedEventHandlers) { + + var oDoc = document; + + Event.addListener(oDoc, "mouseover", onDOMEvent, this, true); + Event.addListener(oDoc, "mouseout", onDOMEvent, this, true); + Event.addListener(oDoc, "mousedown", onDOMEvent, this, true); + Event.addListener(oDoc, "mouseup", onDOMEvent, this, true); + Event.addListener(oDoc, "click", onDOMEvent, this, true); + Event.addListener(oDoc, "keydown", onDOMEvent, this, true); + Event.addListener(oDoc, "keyup", onDOMEvent, this, true); + Event.addListener(oDoc, "keypress", onDOMEvent, this, true); + + m_bInitializedEventHandlers = true; + + + } + + p_oMenu.destroyEvent.subscribe(onMenuDestroy, p_oMenu, this); + + p_oMenu.cfg.subscribeToConfigEvent( + "visible", + onMenuVisibleConfigChange, + p_oMenu + ); + + p_oMenu.itemAddedEvent.subscribe(onItemAdded); + p_oMenu.itemRemovedEvent.subscribe(onItemRemoved); + + + } + + }; + + /** + * @method removeMenu + * @description Removes a menu from the collection of known menus. + * @param {YAHOO.widget.Menu} p_oMenu Object specifying the Menu instance + * to be removed. + */ + this.removeMenu = function(p_oMenu) { + + if(p_oMenu && m_oMenus[p_oMenu.id]) { + + delete m_oMenus[p_oMenu.id]; + + + } + + }; + + /** + * @method hideVisible + * @description Hides all visible, dynamically positioned menus. + */ + this.hideVisible = function() { + + var oMenu; + + for(var i in m_oVisibleMenus) { + + if(m_oVisibleMenus.hasOwnProperty(i)) { + + oMenu = m_oVisibleMenus[i]; + + if(oMenu.cfg.getProperty("position") == "dynamic") { + + oMenu.hide(); + + } + + } + + } + + }; + + /** + * @method getMenus + * @description Returns an array of all menus registered with the + * menu manger. + * @return {Array} + */ + this.getMenus = function() { + + return m_oMenus; + + }; + + /** + * @method getMenu + * @description Returns a menu with the specified id. + * @param {String} p_sId String specifying the id of the menu to + * be retrieved. + * @return {YAHOO.widget.Menu} + */ + this.getMenu = function(p_sId) { + + if(m_oMenus[p_sId]) { + + return m_oMenus[p_sId]; + + } + + }; + + + /** + * @method toString + * @description Returns a string representing the menu manager. + * @return {String} + */ + this.toString = function() { + + return ("MenuManager"); + + }; + +}; + +})(); + +(function() { + +var Dom = YAHOO.util.Dom; +var Event = YAHOO.util.Event; + +/** +* The Menu class creates a container that holds a vertical list representing +* a set of options or commands. Menu is the base class for all +* menu containers. +* @param {String} p_oElement String specifying the id attribute of the +*- Screen-reader accessibility.
+*- Keyboard and mouse navigation.
+*- A rich event model that provides access to all of a menu's +* interesting moments.
+*- Support for +* Progressive +* Enhancement; Menus can be created from simple, +* semantic markup on the page or purely through JavaScript.
+*<div>
element of the menu. +* @param {String} p_oElement String specifying the id attribute of the +*<select>
element to be used as the data source +* for the menu. +* @param {HTMLDivElement} p_oElement Object +* specifying the<div>
element of the menu. +* @param {HTMLSelectElement} p_oElement +* Object specifying the<select>
element to be used as +* the data source for the menu. +* @param {Object} p_oConfig Optional. Object literal specifying the +* configuration for the menu. See configuration class documentation for +* more details. +* @namespace YAHOO.widget +* @class Menu +* @constructor +* @extends YAHOO.widget.Overlay +*/ +YAHOO.widget.Menu = function(p_oElement, p_oConfig) { + + if(p_oConfig) { + + this.parent = p_oConfig.parent; + + this.lazyLoad = p_oConfig.lazyLoad || p_oConfig.lazyload; + + this.itemData = p_oConfig.itemData || p_oConfig.itemdata; + + } + + YAHOO.widget.Menu.superclass.constructor.call( + this, + p_oElement, + p_oConfig + ); + +}; + +YAHOO.extend(YAHOO.widget.Menu, YAHOO.widget.Overlay, { + +// Constants + +/** +* @property CSS_CLASS_NAME +* @description String representing the CSS class(es) to be applied to the +* menu's<div>
element. +* @default "yuimenu" +* @final +* @type String +*/ +CSS_CLASS_NAME: "yuimenu", + +/** +* @property ITEM_TYPE +* @description Object representing the type of menu item to instantiate and +* add when parsing the child nodes (either<li>
element, +*<optgroup>
element or<option>
) +* of the menu's source HTML element. +* @default YAHOO.widget.MenuItem +* @final +* @type YAHOO.widget.MenuItem +*/ +ITEM_TYPE: null, + +/** +* @property GROUP_TITLE_TAG_NAME +* @description String representing the tagname of the HTML element used to +* title the menu's item groups. +* @default H6 +* @final +* @type String +*/ +GROUP_TITLE_TAG_NAME: "h6", + +// Private properties + +/** +* @property _nHideDelayId +* @description Number representing the time-out setting used to cancel the +* hiding of a menu. +* @default null +* @private +* @type Number +*/ +_nHideDelayId: null, + +/** +* @property _nShowDelayId +* @description Number representing the time-out setting used to cancel the +* showing of a menu. +* @default null +* @private +* @type Number +*/ +_nShowDelayId: null, + +/** +* @property _hideDelayEventHandlersAssigned +* @description Boolean indicating if the "mouseover" and "mouseout" event +* handlers used for hiding the menu via a call to "window.setTimeout" have +* already been assigned. +* @default false +* @private +* @type Boolean +*/ +_hideDelayEventHandlersAssigned: false, + +/** +* @property _bHandledMouseOverEvent +* @description Boolean indicating the current state of the menu's +* "mouseover" event. +* @default false +* @private +* @type Boolean +*/ +_bHandledMouseOverEvent: false, + +/** +* @property _bHandledMouseOutEvent +* @description Boolean indicating the current state of the menu's +* "mouseout" event. +* @default false +* @private +* @type Boolean +*/ +_bHandledMouseOutEvent: false, + +/** +* @property _aGroupTitleElements +* @description Array of HTML element used to title groups of menu items. +* @default [] +* @private +* @type Array +*/ +_aGroupTitleElements: null, + +/** +* @property _aItemGroups +* @description Array of menu items. +* @default [] +* @private +* @type Array +*/ +_aItemGroups: null, + +/** +* @property _aListElements +* @description Array of<ul>
elements, each of which is +* the parent node for each item's<li>
element. +* @default [] +* @private +* @type Array +*/ +_aListElements: null, + +// Public properties + +/** +* @property lazyLoad +* @description Boolean indicating if the menu's "lazy load" feature is +* enabled. If set to "true," initialization and rendering of the menu's +* items will be deferred until the first time it is made visible. This +* property should be set via the constructor using the configuration +* object literal. +* @default false +* @type Boolean +*/ +lazyLoad: false, + +/** +* @property itemData +* @description Array of items to be added to the menu. The array can contain +* strings representing the text for each item to be created, object literals +* representing the menu item configuration properties, or MenuItem instances. +* This property should be set via the constructor using the configuration +* object literal. +* @default null +* @type Array +*/ +itemData: null, + +/** +* @property activeItem +* @description Object reference to the item in the menu that has focus. +* @default null +* @type YAHOO.widget.MenuItem +*/ +activeItem: null, + +/** +* @property parent +* @description Object reference to the menu's parent menu or menu item. +* This property can be set via the constructor using the configuration +* object literal. +* @default null +* @type YAHOO.widget.MenuItem +*/ +parent: null, + +/** +* @property srcElement +* @description Object reference to the HTML element (either +*<select>
or<div>
) used to +* create the menu. +* @default null +* @type HTMLSelectElement|HTMLDivElement +*/ +srcElement: null, + +// Events + +/** +* @event mouseOverEvent +* @description Fires when the mouse has entered the menu. Passes back +* the DOM Event object as an argument. +*/ +mouseOverEvent: null, + +/** +* @event mouseOutEvent +* @description Fires when the mouse has left the menu. Passes back the DOM +* Event object as an argument. +* @type YAHOO.util.CustomEvent +*/ +mouseOutEvent: null, + +/** +* @event mouseDownEvent +* @description Fires when the user mouses down on the menu. Passes back the +* DOM Event object as an argument. +* @type YAHOO.util.CustomEvent +*/ +mouseDownEvent: null, + +/** +* @event mouseUpEvent +* @description Fires when the user releases a mouse button while the mouse is +* over the menu. Passes back the DOM Event object as an argument. +* @type YAHOO.util.CustomEvent +*/ +mouseUpEvent: null, + +/** +* @event clickEvent +* @description Fires when the user clicks the on the menu. Passes back the +* DOM Event object as an argument. +* @type YAHOO.util.CustomEvent +*/ +clickEvent: null, + +/** +* @event keyPressEvent +* @description Fires when the user presses an alphanumeric key when one of the +* menu's items has focus. Passes back the DOM Event object as an argument. +* @type YAHOO.util.CustomEvent +*/ +keyPressEvent: null, + +/** +* @event keyDownEvent +* @description Fires when the user presses a key when one of the menu's items +* has focus. Passes back the DOM Event object as an argument. +* @type YAHOO.util.CustomEvent +*/ +keyDownEvent: null, + +/** +* @event keyUpEvent +* @description Fires when the user releases a key when one of the menu's items +* has focus. Passes back the DOM Event object as an argument. +* @type YAHOO.util.CustomEvent +*/ +keyUpEvent: null, + +/** +* @event itemAddedEvent +* @description Fires when an item is added to the menu. +* @type YAHOO.util.CustomEvent +*/ +itemAddedEvent: null, + +/** +* @event itemRemovedEvent +* @description Fires when an item is removed to the menu. +* @type YAHOO.util.CustomEvent +*/ +itemRemovedEvent: null, + +/** +* @method init +* @description The Menu class's initialization method. This method is +* automatically called by the constructor, and sets up all DOM references +* for pre-existing markup, and creates required markup if it is not +* already present. +* @param {String} p_oElement String specifying the id attribute of the +*<div>
element of the menu. +* @param {String} p_oElement String specifying the id attribute of the +*<select>
element to be used as the data source +* for the menu. +* @param {HTMLDivElement} p_oElement Object +* specifying the<div>
element of the menu. +* @param {HTMLSelectElement} p_oElement +* Object specifying the<select>
element to be used as +* the data source for the menu. +* @param {Object} p_oConfig Optional. Object literal specifying the +* configuration for the menu. See configuration class documentation for +* more details. +*/ +init: function(p_oElement, p_oConfig) { + + this._aItemGroups = []; + this._aListElements = []; + this._aGroupTitleElements = []; + + if(!this.ITEM_TYPE) { + + this.ITEM_TYPE = YAHOO.widget.MenuItem; + + } + + var oElement; + + if(typeof p_oElement == "string") { + + oElement = document.getElementById(p_oElement); + + } + else if(p_oElement.tagName) { + + oElement = p_oElement; + + } + + if(oElement && oElement.tagName) { + + switch(oElement.tagName.toUpperCase()) { + + case "DIV": + + this.srcElement = oElement; + + if(!oElement.id) { + + oElement.setAttribute("id", Dom.generateId()); + + } + + /* + Note: we don't pass the user config in here yet + because we only want it executed once, at the lowest + subclass level. + */ + + YAHOO.widget.Menu.superclass.init.call(this, oElement); + + this.beforeInitEvent.fire(YAHOO.widget.Menu); + + + break; + + case "SELECT": + + this.srcElement = oElement; + + + /* + The source element is not something that we can use + outright, so we need to create a new Overlay + + Note: we don't pass the user config in here yet + because we only want it executed once, at the lowest + subclass level. + */ + + YAHOO.widget.Menu.superclass.init.call(this, Dom.generateId()); + + this.beforeInitEvent.fire(YAHOO.widget.Menu); + + break; + + } + + } + else { + + /* + Note: we don't pass the user config in here yet + because we only want it executed once, at the lowest + subclass level. + */ + + YAHOO.widget.Menu.superclass.init.call(this, p_oElement); + + this.beforeInitEvent.fire(YAHOO.widget.Menu); + + } + + if(this.element) { + + var oEl = this.element; + + Dom.addClass(oEl, this.CSS_CLASS_NAME); + + // Subscribe to Custom Events + + this.initEvent.subscribe(this._onInit, this, true); + this.beforeRenderEvent.subscribe(this._onBeforeRender, this, true); + this.renderEvent.subscribe(this._onRender, this, true); + this.beforeShowEvent.subscribe(this._onBeforeShow, this, true); + this.showEvent.subscribe(this._onShow, this, true); + this.beforeHideEvent.subscribe(this._onBeforeHide, this, true); + this.mouseOverEvent.subscribe(this._onMouseOver, this, true); + this.mouseOutEvent.subscribe(this._onMouseOut, this, true); + this.clickEvent.subscribe(this._onClick, this, true); + this.keyDownEvent.subscribe(this._onKeyDown, this, true); + + if(p_oConfig) { + + this.cfg.applyConfig(p_oConfig, true); + + } + + // Register the Menu instance with the MenuManager + + YAHOO.widget.MenuManager.addMenu(this); + + + this.initEvent.fire(YAHOO.widget.Menu); + + } + +}, + +// Private methods + +/** +* @method _initSubTree +* @description Iterates the childNodes of the source element to find nodes +* used to instantiate menu and menu items. +* @private +*/ +_initSubTree: function() { + + var oNode; + + if(this.srcElement.tagName == "DIV") { + + /* + Populate the collection of item groups and item + group titles + */ + + oNode = this.body.firstChild; + + var nGroup = 0; + var sGroupTitleTagName = this.GROUP_TITLE_TAG_NAME.toUpperCase(); + + do { + + if(oNode && oNode.tagName) { + + switch(oNode.tagName.toUpperCase()) { + + case sGroupTitleTagName: + + this._aGroupTitleElements[nGroup] = oNode; + + break; + + case "UL": + + this._aListElements[nGroup] = oNode; + this._aItemGroups[nGroup] = []; + nGroup++; + + break; + + } + + } + + } + while((oNode = oNode.nextSibling)); + + /* + Apply the "first-of-type" class to the first UL to mimic + the "first-of-type" CSS3 psuedo class. + */ + + if(this._aListElements[0]) { + + Dom.addClass(this._aListElements[0], "first-of-type"); + + } + + } + + oNode = null; + + if(this.srcElement.tagName) { + + switch(this.srcElement.tagName.toUpperCase()) { + + case "DIV": + + if(this._aListElements.length > 0) { + + + var i = this._aListElements.length - 1; + + do { + + oNode = this._aListElements[i].firstChild; + + + do { + + if(oNode && oNode.tagName) { + + switch(oNode.tagName.toUpperCase()) { + + case "LI": + + + this.addItem( + new this.ITEM_TYPE( + oNode, + { parent: this } + ), + i + ); + + break; + + } + + } + + } + while((oNode = oNode.nextSibling)); + + } + while(i--); + + } + + break; + + case "SELECT": + + + oNode = this.srcElement.firstChild; + + do { + + if(oNode && oNode.tagName) { + + switch(oNode.tagName.toUpperCase()) { + + case "OPTGROUP": + case "OPTION": + + + this.addItem( + new this.ITEM_TYPE( + oNode, + { parent: this } + ) + ); + + break; + + } + + } + + } + while((oNode = oNode.nextSibling)); + + break; + + } + + } + +}, + +/** +* @method _getFirstEnabledItem +* @description Returns the first enabled item in the menu. +* @return {YAHOO.widget.MenuItem} +* @private +*/ +_getFirstEnabledItem: function() { + + var nGroups = this._aItemGroups.length; + var oItem; + var aItemGroup; + + for(var i=0; i= aGroup.length); + + if(aGroup[p_nItemIndex]) { + + aGroup.splice(p_nItemIndex, 0, oItem); + + } + else { + + aGroup[p_nItemIndex] = oItem; + + } + + oGroupItem = aGroup[p_nItemIndex]; + + if(oGroupItem) { + + if( + bAppend && + ( + !oGroupItem.element.parentNode || + oGroupItem.element.parentNode.nodeType == 11 + ) + ) { + + this._aListElements[nGroupIndex].appendChild( + oGroupItem.element + ); + + } + else { + + + /** + * Returns the next sibling of an item in an array. + * @private + * @param {p_aArray} Array to search. + * @param {p_nStartIndex} Number indicating the index to + * start searching the array. + * @return {Object} + */ + var getNextItemSibling = + + function(p_aArray, p_nStartIndex) { + + return ( + p_aArray[p_nStartIndex] || + getNextItemSibling( + p_aArray, + (p_nStartIndex+1) + ) + ); + + }; + + + var oNextItemSibling = + getNextItemSibling(aGroup, (p_nItemIndex+1)); + + if( + oNextItemSibling && + ( + !oGroupItem.element.parentNode || + oGroupItem.element.parentNode.nodeType == 11 + ) + ) { + + this._aListElements[nGroupIndex].insertBefore( + oGroupItem.element, + oNextItemSibling.element + ); + + } + + } + + + oGroupItem.parent = this; + + this._subscribeToItemEvents(oGroupItem); + + this._configureSubmenu(oGroupItem); + + this._updateItemProperties(nGroupIndex); + + + this.itemAddedEvent.fire(oGroupItem); + + return oGroupItem; + + } + + } + else { + + var nItemIndex = aGroup.length; + + aGroup[nItemIndex] = oItem; + + oGroupItem = aGroup[nItemIndex]; + + + if(oGroupItem) { + + if( + !Dom.isAncestor( + this._aListElements[nGroupIndex], + oGroupItem.element + ) + ) { + + this._aListElements[nGroupIndex].appendChild( + oGroupItem.element + ); + + } + + oGroupItem.element.setAttribute("groupindex", nGroupIndex); + oGroupItem.element.setAttribute("index", nItemIndex); + + oGroupItem.parent = this; + + oGroupItem.index = nItemIndex; + oGroupItem.groupIndex = nGroupIndex; + + this._subscribeToItemEvents(oGroupItem); + + this._configureSubmenu(oGroupItem); + + if(nItemIndex === 0) { + + Dom.addClass(oGroupItem.element, "first-of-type"); + + } + + + + this.itemAddedEvent.fire(oGroupItem); + + return oGroupItem; + + } + + } + + } + +}, + +/** +* @method _removeItemFromGroupByIndex +* @description Removes a menu item from a group by index. Returns the menu +* item that was removed. +* @private +* @param {Number} p_nGroupIndex Number indicating the group to which the menu +* item belongs. +* @param {Number} p_nItemIndex Number indicating the index of the menu item +* to be removed. +* @return {YAHOO.widget.MenuItem} +*/ +_removeItemFromGroupByIndex: function(p_nGroupIndex, p_nItemIndex) { + + var nGroupIndex = typeof p_nGroupIndex == "number" ? p_nGroupIndex : 0; + var aGroup = this._getItemGroup(nGroupIndex); + + if(aGroup) { + + var aArray = aGroup.splice(p_nItemIndex, 1); + var oItem = aArray[0]; + + if(oItem) { + + // Update the index and className properties of each member + + this._updateItemProperties(nGroupIndex); + + if(aGroup.length === 0) { + + // Remove the UL + + var oUL = this._aListElements[nGroupIndex]; + + if(this.body && oUL) { + + this.body.removeChild(oUL); + + } + + // Remove the group from the array of items + + this._aItemGroups.splice(nGroupIndex, 1); + + + // Remove the UL from the array of ULs + + this._aListElements.splice(nGroupIndex, 1); + + + /* + Assign the "first-of-type" class to the new first UL + in the collection + */ + + oUL = this._aListElements[0]; + + if(oUL) { + + Dom.addClass(oUL, "first-of-type"); + + } + + } + + + this.itemRemovedEvent.fire(oItem); + + // Return a reference to the item that was removed + + return oItem; + + } + + } + +}, + +/** +* @method _removeItemFromGroupByValue +* @description Removes a menu item from a group by reference. Returns the +* menu item that was removed. +* @private +* @param {Number} p_nGroupIndex Number indicating the group to which the +* menu item belongs. +* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem +* instance to be removed. +* @return {YAHOO.widget.MenuItem} +*/ +_removeItemFromGroupByValue: function(p_nGroupIndex, p_oItem) { + + var aGroup = this._getItemGroup(p_nGroupIndex); + + if(aGroup) { + + var nItems = aGroup.length; + var nItemIndex = -1; + + if(nItems > 0) { + + var i = nItems-1; + + do { + + if(aGroup[i] == p_oItem) { + + nItemIndex = i; + break; + + } + + } + while(i--); + + if(nItemIndex > -1) { + + return this._removeItemFromGroupByIndex( + p_nGroupIndex, + nItemIndex + ); + + } + + } + + } + +}, + +/** +* @method _updateItemProperties +* @description Updates the "index," "groupindex," and "className" properties +* of the menu items in the specified group. +* @private +* @param {Number} p_nGroupIndex Number indicating the group of items to update. +*/ +_updateItemProperties: function(p_nGroupIndex) { + + var aGroup = this._getItemGroup(p_nGroupIndex); + var nItems = aGroup.length; + + if(nItems > 0) { + + var i = nItems - 1; + var oItem; + var oLI; + + // Update the index and className properties of each member + + do { + + oItem = aGroup[i]; + + if(oItem) { + + oLI = oItem.element; + + oItem.index = i; + oItem.groupIndex = p_nGroupIndex; + + oLI.setAttribute("groupindex", p_nGroupIndex); + oLI.setAttribute("index", i); + + Dom.removeClass(oLI, "first-of-type"); + + } + + } + while(i--); + + if(oLI) { + + Dom.addClass(oLI, "first-of-type"); + + } + + } + +}, + +/** +* @method _createItemGroup +* @description Creates a new menu item group (array) and its associated +* <ul>
element. Returns an aray of menu item groups. +* @private +* @param {Number} p_nIndex Number indicating the group to create. +* @return {Array} +*/ +_createItemGroup: function(p_nIndex) { + + if(!this._aItemGroups[p_nIndex]) { + + this._aItemGroups[p_nIndex] = []; + + var oUL = document.createElement("ul"); + + this._aListElements[p_nIndex] = oUL; + + return this._aItemGroups[p_nIndex]; + + } + +}, + +/** +* @method _getItemGroup +* @description Returns the menu item group at the specified index. +* @private +* @param {Number} p_nIndex Number indicating the index of the menu item group +* to be retrieved. +* @return {Array} +*/ +_getItemGroup: function(p_nIndex) { + + var nIndex = ((typeof p_nIndex == "number") ? p_nIndex : 0); + + return this._aItemGroups[nIndex]; + +}, + +/** +* @method _configureSubmenu +* @description Subscribes the menu item's submenu to its parent menu's events. +* @private +* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem +* instance with the submenu to be configured. +*/ +_configureSubmenu: function(p_oItem) { + + var oSubmenu = p_oItem.cfg.getProperty("submenu"); + + if(oSubmenu) { + + /* + Listen for configuration changes to the parent menu + so they they can be applied to the submenu. + */ + + this.cfg.configChangedEvent.subscribe( + this._onParentMenuConfigChange, + oSubmenu, + true + ); + + this.renderEvent.subscribe( + this._onParentMenuRender, + oSubmenu, + true + ); + + oSubmenu.beforeShowEvent.subscribe( + this._onSubmenuBeforeShow, + oSubmenu, + true + ); + + oSubmenu.showEvent.subscribe( + this._onSubmenuShow, + oSubmenu, + true + ); + + oSubmenu.hideEvent.subscribe( + this._onSubmenuHide, + oSubmenu, + true + ); + + } + +}, + +/** +* @method _subscribeToItemEvents +* @description Subscribes a menu to a menu item's event. +* @private +* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem +* instance whose events should be subscribed to. +*/ +_subscribeToItemEvents: function(p_oItem) { + + p_oItem.focusEvent.subscribe(this._onMenuItemFocus, p_oItem, this); + + p_oItem.blurEvent.subscribe(this._onMenuItemBlur, this, true); + + p_oItem.cfg.configChangedEvent.subscribe( + this._onMenuItemConfigChange, + p_oItem, + this + ); + +}, + +/** +* @method _getOffsetWidth +* @description Returns the offset width of the menu's +*<div>
element. +* @private +*/ +_getOffsetWidth: function() { + + var oClone = this.element.cloneNode(true); + + Dom.setStyle(oClone, "width", ""); + + document.body.appendChild(oClone); + + var sWidth = oClone.offsetWidth; + + document.body.removeChild(oClone); + + return sWidth; + +}, + +/** +* @method _cancelHideDelay +* @description Cancels the call to "hideMenu." +* @private +*/ +_cancelHideDelay: function() { + + var oRoot = this.getRoot(); + + if(oRoot._nHideDelayId) { + + window.clearTimeout(oRoot._nHideDelayId); + + } + +}, + +/** +* @method _execHideDelay +* @description Hides the menu after the number of milliseconds specified by +* the "hidedelay" configuration property. +* @private +*/ +_execHideDelay: function() { + + this._cancelHideDelay(); + + var oRoot = this.getRoot(); + var me = this; + + var hideMenu = function() { + + if(oRoot.activeItem) { + + oRoot.clearActiveItem(); + + } + + if(oRoot == me && me.cfg.getProperty("position") == "dynamic") { + + me.hide(); + + } + + }; + + oRoot._nHideDelayId = + window.setTimeout(hideMenu, oRoot.cfg.getProperty("hidedelay")); + +}, + +/** +* @method _cancelShowDelay +* @description Cancels the call to the "showMenu." +* @private +*/ +_cancelShowDelay: function() { + + var oRoot = this.getRoot(); + + if(oRoot._nShowDelayId) { + + window.clearTimeout(oRoot._nShowDelayId); + + } + +}, + +/** +* @method _execShowDelay +* @description Shows the menu after the number of milliseconds specified by +* the "showdelay" configuration property have ellapsed. +* @private +* @param {YAHOO.widget.Menu} p_oMenu Object specifying the menu that should +* be made visible. +*/ +_execShowDelay: function(p_oMenu) { + + this._cancelShowDelay(); + + var oRoot = this.getRoot(); + + var showMenu = function() { + + p_oMenu.show(); + + }; + + oRoot._nShowDelayId = + window.setTimeout(showMenu, oRoot.cfg.getProperty("showdelay")); + +}, + +// Protected methods + +/** +* @method _onMouseOver +* @description "mouseover" event handler for the menu. +* @protected +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that +* fired the event. +*/ +_onMouseOver: function(p_sType, p_aArgs, p_oMenu) { + + var oEvent = p_aArgs[0]; + var oItem = p_aArgs[1]; + var oTarget = Event.getTarget(oEvent); + + if( + !this._bHandledMouseOverEvent && + (oTarget == this.element || Dom.isAncestor(this.element, oTarget)) + ) { + + // MENU MOUSEOVER LOGIC HERE + + this.clearActiveItem(); + + this._bHandledMouseOverEvent = true; + this._bHandledMouseOutEvent = false; + + } + + if( + oItem && !oItem.handledMouseOverEvent && + (oTarget == oItem.element || Dom.isAncestor(oItem.element, oTarget)) + ) { + + var oItemCfg = oItem.cfg; + + // Select and focus the current menu item + + oItemCfg.setProperty("selected", true); + oItem.focus(); + + if(this.cfg.getProperty("autosubmenudisplay")) { + + // Show the submenu this menu item + + var oSubmenu = oItemCfg.getProperty("submenu"); + + if(oSubmenu) { + + if(this.cfg.getProperty("showdelay") > 0) { + + this._execShowDelay(oSubmenu); + + } + else { + + oSubmenu.show(); + + } + + } + + } + + oItem.handledMouseOverEvent = true; + oItem.handledMouseOutEvent = false; + + } + +}, + +/** +* @method _onMouseOut +* @description "mouseout" event handler for the menu. +* @protected +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that +* fired the event. +*/ +_onMouseOut: function(p_sType, p_aArgs, p_oMenu) { + + var oEvent = p_aArgs[0]; + var oItem = p_aArgs[1]; + var oRelatedTarget = Event.getRelatedTarget(oEvent); + var bMovingToSubmenu = false; + + if(oItem) { + + var oItemCfg = oItem.cfg; + var oSubmenu = oItemCfg.getProperty("submenu"); + + if( + oSubmenu && + ( + oRelatedTarget == oSubmenu.element || + Dom.isAncestor(oSubmenu.element, oRelatedTarget) + ) + ) { + + bMovingToSubmenu = true; + + } + + if( + !oItem.handledMouseOutEvent && + ( + ( + oRelatedTarget != oItem.element && + !Dom.isAncestor(oItem.element, oRelatedTarget) + ) || bMovingToSubmenu + ) + ) { + + if(this.cfg.getProperty("showdelay") > 0) { + + this._cancelShowDelay(); + + } + + if(!bMovingToSubmenu) { + + oItemCfg.setProperty("selected", false); + + } + + if(this.cfg.getProperty("autosubmenudisplay")) { + + if(oSubmenu) { + + if( + !( + oRelatedTarget == oSubmenu.element || + YAHOO.util.Dom.isAncestor( + oSubmenu.element, + oRelatedTarget + ) + ) + ) { + + oSubmenu.hide(); + + } + + } + + } + + oItem.handledMouseOutEvent = true; + oItem.handledMouseOverEvent = false; + + } + + } + + if( + !this._bHandledMouseOutEvent && + ( + ( + oRelatedTarget != this.element && + !Dom.isAncestor(this.element, oRelatedTarget) + ) + || bMovingToSubmenu + ) + ) { + + this._bHandledMouseOutEvent = true; + this._bHandledMouseOverEvent = false; + + } + +}, + +/** +* @method _onClick +* @description "click" event handler for the menu. +* @protected +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that +* fired the event. +*/ +_onClick: function(p_sType, p_aArgs, p_oMenu) { + + var oEvent = p_aArgs[0]; + var oItem = p_aArgs[1]; + var oTarget = Event.getTarget(oEvent); + + if(oItem) { + + var oItemCfg = oItem.cfg; + var oSubmenu = oItemCfg.getProperty("submenu"); + + /* + ACCESSIBILITY FEATURE FOR SCREEN READERS: + Expand/collapse the submenu when the user clicks + on the submenu indicator image. + */ + + if(oTarget == oItem.submenuIndicator && oSubmenu) { + + if(oSubmenu.cfg.getProperty("visible")) { + + oSubmenu.hide(); + + } + else { + + this.clearActiveItem(); + + this.activeItem = oItem; + + oItem.cfg.setProperty("selected", true); + + oSubmenu.show(); + + } + + } + else { + + var sURL = oItemCfg.getProperty("url"); + var bCurrentPageURL = (sURL.substr((sURL.length-1),1) == "#"); + var sTarget = oItemCfg.getProperty("target"); + var bHasTarget = (sTarget && sTarget.length > 0); + + /* + Prevent the browser from following links + equal to "#" + */ + + if( + oTarget.tagName.toUpperCase() == "A" && + bCurrentPageURL && !bHasTarget + ) { + + Event.preventDefault(oEvent); + + } + + if( + oTarget.tagName.toUpperCase() != "A" && + !bCurrentPageURL && !bHasTarget + ) { + + /* + Follow the URL of the item regardless of + whether or not the user clicked specifically + on the anchor element. + */ + + document.location = sURL; + + } + + /* + If the item doesn't navigate to a URL and it doesn't have + a submenu, then collapse the menu tree. + */ + + if(bCurrentPageURL && !oSubmenu) { + + var oRoot = this.getRoot(); + + if(oRoot.cfg.getProperty("position") == "static") { + + oRoot.clearActiveItem(); + + } + else { + + oRoot.hide(); + + } + + } + + } + + } + +}, + +/** +* @method _onKeyDown +* @description "keydown" event handler for the menu. +* @protected +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that +* fired the event. +*/ +_onKeyDown: function(p_sType, p_aArgs, p_oMenu) { + + var oEvent = p_aArgs[0]; + var oItem = p_aArgs[1]; + var oSubmenu; + + if(oItem) { + + var oItemCfg = oItem.cfg; + var oParentItem = this.parent; + var oRoot; + var oNextItem; + + switch(oEvent.keyCode) { + + case 38: // Up arrow + case 40: // Down arrow + + if( + oItem == this.activeItem && + !oItemCfg.getProperty("selected") + ) { + + oItemCfg.setProperty("selected", true); + + } + else { + + oNextItem = (oEvent.keyCode == 38) ? + oItem.getPreviousEnabledSibling() : + oItem.getNextEnabledSibling(); + + if(oNextItem) { + + this.clearActiveItem(); + + oNextItem.cfg.setProperty("selected", true); + oNextItem.focus(); + + } + + } + + Event.preventDefault(oEvent); + + break; + + + case 39: // Right arrow + + oSubmenu = oItemCfg.getProperty("submenu"); + + if(oSubmenu) { + + if(!oItemCfg.getProperty("selected")) { + + oItemCfg.setProperty("selected", true); + + } + + oSubmenu.show(); + + oSubmenu.setInitialSelection(); + + } + else { + + oRoot = this.getRoot(); + + if(oRoot instanceof YAHOO.widget.MenuBar) { + + oNextItem = oRoot.activeItem.getNextEnabledSibling(); + + if(oNextItem) { + + oRoot.clearActiveItem(); + + oNextItem.cfg.setProperty("selected", true); + + oSubmenu = oNextItem.cfg.getProperty("submenu"); + + if(oSubmenu) { + + oSubmenu.show(); + + } + + oNextItem.focus(); + + } + + } + + } + + + Event.preventDefault(oEvent); + + break; + + + case 37: // Left arrow + + if(oParentItem) { + + var oParentMenu = oParentItem.parent; + + if(oParentMenu instanceof YAHOO.widget.MenuBar) { + + oNextItem = + oParentMenu.activeItem.getPreviousEnabledSibling(); + + if(oNextItem) { + + oParentMenu.clearActiveItem(); + + oNextItem.cfg.setProperty("selected", true); + + oSubmenu = oNextItem.cfg.getProperty("submenu"); + + if(oSubmenu) { + + oSubmenu.show(); + + } + + oNextItem.focus(); + + } + + } + else { + + this.hide(); + + oParentItem.focus(); + + } + + } + + Event.preventDefault(oEvent); + + break; + + } + + } + + if(oEvent.keyCode == 27) { // Esc key + + if(this.cfg.getProperty("position") == "dynamic") { + + this.hide(); + + if(this.parent) { + + this.parent.focus(); + + } + + } + else if(this.activeItem) { + + oSubmenu = this.activeItem.cfg.getProperty("submenu"); + + if(oSubmenu && oSubmenu.cfg.getProperty("visible")) { + + oSubmenu.hide(); + this.activeItem.focus(); + + } + else { + + this.activeItem.cfg.setProperty("selected", false); + this.activeItem.blur(); + + } + + } + + Event.preventDefault(oEvent); + + } + +}, + +// Private methods + +/** +* @method _onInit +* @description "init" event handler for the menu. +* @private +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that +* fired the event. +*/ +_onInit: function(p_sType, p_aArgs, p_oMenu) { + + if( + ( + (this.parent && !this.lazyLoad) || + (!this.parent && this.cfg.getProperty("position") == "static") || + ( + !this.parent && + !this.lazyLoad && + this.cfg.getProperty("position") == "dynamic" + ) + ) && + this.getItemGroups().length === 0 + ) { + + if(this.srcElement) { + + this._initSubTree(); + + } + + if(this.itemData) { + + this.addItems(this.itemData); + + } + + } + else if(this.lazyLoad) { + + this.cfg.fireQueue(); + + } + +}, + +/** +* @method _onBeforeRender +* @description "beforerender" event handler for the menu. Appends all of the +*<ul>
,<li>
and their accompanying +* title elements to the body element of the menu. +* @private +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that +* fired the event. +*/ +_onBeforeRender: function(p_sType, p_aArgs, p_oMenu) { + + var oConfig = this.cfg; + var oEl = this.element; + var nListElements = this._aListElements.length; + + if(nListElements > 0) { + + var i = 0; + var bFirstList = true; + var oUL; + var oGroupTitle; + + do { + + oUL = this._aListElements[i]; + + if(oUL) { + + if(bFirstList) { + + Dom.addClass(oUL, "first-of-type"); + bFirstList = false; + + } + + if(!Dom.isAncestor(oEl, oUL)) { + + this.appendToBody(oUL); + + } + + oGroupTitle = this._aGroupTitleElements[i]; + + if(oGroupTitle) { + + if(!Dom.isAncestor(oEl, oGroupTitle)) { + + oUL.parentNode.insertBefore(oGroupTitle, oUL); + + } + + Dom.addClass(oUL, "hastitle"); + + } + + } + + i++; + + } + while(i < nListElements); + + } + +}, + +/** +* @method _onRender +* @description "render" event handler for the menu. +* @private +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that +* fired the event. +*/ +_onRender: function(p_sType, p_aArgs, p_oMenu) { + + if(this.cfg.getProperty("position") == "dynamic") { + + var sWidth = + this.element.parentNode.tagName.toUpperCase() == "BODY" ? + this.element.offsetWidth : this._getOffsetWidth(); + + this.cfg.setProperty("width", (sWidth + "px")); + + } + +}, + +/** +* @method _onBeforeShow +* @description "beforeshow" event handler for the menu. +* @private +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that +* fired the event. +*/ +_onBeforeShow: function(p_sType, p_aArgs, p_oMenu) { + + if(this.lazyLoad && this.getItemGroups().length === 0) { + + if(this.srcElement) { + + this._initSubTree(); + + } + + if(this.itemData) { + + if( + this.parent && this.parent.parent && + this.parent.parent.srcElement && + this.parent.parent.srcElement.tagName.toUpperCase() == "SELECT" + ) { + + var nOptions = this.itemData.length; + + for(var n=0; n0) { + + oConfig.showdelay = nShowDelay; + + } + + var nHideDelay = oParentMenu.cfg.getProperty("hidedelay"); + + if(nHideDelay > 0) { + + oConfig.hidedelay = nHideDelay; + + } + + /* + Only sync the "iframe" configuration property if the parent + menu's "position" configuration is the same. + */ + + if( + this.cfg.getProperty("position") == + oParentMenu.cfg.getProperty("position") + ) { + + oConfig.iframe = oParentMenu.cfg.getProperty("iframe"); + + } + + + p_oSubmenu.cfg.applyConfig(oConfig); + + if(!this.lazyLoad) { + + if(Dom.inDocument(this.element)) { + + this.render(); + + } + else { + + this.render(this.parent.element); + + } + + } + +}, + +/** +* @method _onSubmenuBeforeShow +* @description "beforeshow" event handler for a submenu. +* @private +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oSubmenu Object representing the submenu that +* subscribed to the event. +*/ +_onSubmenuBeforeShow: function(p_sType, p_aArgs, p_oSubmenu) { + + var oParent = this.parent; + var aAlignment = oParent.parent.cfg.getProperty("submenualignment"); + + this.cfg.setProperty( + "context", + [oParent.element, aAlignment[0], aAlignment[1]] + ); + + oParent.submenuIndicator.alt = oParent.EXPANDED_SUBMENU_INDICATOR_ALT_TEXT; + +}, + +/** +* @method _onSubmenuShow +* @description "show" event handler for a submenu. +* @private +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oSubmenu Object representing the submenu that +* subscribed to the event. +*/ +_onSubmenuShow: function(p_sType, p_aArgs, p_oSubmenu) { + + var oParent = this.parent; + + oParent.submenuIndicator.alt = oParent.EXPANDED_SUBMENU_INDICATOR_ALT_TEXT; + +}, + +/** +* @method _onSubmenuHide +* @description "hide" Custom Event handler for a submenu. +* @private +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oSubmenu Object representing the submenu that +* subscribed to the event. +*/ +_onSubmenuHide: function(p_sType, p_aArgs, p_oSubmenu) { + + var oParent = this.parent; + + oParent.submenuIndicator.alt = oParent.COLLAPSED_SUBMENU_INDICATOR_ALT_TEXT; + +}, + +/** +* @method _onMenuItemFocus +* @description "focus" event handler for the menu's items. +* @private +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item +* that fired the event. +*/ +_onMenuItemFocus: function(p_sType, p_aArgs, p_oItem) { + + this.activeItem = p_oItem; + +}, + +/** +* @method _onMenuItemBlur +* @description "blur" event handler for the menu's items. +* @private +* @param {String} p_sType String representing the name of the event +* that was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +*/ +_onMenuItemBlur: function(p_sType, p_aArgs) { + + this.activeItem = null; + +}, + +/** +* @method _onMenuItemConfigChange +* @description "configchange" event handler for the menu's items. +* @private +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item +* that fired the event. +*/ +_onMenuItemConfigChange: function(p_sType, p_aArgs, p_oItem) { + + var sProperty = p_aArgs[0][0]; + + switch(sProperty) { + + case "submenu": + + var oSubmenu = p_aArgs[0][1]; + + if(oSubmenu) { + + this._configureSubmenu(p_oItem); + + } + + break; + + case "text": + case "helptext": + + /* + A change to an item's "text" or "helptext" + configuration properties requires the width of the parent + menu to be recalculated. + */ + + if(this.element.style.width) { + + var sWidth = this._getOffsetWidth() + "px"; + + Dom.setStyle(this.element, "width", sWidth); + + } + + break; + + } + +}, + +// Public event handlers for configuration properties + +/** +* @method enforceConstraints +* @description The default event handler executed when the moveEvent is fired, +* if the "constraintoviewport" configuration property is set to true. +* @param {String} type The name of the event that was fired. +* @param {Array} args Collection of arguments sent when the +* event was fired. +* @param {Array} obj Array containing the current Menu instance +* and the item that fired the event. +*/ +enforceConstraints: function(type, args, obj) { + + var oConfig = this.cfg; + + var pos = args[0]; + + var x = pos[0]; + var y = pos[1]; + + var bod = document.getElementsByTagName('body')[0]; + var htm = document.getElementsByTagName('html')[0]; + + var bodyOverflow = Dom.getStyle(bod, "overflow"); + var htmOverflow = Dom.getStyle(htm, "overflow"); + + var offsetHeight = this.element.offsetHeight; + var offsetWidth = this.element.offsetWidth; + + var viewPortWidth = Dom.getClientWidth(); + var viewPortHeight = Dom.getClientHeight(); + + var scrollX = window.scrollX || document.body.scrollLeft; + var scrollY = window.scrollY || document.body.scrollTop; + + var topConstraint = scrollY + 10; + var leftConstraint = scrollX + 10; + var bottomConstraint = scrollY + viewPortHeight - offsetHeight - 10; + var rightConstraint = scrollX + viewPortWidth - offsetWidth - 10; + + var aContext = oConfig.getProperty("context"); + var oContextElement = aContext ? aContext[0] : null; + + + if (x < 10) { + + x = leftConstraint; + + } else if ((x + offsetWidth) > viewPortWidth) { + + if( + oContextElement && + ((x - oContextElement.offsetWidth) > offsetWidth) + ) { + + x = (x - (oContextElement.offsetWidth + offsetWidth)); + + } + else { + + x = rightConstraint; + + } + + } + + if (y < 10) { + + y = topConstraint; + + } else if (y > bottomConstraint) { + + if(oContextElement && (y > offsetHeight)) { + + y = ((y + oContextElement.offsetHeight) - offsetHeight); + + } + else { + + y = bottomConstraint; + + } + + } + + oConfig.setProperty("x", x, true); + oConfig.setProperty("y", y, true); + +}, + +/** +* @method configVisible +* @description Event handler for when the "visible" configuration property +* the menu changes. +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that +* fired the event. +*/ +configVisible: function(p_sType, p_aArgs, p_oMenu) { + + if(this.cfg.getProperty("position") == "dynamic") { + + YAHOO.widget.Menu.superclass.configVisible.call( + this, + p_sType, + p_aArgs, + p_oMenu + ); + + } + else { + + var bVisible = p_aArgs[0]; + var sDisplay = Dom.getStyle(this.element, "display"); + + if(bVisible) { + + if(sDisplay != "block") { + this.beforeShowEvent.fire(); + Dom.setStyle(this.element, "display", "block"); + this.showEvent.fire(); + } + + } + else { + + if(sDisplay == "block") { + this.beforeHideEvent.fire(); + Dom.setStyle(this.element, "display", "none"); + this.hideEvent.fire(); + } + + } + + } + +}, + +/** +* @method configPosition +* @description Event handler for when the "position" configuration property +* of the menu changes. +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that +* fired the event. +*/ +configPosition: function(p_sType, p_aArgs, p_oMenu) { + + var sCSSPosition = p_aArgs[0] == "static" ? "static" : "absolute"; + var oCfg = this.cfg; + + Dom.setStyle(this.element, "position", sCSSPosition); + + if(sCSSPosition == "static") { + + /* + Remove the iframe for statically positioned menus since it will + intercept mouse events. + */ + + oCfg.setProperty("iframe", false); + + // Statically positioned menus are visible by default + + Dom.setStyle(this.element, "display", "block"); + + oCfg.setProperty("visible", true); + + } + else { + + /* + Even though the "visible" property is queued to + "false" by default, we need to set the "visibility" property to + "hidden" since Overlay's "configVisible" implementation checks the + element's "visibility" style property before deciding whether + or not to show an Overlay instance. + */ + + Dom.setStyle(this.element, "visibility", "hidden"); + + } + + if(sCSSPosition == "absolute") { + + var nZIndex = oCfg.getProperty("zindex"); + + if(!nZIndex || nZIndex === 0) { + + nZIndex = this.parent ? + (this.parent.parent.cfg.getProperty("zindex") + 1) : 1; + + oCfg.setProperty("zindex", nZIndex); + + } + + } + +}, + +/** +* @method configIframe +* @description Event handler for when the "iframe" configuration property of +* the menu changes. +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that +* fired the event. +*/ +configIframe: function(p_sType, p_aArgs, p_oMenu) { + + if(this.cfg.getProperty("position") == "dynamic") { + + YAHOO.widget.Menu.superclass.configIframe.call( + this, + p_sType, + p_aArgs, + p_oMenu + ); + + } + +}, + +/** +* @method configHideDelay +* @description Event handler for when the "hidedelay" configuration property +* of the menu changes. +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that +* fired the event. +*/ +configHideDelay: function(p_sType, p_aArgs, p_oMenu) { + + var nHideDelay = p_aArgs[0]; + var oMouseOutEvent = this.mouseOutEvent; + var oMouseOverEvent = this.mouseOverEvent; + var oKeyDownEvent = this.keyDownEvent; + + if(nHideDelay > 0) { + + /* + Only assign event handlers once. This way the user change + the value for the hidedelay as many times as they want. + */ + + if(!this._hideDelayEventHandlersAssigned) { + + oMouseOutEvent.subscribe(this._execHideDelay, true); + oMouseOverEvent.subscribe(this._cancelHideDelay, this, true); + oKeyDownEvent.subscribe(this._cancelHideDelay, this, true); + + this._hideDelayEventHandlersAssigned = true; + + } + + } + else { + + oMouseOutEvent.unsubscribe(this._execHideDelay, this); + oMouseOverEvent.unsubscribe(this._cancelHideDelay, this); + oKeyDownEvent.unsubscribe(this._cancelHideDelay, this); + + this._hideDelayEventHandlersAssigned = false; + + } + +}, + +/** +* @method configContainer +* @description Event handler for when the "container" configuration property +of the menu changes. +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.Menu} p_oMenu Object representing the menu that +* fired the event. +*/ +configContainer: function(p_sType, p_aArgs, p_oMenu) { + + var oElement = p_aArgs[0]; + + if(typeof oElement == 'string') { + + this.cfg.setProperty( + "container", + document.getElementById(oElement), + true + ); + + } + +}, + +// Public methods + +/** +* Event handler called when the resize monitor element's "resize" evet is fired. +*/ +onDomResize: function(e, obj) { + + if(!this._handleResize) { + + this._handleResize = true; + return; + + } + + var oConfig = this.cfg; + + if(oConfig.getProperty("position") == "dynamic") { + + oConfig.setProperty("width", (this._getOffsetWidth() + "px")); + + } + + YAHOO.widget.Menu.superclass.onDomResize.call(this, e, obj); + +}, + +/** +* @method initEvents +* @description Initializes the custom events for the menu. +*/ +initEvents: function() { + + YAHOO.widget.Menu.superclass.initEvents.call(this); + + // Create custom events + + var CustomEvent = YAHOO.util.CustomEvent; + + this.mouseOverEvent = new CustomEvent("mouseOverEvent", this); + this.mouseOutEvent = new CustomEvent("mouseOutEvent", this); + this.mouseDownEvent = new CustomEvent("mouseDownEvent", this); + this.mouseUpEvent = new CustomEvent("mouseUpEvent", this); + this.clickEvent = new CustomEvent("clickEvent", this); + this.keyPressEvent = new CustomEvent("keyPressEvent", this); + this.keyDownEvent = new CustomEvent("keyDownEvent", this); + this.keyUpEvent = new CustomEvent("keyUpEvent", this); + this.itemAddedEvent = new CustomEvent("itemAddedEvent", this); + this.itemRemovedEvent = new CustomEvent("itemRemovedEvent", this); + +}, + +/** +* @method getRoot +* @description Finds the menu's root menu. +*/ +getRoot: function() { + + var oItem = this.parent; + + if(oItem) { + + var oParentMenu = oItem.parent; + + return oParentMenu ? oParentMenu.getRoot() : this; + + } + else { + + return this; + + } + +}, + +/** +* @method toString +* @description Returns a string representing the menu. +* @return {String} +*/ +toString: function() { + + return ("Menu " + this.id); + +}, + +/** +* @method setItemGroupTitle +* @description Sets the title of a group of menu items. +* @param {String} p_sGroupTitle String specifying the title of the group. +* @param {Number} p_nGroupIndex Optional. Number specifying the group to which +* the title belongs. +*/ +setItemGroupTitle: function(p_sGroupTitle, p_nGroupIndex) { + + if(typeof p_sGroupTitle == "string" && p_sGroupTitle.length > 0) { + + var nGroupIndex = typeof p_nGroupIndex == "number" ? p_nGroupIndex : 0; + var oTitle = this._aGroupTitleElements[nGroupIndex]; + + if(oTitle) { + + oTitle.innerHTML = p_sGroupTitle; + + } + else { + + oTitle = document.createElement(this.GROUP_TITLE_TAG_NAME); + + oTitle.innerHTML = p_sGroupTitle; + + this._aGroupTitleElements[nGroupIndex] = oTitle; + + } + + var i = this._aGroupTitleElements.length - 1; + var nFirstIndex; + + do { + + if(this._aGroupTitleElements[i]) { + + Dom.removeClass(this._aGroupTitleElements[i], "first-of-type"); + + nFirstIndex = i; + + } + + } + while(i--); + + if(nFirstIndex !== null) { + + Dom.addClass( + this._aGroupTitleElements[nFirstIndex], + "first-of-type" + ); + + } + + } + +}, + +/** +* @method addItem +* @description Appends an item to the menu. +* @param {YAHOO.widget.MenuItem} p_oItem Object reference for the MenuItem +* instance to be added to the menu. +* @param {String} p_oItem String specifying the text of the item to be added +* to the menu. +* @param {Object} p_oItem Object literal containing a set of menu item +* configuration properties. +* @param {Number} p_nGroupIndex Optional. Number indicating the group to +* which the item belongs. +* @return {YAHOO.widget.MenuItem} +*/ +addItem: function(p_oItem, p_nGroupIndex) { + + if(p_oItem) { + + return this._addItemToGroup(p_nGroupIndex, p_oItem); + + } + +}, + +/** +* @method addItems +* @description Adds an array of items to the menu. +* @param {Array} p_aItems Array of items to be added to the menu. The array +* can contain strings specifying the text for each item to be created, object +* literals specifying each of the menu item configuration properties, +* or MenuItem instances. +* @param {Number} p_nGroupIndex Optional. Number specifying the group to +* which the items belongs. +* @return {Array} +*/ +addItems: function(p_aItems, p_nGroupIndex) { + + function isArray(p_oValue) { + + return (typeof p_oValue == "object" && p_oValue.constructor == Array); + + } + + if(isArray(p_aItems)) { + + var nItems = p_aItems.length; + var aItems = []; + var oItem; + + for(var i=0; i <div> element +* (and accompanying child nodes) from the document. +*/ +destroy: function() { + + // Remove Custom Event listeners + + this.mouseOverEvent.unsubscribeAll(); + this.mouseOutEvent.unsubscribeAll(); + this.mouseDownEvent.unsubscribeAll(); + this.mouseUpEvent.unsubscribeAll(); + this.clickEvent.unsubscribeAll(); + this.keyPressEvent.unsubscribeAll(); + this.keyDownEvent.unsubscribeAll(); + this.keyUpEvent.unsubscribeAll(); + + var nItemGroups = this._aItemGroups.length; + var nItems; + var oItemGroup; + var oItem; + var i; + var n; + + // Remove all items + + if(nItemGroups > 0) { + + i = nItemGroups - 1; + + do { + + oItemGroup = this._aItemGroups[i]; + + if(oItemGroup) { + + nItems = oItemGroup.length; + + if(nItems > 0) { + + n = nItems - 1; + + do { + + oItem = this._aItemGroups[i][n]; + + if(oItem) { + + oItem.destroy(); + } + + } + while(n--); + + } + + } + + } + while(i--); + + } + + // Continue with the superclass implementation of this method + + YAHOO.widget.Menu.superclass.destroy.call(this); + + +}, + +/** +* @method setInitialFocus +* @description Sets focus to the menu's first enabled item. +*/ +setInitialFocus: function() { + + var oItem = this._getFirstEnabledItem(); + + if(oItem) { + + oItem.focus(); + } + +}, + +/** +* @method setInitialSelection +* @description Sets the "selected" configuration property of the menu's first +* enabled item to "true." +*/ +setInitialSelection: function() { + + var oItem = this._getFirstEnabledItem(); + + if(oItem) { + + oItem.cfg.setProperty("selected", true); + } + +}, + +/** +* @method clearActiveItem +* @description Sets the "selected" configuration property of the menu's active +* item to "false" and hides the item's submenu. +* @param {Boolean} p_bBlur Boolean indicating if the menu's active item +* should be blurred. +*/ +clearActiveItem: function(p_bBlur) { + + if(this.cfg.getProperty("showdelay") > 0) { + + this._cancelShowDelay(); + + } + + var oActiveItem = this.activeItem; + + if(oActiveItem) { + + var oConfig = oActiveItem.cfg; + + oConfig.setProperty("selected", false); + + var oSubmenu = oConfig.getProperty("submenu"); + + if(oSubmenu) { + + oSubmenu.hide(); + + } + + if(p_bBlur) { + + oActiveItem.blur(); + + } + + } + +}, + +/** +* @description Initializes the class's configurable properties which can be +* changed using the menu's Config object ("cfg"). +* @method initDefaultConfig +*/ +initDefaultConfig: function() { + + YAHOO.widget.Menu.superclass.initDefaultConfig.call(this); + + var oConfig = this.cfg; + + // Add configuration properties + + /* + Change the default value for the "visible" configuration + property to "false" by re-adding the property. + */ + + /** + * @config visible + * @description Boolean indicating whether or not the menu is visible. If + * the menu's "position" configuration property is set to "dynamic" (the + * default), this property toggles the menu's <div>
+ * element's "visibility" style property between "visible" (true) or + * "hidden" (false). If the menu's "position" configuration property is + * set to "static" this property toggles the menu's + *<div>
element's "display" style property + * between "block" (true) or "none" (false). + * @default false + * @type Boolean + */ + oConfig.addProperty( + "visible", + { + value:false, + handler:this.configVisible, + validator:this.cfg.checkBoolean + } + ); + + /* + Change the default value for the "constraintoviewport" configuration + property to "true" by re-adding the property. + */ + + /** + * @config constraintoviewport + * @description Boolean indicating if the menu will try to remain inside + * the boundaries of the size of viewport. + * @default true + * @type Boolean + */ + oConfig.addProperty( + "constraintoviewport", + { + value:true, + handler:this.configConstrainToViewport, + validator:this.cfg.checkBoolean, + supercedes:["iframe","x","y","xy"] + } + ); + + /** + * @config position + * @description String indicating how a menu should be positioned on the + * screen. Possible values are "static" and "dynamic." Static menus are + * visible by default and reside in the normal flow of the document + * (CSS position: static). Dynamic menus are hidden by default, reside + * out of the normal flow of the document (CSS position: absolute), and + * can overlay other elements on the screen. + * @default dynamic + * @type String + */ + oConfig.addProperty( + "position", + { + value: "dynamic", + handler: this.configPosition, + validator: this._checkPosition, + supercedes: ["visible"] + } + ); + + /** + * @config submenualignment + * @description Array defining how submenus should be aligned to their + * parent menu item. The format is: [itemCorner, submenuCorner]. By default + * a submenu's top left corner is aligned to its parent menu item's top + * right corner. + * @default ["tl","tr"] + * @type Array + */ + oConfig.addProperty("submenualignment", { value: ["tl","tr"] } ); + + /** + * @config autosubmenudisplay + * @description Boolean indicating if submenus are automatically made + * visible when the user mouses over the menu's items. + * @default true + * @type Boolean + */ + oConfig.addProperty( + "autosubmenudisplay", + { + value: true, + validator: oConfig.checkBoolean + } + ); + + /** + * @config showdelay + * @description Number indicating the time (in milliseconds) that should + * expire before a submenu is made visible when the user mouses over + * the menu's items. + * @default 0 + * @type Number + */ + oConfig.addProperty( + "showdelay", + { + value: 0, + validator: oConfig.checkNumber + } + ); + + /** + * @config hidedelay + * @description Number indicating the time (in milliseconds) that should + * expire before the menu is hidden. + * @default 0 + * @type Number + */ + oConfig.addProperty( + "hidedelay", + { + value: 0, + validator: oConfig.checkNumber, + handler: this.configHideDelay, + suppressEvent: true + } + ); + + /** + * @config clicktohide + * @description Boolean indicating if the menu will automatically be + * hidden if the user clicks outside of it. + * @default true + * @type Boolean + */ + oConfig.addProperty( + "clicktohide", + { + value: true, + validator: oConfig.checkBoolean + } + ); + + /** + * @config container + * @description HTML element reference or string specifying the id + * attribute of the HTML element that the menu's markup should be rendered into. + * @type HTMLElement|String + * @default document.body + */ + this.cfg.addProperty( + "container", + { value:document.body, handler:this.configContainer } + ); + +} + +}); // END YAHOO.extend + +})(); + +/** +* The base class for all menuing containers. +* +* @param {String} p_oElement String specifying the id attribute of the +*<div>
element of the menu module. +* @param {String} p_oElement String specifying the id attribute of the +*<select>
element to be used as the data source for the +* menu module. +* @param {HTMLDivElement} p_oElement Object +* specifying the<div>
element of the menu module. +* @param {HTMLSelectElement} p_oElement Object +* specifying the<select>
element to be used as the data +* source for the menu module. +* @param {Object} p_oConfig Optional. Object literal specifying the +* configuration for the menu module. See configuration class documentation for +* more details. +* @class MenuModule +* @constructor +* @extends YAHOO.widget.Overlay +* @deprecated As of version 0.12, all MenuModule functionality has been +* implemented directly in YAHOO.widget.Menu, making YAHOO.widget.Menu the base +* class for all menuing containers. +*/ +YAHOO.widget.MenuModule = YAHOO.widget.Menu; + +(function() { + +var Dom = YAHOO.util.Dom; +var Module = YAHOO.widget.Module; +var Menu = YAHOO.widget.Menu; + +/** +* Creates an item for a menu. +* +* @param {String} p_oObject String specifying the text of the menu item. +* @param {HTMLLIElement} p_oObject Object specifying +* the<li>
element of the menu item. +* @param {HTMLOptGroupElement} p_oObject Object +* specifying the<optgroup>
element of the menu item. +* @param {HTMLOptionElement} p_oObject Object +* specifying the<option>
element of the menu item. +* @param {Object} p_oConfig Optional. Object literal specifying the +* configuration for the menu item. See configuration class documentation +* for more details. +* @class MenuItem +* @constructor +*/ +YAHOO.widget.MenuItem = function(p_oObject, p_oConfig) { + + if(p_oObject) { + + if(p_oConfig) { + + this.parent = p_oConfig.parent; + this.value = p_oConfig.value; + + } + + this.init(p_oObject, p_oConfig); + + } + +}; + +YAHOO.widget.MenuItem.prototype = { + + // Constants + + /** + * @property SUBMENU_INDICATOR_IMAGE_PATH + * @description String representing the path to the image to be used for the + * menu item's submenu arrow indicator. + * @default "nt/ic/ut/alt1/menuarorght8_nrm_1.gif" + * @final + * @type String + */ + SUBMENU_INDICATOR_IMAGE_PATH: "nt/ic/ut/alt1/menuarorght8_nrm_1.gif", + + /** + * @property SELECTED_SUBMENU_INDICATOR_IMAGE_PATH + * @description String representing the path to the image to be used for the + * submenu arrow indicator when the menu item is selected. + * @default "nt/ic/ut/alt1/menuarorght8_hov_1.gif" + * @final + * @type String + */ + SELECTED_SUBMENU_INDICATOR_IMAGE_PATH: + "nt/ic/ut/alt1/menuarorght8_hov_1.gif", + + /** + * @property DISABLED_SUBMENU_INDICATOR_IMAGE_PATH + * @description String representing the path to the image to be used for the + * submenu arrow indicator when the menu item is disabled. + * @default "nt/ic/ut/alt1/menuarorght8_dim_1.gif" + * @final + * @type String + */ + DISABLED_SUBMENU_INDICATOR_IMAGE_PATH: + "nt/ic/ut/alt1/menuarorght8_dim_1.gif", + + /** + * @property COLLAPSED_SUBMENU_INDICATOR_ALT_TEXT + * @description String representing the alt text for the image to be used + * for the submenu arrow indicator. + * @default "Collapsed. Click to expand." + * @final + * @type String + */ + COLLAPSED_SUBMENU_INDICATOR_ALT_TEXT: "Collapsed. Click to expand.", + + /** + * @property EXPANDED_SUBMENU_INDICATOR_ALT_TEXT + * @description String representing the alt text for the image to be used + * for the submenu arrow indicator when the submenu is visible. + * @default "Expanded. Click to collapse." + * @final + * @type String + */ + EXPANDED_SUBMENU_INDICATOR_ALT_TEXT: "Expanded. Click to collapse.", + + /** + * @property DISABLED_SUBMENU_INDICATOR_ALT_TEXT + * @description String representing the alt text for the image to be used + * for the submenu arrow indicator when the menu item is disabled. + * @default "Disabled." + * @final + * @type String + */ + DISABLED_SUBMENU_INDICATOR_ALT_TEXT: "Disabled.", + + /** + * @property CHECKED_IMAGE_PATH + * @description String representing the path to the image to be used for + * the checked state. + * @default "nt/ic/ut/bsc/menuchk8_nrm_1.gif" + * @final + * @type String + */ + CHECKED_IMAGE_PATH: "nt/ic/ut/bsc/menuchk8_nrm_1.gif", + + + /** + * @property SELECTED_CHECKED_IMAGE_PATH + * @description String representing the path to the image to be used for + * the selected checked state. + * @default "nt/ic/ut/bsc/menuchk8_hov_1.gif" + * @final + * @type String + */ + SELECTED_CHECKED_IMAGE_PATH: "nt/ic/ut/bsc/menuchk8_hov_1.gif", + + + /** + * @property DISABLED_CHECKED_IMAGE_PATH + * @description String representing the path to the image to be used for + * the disabled checked state. + * @default "nt/ic/ut/bsc/menuchk8_dim_1.gif" + * @final + * @type String + */ + DISABLED_CHECKED_IMAGE_PATH: "nt/ic/ut/bsc/menuchk8_dim_1.gif", + + + /** + * @property CHECKED_IMAGE_ALT_TEXT + * @description String representing the alt text for the image to be used + * for the checked image. + * @default "Checked." + * @final + * @type String + */ + CHECKED_IMAGE_ALT_TEXT: "Checked.", + + + /** + * @property DISABLED_CHECKED_IMAGE_ALT_TEXT + * @description String representing the alt text for the image to be used + * for the checked image when the item is disabled. + * @default "Checked. (Item disabled.)" + * @final + * @type String + */ + DISABLED_CHECKED_IMAGE_ALT_TEXT: "Checked. (Item disabled.)", + + /** + * @property CSS_CLASS_NAME + * @description String representing the CSS class(es) to be applied to the + *<li>
element of the menu item. + * @default "yuimenuitem" + * @final + * @type String + */ + CSS_CLASS_NAME: "yuimenuitem", + + /** + * @property SUBMENU_TYPE + * @description Object representing the type of menu to instantiate and + * add when parsing the child nodes of the menu item's source HTML element. + * @final + * @type YAHOO.widget.Menu + */ + SUBMENU_TYPE: null, + + /** + * @property IMG_ROOT + * @description String representing the prefix path to use for + * non-secure images. + * @default "http://us.i1.yimg.com/us.yimg.com/i/" + * @type String + */ + IMG_ROOT: "http://us.i1.yimg.com/us.yimg.com/i/", + + + /** + * @property IMG_ROOT_SSL + * @description String representing the prefix path to use for securely + * served images. + * @default "https://a248.e.akamai.net/sec.yimg.com/i/" + * @type String + */ + IMG_ROOT_SSL: "https://a248.e.akamai.net/sec.yimg.com/i/", + + // Private member variables + + /** + * @property _oAnchor + * @description Object reference to the menu item's + *<a>
element. + * @default null + * @private + * @type HTMLAnchorElement + */ + _oAnchor: null, + + + /** + * @property _oText + * @description Object reference to the menu item's text node. + * @default null + * @private + * @type TextNode + */ + _oText: null, + + + /** + * @property _oHelpTextEM + * @description Object reference to the menu item's help text + *<em>
element. + * @default null + * @private + * @type HTMLElement + */ + _oHelpTextEM: null, + + + /** + * @property _oSubmenu + * @description Object reference to the menu item's submenu. + * @default null + * @private + * @type YAHOO.widget.Menu + */ + _oSubmenu: null, + + /** + * @property _checkImage + * @description Object reference to the menu item's checkmark image. + * @default null + * @private + * @type HTMLImageElement + */ + _checkImage: null, + + // Public properties + + /** + * @property constructor + * @description Object reference to the menu item's constructor function. + * @default YAHOO.widget.MenuItem + * @type YAHOO.widget.MenuItem + */ + constructor: YAHOO.widget.MenuItem, + + /** + * @property imageRoot + * @description String representing the root path for all of the menu + * item's images. + * @type String + */ + imageRoot: null, + + /** + * @property isSecure + * @description Boolean representing whether or not the current browsing + * context is secure (HTTPS). + * @type Boolean + */ + isSecure: Module.prototype.isSecure, + + /** + * @property index + * @description Number indicating the ordinal position of the menu item in + * its group. + * @default null + * @type Number + */ + index: null, + + /** + * @property groupIndex + * @description Number indicating the index of the group to which the menu + * item belongs. + * @default null + * @type Number + */ + groupIndex: null, + + /** + * @property parent + * @description Object reference to the menu item's parent menu. + * @default null + * @type YAHOO.widget.Menu + */ + parent: null, + + /** + * @property element + * @description Object reference to the menu item's + *<li>
element. + * @default HTMLLIElement + * @type HTMLLIElement + */ + element: null, + + /** + * @property srcElement + * @description Object reference to the HTML element (either + *<li>
,<optgroup>
or + *<option>
) used create the menu item. + * @default HTMLLIElement|HTMLOptGroupElement|HTMLOptionElement + * @type HTMLLIElement| + * HTMLOptGroupElement|HTMLOptionElement + */ + srcElement: null, + + /** + * @property value + * @description Object reference to the menu item's value. + * @default null + * @type Object + */ + value: null, + + /** + * @property submenuIndicator + * @description Object reference to the<img>
element + * used to create the submenu indicator for the menu item. + * @default HTMLImageElement + * @type HTMLImageElement + */ + submenuIndicator: null, + + /** + * @property browser + * @description String representing the browser. + * @type String + */ + browser: Module.prototype.browser, + + // Events + + /** + * @event destroyEvent + * @description Fires when the menu item's<li>
+ * element is removed from its parent<ul>
element. + * @type YAHOO.util.CustomEvent + */ + destroyEvent: null, + + /** + * @event mouseOverEvent + * @description Fires when the mouse has entered the menu item. Passes + * back the DOM Event object as an argument. + * @type YAHOO.util.CustomEvent + */ + mouseOverEvent: null, + + /** + * @event mouseOutEvent + * @description Fires when the mouse has left the menu item. Passes back + * the DOM Event object as an argument. + * @type YAHOO.util.CustomEvent + */ + mouseOutEvent: null, + + /** + * @event mouseDownEvent + * @description Fires when the user mouses down on the menu item. Passes + * back the DOM Event object as an argument. + * @type YAHOO.util.CustomEvent + */ + mouseDownEvent: null, + + /** + * @event mouseUpEvent + * @description Fires when the user releases a mouse button while the mouse + * is over the menu item. Passes back the DOM Event object as an argument. + * @type YAHOO.util.CustomEvent + */ + mouseUpEvent: null, + + /** + * @event clickEvent + * @description Fires when the user clicks the on the menu item. Passes + * back the DOM Event object as an argument. + * @type YAHOO.util.CustomEvent + */ + clickEvent: null, + + /** + * @event keyPressEvent + * @description Fires when the user presses an alphanumeric key when the + * menu item has focus. Passes back the DOM Event object as an argument. + * @type YAHOO.util.CustomEvent + */ + keyPressEvent: null, + + /** + * @event keyDownEvent + * @description Fires when the user presses a key when the menu item has + * focus. Passes back the DOM Event object as an argument. + * @type YAHOO.util.CustomEvent + */ + keyDownEvent: null, + + /** + * @event keyUpEvent + * @description Fires when the user releases a key when the menu item has + * focus. Passes back the DOM Event object as an argument. + * @type YAHOO.util.CustomEvent + */ + keyUpEvent: null, + + /** + * @event focusEvent + * @description Fires when the menu item receives focus. + * @type YAHOO.util.CustomEvent + */ + focusEvent: null, + + /** + * @event blurEvent + * @description Fires when the menu item loses the input focus. + * @type YAHOO.util.CustomEvent + */ + blurEvent: null, + + /** + * @method init + * @description The MenuItem class's initialization method. This method is + * automatically called by the constructor, and sets up all DOM references + * for pre-existing markup, and creates required markup if it is not + * already present. + * @param {String} p_oObject String specifying the text of the menu item. + * @param {HTMLLIElement} p_oObject Object specifying + * the<li>
element of the menu item. + * @param {HTMLOptGroupElement} p_oObject Object + * specifying the<optgroup>
element of the menu item. + * @param {HTMLOptionElement} p_oObject Object + * specifying the<option>
element of the menu item. + * @param {Object} p_oConfig Optional. Object literal specifying the + * configuration for the menu item. See configuration class documentation + * for more details. + */ + init: function(p_oObject, p_oConfig) { + + this.imageRoot = (this.isSecure) ? this.IMG_ROOT_SSL : this.IMG_ROOT; + + if(!this.SUBMENU_TYPE) { + + this.SUBMENU_TYPE = Menu; + + } + + // Create the config object + + this.cfg = new YAHOO.util.Config(this); + + this.initDefaultConfig(); + + var oConfig = this.cfg; + + if(this._checkString(p_oObject)) { + + this._createRootNodeStructure(); + + oConfig.setProperty("text", p_oObject); + + } + else if(this._checkDOMNode(p_oObject)) { + + switch(p_oObject.tagName.toUpperCase()) { + + case "OPTION": + + this._createRootNodeStructure(); + + oConfig.setProperty("text", p_oObject.text); + + this.srcElement = p_oObject; + + break; + + case "OPTGROUP": + + this._createRootNodeStructure(); + + oConfig.setProperty("text", p_oObject.label); + + this.srcElement = p_oObject; + + this._initSubTree(); + + break; + + case "LI": + + // Get the anchor node (if it exists) + + var oAnchor = this._getFirstElement(p_oObject, "A"); + var sURL = "#"; + var sTarget = null; + var sText = null; + + // Capture the "text" and/or the "URL" + + if(oAnchor) { + + sURL = oAnchor.getAttribute("href"); + sTarget = oAnchor.getAttribute("target"); + + if(oAnchor.innerText) { + + sText = oAnchor.innerText; + + } + else { + + var oRange = oAnchor.ownerDocument.createRange(); + + oRange.selectNodeContents(oAnchor); + + sText = oRange.toString(); + + } + + } + else { + + var oText = p_oObject.firstChild; + + sText = oText.nodeValue; + + oAnchor = document.createElement("a"); + + oAnchor.setAttribute("href", sURL); + + p_oObject.replaceChild(oAnchor, oText); + + oAnchor.appendChild(oText); + + } + + this.srcElement = p_oObject; + this.element = p_oObject; + this._oAnchor = oAnchor; + + + // Check if emphasis has been applied to the MenuItem + + var oEmphasisNode = this._getFirstElement(oAnchor); + var bEmphasis = false; + var bStrongEmphasis = false; + + if(oEmphasisNode) { + + // Set a reference to the text node + + this._oText = oEmphasisNode.firstChild; + + switch(oEmphasisNode.tagName.toUpperCase()) { + + case "EM": + + bEmphasis = true; + + break; + + case "STRONG": + + bStrongEmphasis = true; + + break; + + } + + } + else { + + // Set a reference to the text node + + this._oText = oAnchor.firstChild; + + } + + /* + Set these properties silently to sync up the + configuration object without making changes to the + element's DOM + */ + + oConfig.setProperty("text", sText, true); + oConfig.setProperty("url", sURL, true); + oConfig.setProperty("target", sTarget, true); + oConfig.setProperty("emphasis", bEmphasis, true); + oConfig.setProperty( + "strongemphasis", + bStrongEmphasis, + true + ); + + this._initSubTree(); + + break; + + } + + } + + if(this.element) { + + Dom.addClass(this.element, this.CSS_CLASS_NAME); + + // Create custom events + + var CustomEvent = YAHOO.util.CustomEvent; + + this.destroyEvent = new CustomEvent("destroyEvent", this); + this.mouseOverEvent = new CustomEvent("mouseOverEvent", this); + this.mouseOutEvent = new CustomEvent("mouseOutEvent", this); + this.mouseDownEvent = new CustomEvent("mouseDownEvent", this); + this.mouseUpEvent = new CustomEvent("mouseUpEvent", this); + this.clickEvent = new CustomEvent("clickEvent", this); + this.keyPressEvent = new CustomEvent("keyPressEvent", this); + this.keyDownEvent = new CustomEvent("keyDownEvent", this); + this.keyUpEvent = new CustomEvent("keyUpEvent", this); + this.focusEvent = new CustomEvent("focusEvent", this); + this.blurEvent = new CustomEvent("blurEvent", this); + + if(p_oConfig) { + + oConfig.applyConfig(p_oConfig); + + } + + oConfig.fireQueue(); + + } + + }, + + // Private methods + + /** + * @method _getFirstElement + * @description Returns an HTML element's first HTML element node. + * @private + * @param {HTMLElement} p_oElement Object + * reference specifying the element to be evaluated. + * @param {String} p_sTagName Optional. String specifying the tagname of + * the element to be retrieved. + * @return {HTMLElement} + */ + _getFirstElement: function(p_oElement, p_sTagName) { + + var oElement; + + if(p_oElement.firstChild && p_oElement.firstChild.nodeType == 1) { + + oElement = p_oElement.firstChild; + + } + else if( + p_oElement.firstChild && + p_oElement.firstChild.nextSibling && + p_oElement.firstChild.nextSibling.nodeType == 1 + ) { + + oElement = p_oElement.firstChild.nextSibling; + + } + + if(p_sTagName) { + + return (oElement && oElement.tagName.toUpperCase() == p_sTagName) ? + oElement : false; + + } + + return oElement; + + }, + + /** + * @method _checkString + * @description Determines if an object is a string. + * @private + * @param {Object} p_oObject Object to be evaluated. + * @return {Boolean} + */ + _checkString: function(p_oObject) { + + return (typeof p_oObject == "string"); + + }, + + /** + * @method _checkDOMNode + * @description Determines if an object is an HTML element. + * @private + * @param {Object} p_oObject Object to be evaluated. + * @return {Boolean} + */ + _checkDOMNode: function(p_oObject) { + + return (p_oObject && p_oObject.tagName); + + }, + + /** + * @method _createRootNodeStructure + * @description Creates the core DOM structure for the menu item. + * @private + */ + _createRootNodeStructure: function () { + + this.element = document.createElement("li"); + + this._oText = document.createTextNode(""); + + this._oAnchor = document.createElement("a"); + this._oAnchor.appendChild(this._oText); + + this.cfg.refireEvent("url"); + + this.element.appendChild(this._oAnchor); + + }, + + /** + * @method _initSubTree + * @description Iterates the source element's childNodes collection and uses + * the child nodes to instantiate other menus. + * @private + */ + _initSubTree: function() { + + var oSrcEl = this.srcElement; + var oConfig = this.cfg; + + if(oSrcEl.childNodes.length > 0) { + + if( + this.parent.lazyLoad && + this.parent.srcElement && + this.parent.srcElement.tagName.toUpperCase() == "SELECT" + ) { + + oConfig.setProperty( + "submenu", + { id: Dom.generateId(), itemdata: oSrcEl.childNodes } + ); + + } + else { + + var oNode = oSrcEl.firstChild; + var aOptions = []; + + do { + + if(oNode && oNode.tagName) { + + switch(oNode.tagName.toUpperCase()) { + + case "DIV": + + oConfig.setProperty("submenu", oNode); + + break; + + case "OPTION": + + aOptions[aOptions.length] = oNode; + + break; + + } + + } + + } + while((oNode = oNode.nextSibling)); + + + var nOptions = aOptions.length; + + if(nOptions > 0) { + + var oMenu = new this.SUBMENU_TYPE(Dom.generateId()); + + oConfig.setProperty("submenu", oMenu); + + for(var n=0; n0) { + + oAnchor.setAttribute("target", sTarget); + + } + else { + + oAnchor.removeAttribute("target"); + + } + + }, + + /** + * @method configEmphasis + * @description Event handler for when the "emphasis" configuration property + * of the menu item changes. + * @param {String} p_sType String representing the name of the event that + * was fired. + * @param {Array} p_aArgs Array of arguments sent when the event was fired. + * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item + * that fired the event. + */ + configEmphasis: function(p_sType, p_aArgs, p_oItem) { + + var bEmphasis = p_aArgs[0]; + var oAnchor = this._oAnchor; + var oText = this._oText; + var oConfig = this.cfg; + var oEM; + + if(bEmphasis && oConfig.getProperty("strongemphasis")) { + + oConfig.setProperty("strongemphasis", false); + + } + + if(oAnchor) { + + if(bEmphasis) { + + oEM = document.createElement("em"); + oEM.appendChild(oText); + + oAnchor.appendChild(oEM); + + } + else { + + oEM = this._getFirstElement(oAnchor, "EM"); + + oAnchor.removeChild(oEM); + oAnchor.appendChild(oText); + + } + + } + + }, + + /** + * @method configStrongEmphasis + * @description Event handler for when the "strongemphasis" configuration + * property of the menu item changes. + * @param {String} p_sType String representing the name of the event that + * was fired. + * @param {Array} p_aArgs Array of arguments sent when the event was fired. + * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item + * that fired the event. + */ + configStrongEmphasis: function(p_sType, p_aArgs, p_oItem) { + + var bStrongEmphasis = p_aArgs[0]; + var oAnchor = this._oAnchor; + var oText = this._oText; + var oConfig = this.cfg; + var oStrong; + + if(bStrongEmphasis && oConfig.getProperty("emphasis")) { + + oConfig.setProperty("emphasis", false); + + } + + if(oAnchor) { + + if(bStrongEmphasis) { + + oStrong = document.createElement("strong"); + oStrong.appendChild(oText); + + oAnchor.appendChild(oStrong); + + } + else { + + oStrong = this._getFirstElement(oAnchor, "STRONG"); + + oAnchor.removeChild(oStrong); + oAnchor.appendChild(oText); + + } + + } + + }, + + /** + * @method configChecked + * @description Event handler for when the "checked" configuration property + * of the menu item changes. + * @param {String} p_sType String representing the name of the event that + * was fired. + * @param {Array} p_aArgs Array of arguments sent when the event was fired. + * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item + * that fired the event. + */ + configChecked: function(p_sType, p_aArgs, p_oItem) { + + var bChecked = p_aArgs[0]; + var oEl = this.element; + var oConfig = this.cfg; + var oImg; + + + if(bChecked) { + + this._preloadImage(this.CHECKED_IMAGE_PATH); + this._preloadImage(this.SELECTED_CHECKED_IMAGE_PATH); + this._preloadImage(this.DISABLED_CHECKED_IMAGE_PATH); + + oImg = document.createElement("img"); + oImg.src = (this.imageRoot + this.CHECKED_IMAGE_PATH); + oImg.alt = this.CHECKED_IMAGE_ALT_TEXT; + + var oSubmenu = this.cfg.getProperty("submenu"); + + if(oSubmenu) { + + oEl.insertBefore(oImg, oSubmenu.element); + + } + else { + + oEl.appendChild(oImg); + + } + + Dom.addClass([oEl, oImg], "checked"); + + this._checkImage = oImg; + + if(oConfig.getProperty("disabled")) { + + oConfig.refireEvent("disabled"); + + } + + if(oConfig.getProperty("selected")) { + + oConfig.refireEvent("selected"); + + } + + } + else { + + oImg = this._checkImage; + + Dom.removeClass([oEl, oImg], "checked"); + + if(oImg) { + + oEl.removeChild(oImg); + + } + + this._checkImage = null; + + } + + }, + + /** + * @method configDisabled + * @description Event handler for when the "disabled" configuration property + * of the menu item changes. + * @param {String} p_sType String representing the name of the event that + * was fired. + * @param {Array} p_aArgs Array of arguments sent when the event was fired. + * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item + * that fired the event. + */ + configDisabled: function(p_sType, p_aArgs, p_oItem) { + + var bDisabled = p_aArgs[0]; + var oAnchor = this._oAnchor; + var aNodes = [this.element, oAnchor]; + var oEM = this._oHelpTextEM; + var oConfig = this.cfg; + var oImg; + var sImgSrc; + var sImgAlt; + + if(oEM) { + + aNodes[2] = oEM; + + } + + if(this.cfg.getProperty("checked")) { + + sImgAlt = this.CHECKED_IMAGE_ALT_TEXT; + sImgSrc = this.CHECKED_IMAGE_PATH; + oImg = this._checkImage; + + if(bDisabled) { + + sImgAlt = this.DISABLED_CHECKED_IMAGE_ALT_TEXT; + sImgSrc = this.DISABLED_CHECKED_IMAGE_PATH; + + } + + oImg.src = document.images[(this.imageRoot + sImgSrc)].src; + oImg.alt = sImgAlt; + + } + + oImg = this.submenuIndicator; + + if(bDisabled) { + + if(oConfig.getProperty("selected")) { + + oConfig.setProperty("selected", false); + + } + + oAnchor.removeAttribute("href"); + + Dom.addClass(aNodes, "disabled"); + + sImgSrc = this.DISABLED_SUBMENU_INDICATOR_IMAGE_PATH; + sImgAlt = this.DISABLED_SUBMENU_INDICATOR_ALT_TEXT; + + } + else { + + oAnchor.setAttribute("href", oConfig.getProperty("url")); + + Dom.removeClass(aNodes, "disabled"); + + sImgSrc = this.SUBMENU_INDICATOR_IMAGE_PATH; + sImgAlt = this.COLLAPSED_SUBMENU_INDICATOR_ALT_TEXT; + + } + + if(oImg) { + + oImg.src = this.imageRoot + sImgSrc; + oImg.alt = sImgAlt; + + } + + }, + + /** + * @method configSelected + * @description Event handler for when the "selected" configuration property + * of the menu item changes. + * @param {String} p_sType String representing the name of the event that + * was fired. + * @param {Array} p_aArgs Array of arguments sent when the event was fired. + * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item + * that fired the event. + */ + configSelected: function(p_sType, p_aArgs, p_oItem) { + + if(!this.cfg.getProperty("disabled")) { + + var bSelected = p_aArgs[0]; + var oEM = this._oHelpTextEM; + var aNodes = [this.element, this._oAnchor]; + var oImg = this.submenuIndicator; + var sImgSrc; + + if(oEM) { + + aNodes[aNodes.length] = oEM; + + } + + if(oImg) { + + aNodes[aNodes.length] = oImg; + + } + + + if(this.cfg.getProperty("checked")) { + + sImgSrc = this.imageRoot + (bSelected ? + this.SELECTED_CHECKED_IMAGE_PATH : this.CHECKED_IMAGE_PATH); + + this._checkImage.src = document.images[sImgSrc].src; + + } + + if(bSelected) { + + Dom.addClass(aNodes, "selected"); + sImgSrc = this.SELECTED_SUBMENU_INDICATOR_IMAGE_PATH; + + } + else { + + Dom.removeClass(aNodes, "selected"); + sImgSrc = this.SUBMENU_INDICATOR_IMAGE_PATH; + + } + + if(oImg) { + + oImg.src = document.images[(this.imageRoot + sImgSrc)].src; + + } + + } + + }, + + /** + * @method configSubmenu + * @description Event handler for when the "submenu" configuration property + * of the menu item changes. + * @param {String} p_sType String representing the name of the event that + * was fired. + * @param {Array} p_aArgs Array of arguments sent when the event was fired. + * @param {YAHOO.widget.MenuItem} p_oItem Object representing the menu item + * that fired the event. + */ + configSubmenu: function(p_sType, p_aArgs, p_oItem) { + + var oEl = this.element; + var oSubmenu = p_aArgs[0]; + var oImg = this.submenuIndicator; + var oConfig = this.cfg; + var aNodes = [this.element, this._oAnchor]; + var oMenu; + var bLazyLoad = this.parent && this.parent.lazyLoad; + + if(oSubmenu) { + + if(oSubmenu instanceof Menu) { + + oMenu = oSubmenu; + oMenu.parent = this; + oMenu.lazyLoad = bLazyLoad; + + } + else if( + typeof oSubmenu == "object" && + oSubmenu.id && + !oSubmenu.nodeType + ) { + + var sSubmenuId = oSubmenu.id; + var oSubmenuConfig = oSubmenu; + + delete oSubmenu["id"]; + + oSubmenuConfig.lazyload = bLazyLoad; + oSubmenuConfig.parent = this; + + oMenu = new this.SUBMENU_TYPE(sSubmenuId, oSubmenuConfig); + + // Set the value of the property to the Menu instance + + this.cfg.setProperty("submenu", oMenu, true); + + } + else { + + oMenu = new this.SUBMENU_TYPE( + oSubmenu, + { lazyload: bLazyLoad, parent: this } + ); + + // Set the value of the property to the Menu instance + + this.cfg.setProperty("submenu", oMenu, true); + + } + + if(oMenu) { + + this._oSubmenu = oMenu; + + if(!oImg) { + + this._preloadImage(this.SUBMENU_INDICATOR_IMAGE_PATH); + this._preloadImage( + this.SELECTED_SUBMENU_INDICATOR_IMAGE_PATH + ); + + this._preloadImage( + this.DISABLED_SUBMENU_INDICATOR_IMAGE_PATH + ); + + oImg = document.createElement("img"); + + oImg.src = + (this.imageRoot + this.SUBMENU_INDICATOR_IMAGE_PATH); + + oImg.alt = this.COLLAPSED_SUBMENU_INDICATOR_ALT_TEXT; + + oEl.appendChild(oImg); + + this.submenuIndicator = oImg; + + Dom.addClass(aNodes, "hassubmenu"); + + if(oConfig.getProperty("disabled")) { + + oConfig.refireEvent("disabled"); + + } + + if(oConfig.getProperty("selected")) { + + oConfig.refireEvent("selected"); + + } + + } + + } + + } + else { + + Dom.removeClass(aNodes, "hassubmenu"); + + if(oImg) { + + oEl.removeChild(oImg); + + } + + if(this._oSubmenu) { + + this._oSubmenu.destroy(); + + } + + } + + }, + + // Public methods + + /** + * @method initDefaultConfig + * @description Initializes an item's configurable properties. + */ + initDefaultConfig : function() { + + var oConfig = this.cfg; + var CheckBoolean = oConfig.checkBoolean; + + // Define the config properties + + /** + * @config text + * @description String specifying the text label for the menu item. + * When building a menu from existing HTML the value of this property + * will be interpreted from the menu's markup. + * @default "" + * @type String + */ + oConfig.addProperty( + "text", + { + value: "", + handler: this.configText, + validator: this._checkString, + suppressEvent: true + } + ); + + + /** + * @config helptext + * @description String specifying additional instructional text to + * accompany the text for the nenu item. + * @default null + * @type String| + * HTMLElement + */ + oConfig.addProperty("helptext", { handler: this.configHelpText }); + + /** + * @config url + * @description String specifying the URL for the menu item's anchor's + * "href" attribute. When building a menu from existing HTML the value + * of this property will be interpreted from the menu's markup. + * @default "#" + * @type String + */ + oConfig.addProperty( + "url", + { value: "#", handler: this.configURL, suppressEvent: true } + ); + + /** + * @config target + * @description String specifying the value for the "target" attribute + * of the menu item's anchor element. Specifying a target will + * require the user to click directly on the menu item's anchor node in + * order to cause the browser to navigate to the specified URL. + * When building a menu from existing HTML the value of this property + * will be interpreted from the menu's markup. + * @default null + * @type String + */ + oConfig.addProperty( + "target", + { handler: this.configTarget, suppressEvent: true } + ); + + /** + * @config emphasis + * @description Boolean indicating if the text of the menu item will be + * rendered with emphasis. When building a menu from existing HTML the + * value of this property will be interpreted from the menu's markup. + * @default false + * @type Boolean + */ + oConfig.addProperty( + "emphasis", + { + value: false, + handler: this.configEmphasis, + validator: CheckBoolean, + suppressEvent: true + } + ); + + /** + * @config strongemphasis + * @description Boolean indicating if the text of the menu item will be + * rendered with strong emphasis. When building a menu from existing + * HTML the value of this property will be interpreted from the + * menu's markup. + * @default false + * @type Boolean + */ + oConfig.addProperty( + "strongemphasis", + { + value: false, + handler: this.configStrongEmphasis, + validator: CheckBoolean, + suppressEvent: true + } + ); + + /** + * @config checked + * @description Boolean indicating if the menu item should be rendered + * with a checkmark. + * @default false + * @type Boolean + */ + oConfig.addProperty( + "checked", + { + value: false, + handler: this.configChecked, + validator: this.cfg.checkBoolean, + suppressEvent: true, + supercedes:["disabled"] + } + ); + + /** + * @config disabled + * @description Boolean indicating if the menu item should be disabled. + * (Disabled menu items are dimmed and will not respond to user input + * or fire events.) + * @default false + * @type Boolean + */ + oConfig.addProperty( + "disabled", + { + value: false, + handler: this.configDisabled, + validator: CheckBoolean, + suppressEvent: true + } + ); + + /** + * @config selected + * @description Boolean indicating if the menu item should + * be highlighted. + * @default false + * @type Boolean + */ + oConfig.addProperty( + "selected", + { + value: false, + handler: this.configSelected, + validator: CheckBoolean, + suppressEvent: true + } + ); + + /** + * @config submenu + * @description Object specifying the submenu to be appended to the + * menu item. The value can be one of the following: + * @default null + * @type Menu|String|Object| + * HTMLElement + */ + oConfig.addProperty("submenu", { handler: this.configSubmenu }); + + }, + + /** + * @method getNextEnabledSibling + * @description Finds the menu item's next enabled sibling. + * @return YAHOO.widget.MenuItem + */ + getNextEnabledSibling: function() { + + if(this.parent instanceof Menu) { + + var nGroupIndex = this.groupIndex; + + /** + * Finds the next item in an array. + * @private + * @param {p_aArray} Array to search. + * @param {p_nStartIndex} Number indicating the index to + * start searching the array. + * @return {Object} + */ + var getNextArrayItem = function(p_aArray, p_nStartIndex) { + + return p_aArray[p_nStartIndex] || + getNextArrayItem(p_aArray, (p_nStartIndex+1)); + + }; + + + var aItemGroups = this.parent.getItemGroups(); + var oNextItem; + + + if(this.index < (aItemGroups[nGroupIndex].length - 1)) { + + oNextItem = getNextArrayItem( + aItemGroups[nGroupIndex], + (this.index+1) + ); + + } + else { + + var nNextGroupIndex; + + if(nGroupIndex < (aItemGroups.length - 1)) { + + nNextGroupIndex = nGroupIndex + 1; + + } + else { + + nNextGroupIndex = 0; + + } + + var aNextGroup = getNextArrayItem(aItemGroups, nNextGroupIndex); + + // Retrieve the first menu item in the next group + + oNextItem = getNextArrayItem(aNextGroup, 0); + + } + + return ( + oNextItem.cfg.getProperty("disabled") || + oNextItem.element.style.display == "none" + ) ? + oNextItem.getNextEnabledSibling() : oNextItem; + + } + + }, + + /** + * @method getPreviousEnabledSibling + * @description Finds the menu item's previous enabled sibling. + * @return {YAHOO.widget.MenuItem} + */ + getPreviousEnabledSibling: function() { + + if(this.parent instanceof Menu) { + + var nGroupIndex = this.groupIndex; + + /** + * Returns the previous item in an array + * @private + * @param {p_aArray} Array to search. + * @param {p_nStartIndex} Number indicating the index to + * start searching the array. + * @return {Object} + */ + var getPreviousArrayItem = function(p_aArray, p_nStartIndex) { + + return p_aArray[p_nStartIndex] || + getPreviousArrayItem(p_aArray, (p_nStartIndex-1)); + + }; + + /** + * Get the index of the first item in an array + * @private + * @param {p_aArray} Array to search. + * @param {p_nStartIndex} Number indicating the index to + * start searching the array. + * @return {Object} + */ + var getFirstItemIndex = function(p_aArray, p_nStartIndex) { + + return p_aArray[p_nStartIndex] ? + p_nStartIndex : + getFirstItemIndex(p_aArray, (p_nStartIndex+1)); + + }; + + var aItemGroups = this.parent.getItemGroups(); + var oPreviousItem; + + if( + this.index > getFirstItemIndex(aItemGroups[nGroupIndex], 0) + ) { + + oPreviousItem = + getPreviousArrayItem( + aItemGroups[nGroupIndex], + (this.index-1) + ); + + } + else { + + var nPreviousGroupIndex; + + if(nGroupIndex > getFirstItemIndex(aItemGroups, 0)) { + + nPreviousGroupIndex = nGroupIndex - 1; + + } + else { + + nPreviousGroupIndex = aItemGroups.length - 1; + + } + + var aPreviousGroup = + getPreviousArrayItem(aItemGroups, nPreviousGroupIndex); + + oPreviousItem = + getPreviousArrayItem( + aPreviousGroup, + (aPreviousGroup.length - 1) + ); + + } + + return ( + oPreviousItem.cfg.getProperty("disabled") || + oPreviousItem.element.style.display == "none" + ) ? + oPreviousItem.getPreviousEnabledSibling() : oPreviousItem; + + } + + }, + + /** + * @method focus + * @description Causes the menu item to receive the focus and fires the + * focus event. + */ + focus: function() { + + var oParent = this.parent; + var oAnchor = this._oAnchor; + var oActiveItem = oParent.activeItem; + + if( + !this.cfg.getProperty("disabled") && + oParent && + oParent.cfg.getProperty("visible") && + this.element.style.display != "none" + ) { + + if(oActiveItem) { + + oActiveItem.blur(); + + } + + try { + + oAnchor.focus(); + + } + catch(e) { + + } + + this.focusEvent.fire(); + + } + + }, + + /** + * @method blur + * @description Causes the menu item to lose focus and fires the + * onblur event. + */ + blur: function() { + + var oParent = this.parent; + + if( + !this.cfg.getProperty("disabled") && + oParent && + Dom.getStyle(oParent.element, "visibility") == "visible" + ) { + + this._oAnchor.blur(); + + this.blurEvent.fire(); + + } + + }, + + /** + * @method destroy + * @description Removes the menu item's
- Object + * specifying a Menu instance.
- Object literal specifying the + * menu to be created. Format:
{ id: [menu id], itemdata: + * [array of values for + * items] }
.- String specifying the id attribute + * of the
<div>
element of the menu.- + * Object specifying the
<div>
element of the + * menu.<li>
element + * from its parent<ul>
element. + */ + destroy: function() { + + var oEl = this.element; + + if(oEl) { + + // Remove CustomEvent listeners + + this.mouseOverEvent.unsubscribeAll(); + this.mouseOutEvent.unsubscribeAll(); + this.mouseDownEvent.unsubscribeAll(); + this.mouseUpEvent.unsubscribeAll(); + this.clickEvent.unsubscribeAll(); + this.keyPressEvent.unsubscribeAll(); + this.keyDownEvent.unsubscribeAll(); + this.keyUpEvent.unsubscribeAll(); + this.focusEvent.unsubscribeAll(); + this.blurEvent.unsubscribeAll(); + this.cfg.configChangedEvent.unsubscribeAll(); + + // Remove the element from the parent node + + var oParentNode = oEl.parentNode; + + if(oParentNode) { + + oParentNode.removeChild(oEl); + + this.destroyEvent.fire(); + + } + + this.destroyEvent.unsubscribeAll(); + + } + + }, + + /** + * @method toString + * @description Returns a string representing the menu item. + * @return {String} + */ + toString: function() { + + return ("MenuItem: " + this.cfg.getProperty("text")); + + } + +}; + +})(); + +/** +* Creates an item for a menu module. +* +* @param {String} p_oObject String specifying the text of the menu module item. +* @param {HTMLLIElement} p_oObject Object specifying the +*<li>
element of the menu module item. +* @param {HTMLOptGroupElement} p_oObject Object specifying +* the<optgroup>
element of the menu module item. +* @param {HTMLOptionElement} p_oObject Object specifying the +*<option>
element of the menu module item. +* @param {Object} p_oConfig Optional. Object literal specifying the +* configuration for the menu module item. See configuration class documentation +* for more details. +* @class MenuModuleItem +* @constructor +* @deprecated As of version 0.12, all MenuModuleItem functionality has been +* implemented directly in YAHOO.widget.MenuItem, making YAHOO.widget.MenuItem +* the base class for all menu items. +*/ +YAHOO.widget.MenuModuleItem = YAHOO.widget.MenuItem; + +/** +* Creates a list of options or commands which are made visible in response to +* an HTML element's "contextmenu" event ("mousedown" for Opera). +* +* @param {String} p_oElement String specifying the id attribute of the +*<div>
element of the context menu. +* @param {String} p_oElement String specifying the id attribute of the +*<select>
element to be used as the data source for the +* context menu. +* @param {HTMLDivElement} p_oElement Object specifying the +*<div>
element of the context menu. +* @param {HTMLSelectElement} p_oElement Object specifying +* the<select>
element to be used as the data source for +* the context menu. +* @param {Object} p_oConfig Optional. Object literal specifying the +* configuration for the context menu. See configuration class documentation +* for more details. +* @class ContextMenu +* @constructor +* @extends YAHOO.widget.Menu +* @namespace YAHOO.widget +*/ +YAHOO.widget.ContextMenu = function(p_oElement, p_oConfig) { + + YAHOO.widget.ContextMenu.superclass.constructor.call( + this, + p_oElement, + p_oConfig + ); + +}; + +YAHOO.extend(YAHOO.widget.ContextMenu, YAHOO.widget.Menu, { + +// Private properties + +/** +* @property _oTrigger +* @description Object reference to the current value of the "trigger" +* configuration property. +* @default null +* @private +* @type String|HTMLElement|Array +*/ +_oTrigger: null, + +// Public properties + +/** +* @property contextEventTarget +* @description Object reference for the HTML element that was the target of the +* "contextmenu" DOM event ("mousedown" for Opera) that triggered the display of +* the context menu. +* @default null +* @type HTMLElement +*/ +contextEventTarget: null, + +/** +* @method init +* @description The ContextMenu class's initialization method. This method is +* automatically called by the constructor, and sets up all DOM references for +* pre-existing markup, and creates required markup if it is not already present. +* @param {String} p_oElement String specifying the id attribute of the +*<div>
element of the context menu. +* @param {String} p_oElement String specifying the id attribute of the +*<select>
element to be used as the data source for +* the context menu. +* @param {HTMLDivElement} p_oElement Object specifying the +*<div>
element of the context menu. +* @param {HTMLSelectElement} p_oElement Object specifying +* the<select>
element to be used as the data source for +* the context menu. +* @param {Object} p_oConfig Optional. Object literal specifying the +* configuration for the context menu. See configuration class documentation +* for more details. +*/ +init: function(p_oElement, p_oConfig) { + + if(!this.ITEM_TYPE) { + + this.ITEM_TYPE = YAHOO.widget.ContextMenuItem; + + } + + // Call the init of the superclass (YAHOO.widget.Menu) + + YAHOO.widget.ContextMenu.superclass.init.call(this, p_oElement); + + this.beforeInitEvent.fire(YAHOO.widget.ContextMenu); + + if(p_oConfig) { + + this.cfg.applyConfig(p_oConfig, true); + + } + + + this.initEvent.fire(YAHOO.widget.ContextMenu); + +}, + +// Private methods + +/** +* @method _removeEventHandlers +* @description Removes all of the DOM event handlers from the HTML element(s) +* whose "context menu" event ("click" for Opera) trigger the display of +* the context menu. +* @private +*/ +_removeEventHandlers: function() { + + var Event = YAHOO.util.Event; + var oTrigger = this._oTrigger; + var bOpera = (this.browser == "opera"); + + // Remove the event handlers from the trigger(s) + + Event.removeListener( + oTrigger, + (bOpera ? "mousedown" : "contextmenu"), + this._onTriggerContextMenu + ); + + if(bOpera) { + + Event.removeListener(oTrigger, "click", this._onTriggerClick); + + } + +}, + +// Private event handlers + +/** +* @method _onTriggerClick +* @description "click" event handler for the HTML element(s) identified as the +* "trigger" for the context menu. Used to cancel default behaviors in Opera. +* @private +* @param {Event} p_oEvent Object representing the DOM event object passed back +* by the event utility (YAHOO.util.Event). +* @param {YAHOO.widget.ContextMenu} p_oMenu Object representing the context +* menu that is handling the event. +*/ +_onTriggerClick: function(p_oEvent, p_oMenu) { + + if(p_oEvent.ctrlKey) { + + YAHOO.util.Event.stopEvent(p_oEvent); + + } + +}, + +/** +* @method _onTriggerContextMenu +* @description "contextmenu" event handler ("mousedown" for Opera) for the HTML +* element(s) that trigger the display of the context menu. +* @private +* @param {Event} p_oEvent Object representing the DOM event object passed back +* by the event utility (YAHOO.util.Event). +* @param {YAHOO.widget.ContextMenu} p_oMenu Object representing the context +* menu that is handling the event. +*/ +_onTriggerContextMenu: function(p_oEvent, p_oMenu) { + + // Hide any other ContextMenu instances that might be visible + + YAHOO.widget.MenuManager.hideVisible(); + + var Event = YAHOO.util.Event; + var oConfig = this.cfg; + + if(p_oEvent.type == "mousedown" && !p_oEvent.ctrlKey) { + + return; + + } + + this.contextEventTarget = Event.getTarget(p_oEvent); + + // Position and display the context menu + + var nX = Event.getPageX(p_oEvent); + var nY = Event.getPageY(p_oEvent); + + oConfig.applyConfig( { xy:[nX, nY], visible:true } ); + oConfig.fireQueue(); + + /* + Prevent the browser's default context menu from appearing and + stop the propagation of the "contextmenu" event so that + other ContextMenu instances are not displayed. + */ + + Event.stopEvent(p_oEvent); + +}, + +// Public methods + +/** +* @method toString +* @description Returns a string representing the context menu. +* @return {String} +*/ +toString: function() { + + return ("ContextMenu " + this.id); + +}, + +/** +* @method initDefaultConfig +* @description Initializes the class's configurable properties which can be +* changed using the context menu's Config object ("cfg"). +*/ +initDefaultConfig: function() { + + YAHOO.widget.ContextMenu.superclass.initDefaultConfig.call(this); + + /** + * @config trigger + * @description The HTML element(s) whose "contextmenu" event ("mousedown" + * for Opera) trigger the display of the context menu. Can be a string + * representing the id attribute of the HTML element, an object reference + * for the HTML element, or an array of strings or HTML element references. + * @default null + * @type String|HTMLElement|Array + */ + this.cfg.addProperty("trigger", { handler: this.configTrigger }); + +}, + +/** +* @method destroy +* @description Removes the context menu's<div>
element +* (and accompanying child nodes) from the document. +*/ +destroy: function() { + + // Remove the DOM event handlers from the current trigger(s) + + this._removeEventHandlers(); + + + // Continue with the superclass implementation of this method + + YAHOO.widget.ContextMenu.superclass.destroy.call(this); + +}, + +// Public event handlers for configuration properties + +/** +* @method configTrigger +* @description Event handler for when the value of the "trigger" configuration +* property changes. +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.ContextMenu} p_oMenu Object representing the context +* menu that fired the event. +*/ +configTrigger: function(p_sType, p_aArgs, p_oMenu) { + + var Event = YAHOO.util.Event; + var oTrigger = p_aArgs[0]; + + if(oTrigger) { + + /* + If there is a current "trigger" - remove the event handlers + from that element(s) before assigning new ones + */ + + if(this._oTrigger) { + + this._removeEventHandlers(); + + } + + this._oTrigger = oTrigger; + + /* + Listen for the "mousedown" event in Opera b/c it does not + support the "contextmenu" event + */ + + var bOpera = (this.browser == "opera"); + + Event.addListener( + oTrigger, + (bOpera ? "mousedown" : "contextmenu"), + this._onTriggerContextMenu, + this, + true + ); + + /* + Assign a "click" event handler to the trigger element(s) for + Opera to prevent default browser behaviors. + */ + + if(bOpera) { + + Event.addListener( + oTrigger, + "click", + this._onTriggerClick, + this, + true + ); + + } + + } + else { + + this._removeEventHandlers(); + + } + +} + +}); // END YAHOO.extend + +/** +* Creates an item for a context menu. +* +* @param {String} p_oObject String specifying the text of the context menu item. +* @param {HTMLLIElement} p_oObject Object specifying the +*<li>
element of the context menu item. +* @param {HTMLOptGroupElement} p_oObject Object +* specifying the<optgroup>
element of the context +* menu item. +* @param {HTMLOptionElement} p_oObject Object specifying +* the<option>
element of the context menu item. +* @param {Object} p_oConfig Optional. Object literal specifying the +* configuration for the context menu item. See configuration class +* documentation for more details. +* @class ContextMenuItem +* @constructor +* @extends YAHOO.widget.MenuItem +*/ +YAHOO.widget.ContextMenuItem = function(p_oObject, p_oConfig) { + + YAHOO.widget.ContextMenuItem.superclass.constructor.call( + this, + p_oObject, + p_oConfig + ); + +}; + +YAHOO.extend(YAHOO.widget.ContextMenuItem, YAHOO.widget.MenuItem, { + +/** +* @method init +* @description The ContextMenuItem class's initialization method. This method +* is automatically called by the constructor, and sets up all DOM references +* for pre-existing markup, and creates required markup if it is not +* already present. +* @param {String} p_oObject String specifying the text of the context menu item. +* @param {HTMLLIElement} p_oObject Object specifying the +*<li>
element of the context menu item. +* @param {HTMLOptGroupElement} p_oObject Object +* specifying the<optgroup>
element of the context +* menu item. +* @param {HTMLOptionElement} p_oObject Object specifying +* the<option>
element of the context menu item. +* @param {Object} p_oConfig Optional. Object literal specifying the +* configuration for the context menu item. See configuration class +* documentation for more details. +*/ +init: function(p_oObject, p_oConfig) { + + if(!this.SUBMENU_TYPE) { + + this.SUBMENU_TYPE = YAHOO.widget.ContextMenu; + + } + + /* + Call the init of the superclass (YAHOO.widget.MenuItem) + Note: We don't pass the user config in here yet + because we only want it executed once, at the lowest + subclass level. + */ + + YAHOO.widget.ContextMenuItem.superclass.init.call(this, p_oObject); + + var oConfig = this.cfg; + + if(p_oConfig) { + + oConfig.applyConfig(p_oConfig, true); + + } + + oConfig.fireQueue(); + +}, + +// Public methods + +/** +* @method toString +* @description Returns a string representing the context menu item. +* @return {String} +*/ +toString: function() { + + return ("MenuBarItem: " + this.cfg.getProperty("text")); + +} + +}); // END YAHOO.extend + +/** +* Horizontal collection of items, each of which can contain a submenu. +* +* @param {String} p_oElement String specifying the id attribute of the +*<div>
element of the menu bar. +* @param {String} p_oElement String specifying the id attribute of the +*<select>
element to be used as the data source for the +* menu bar. +* @param {HTMLDivElement} p_oElement Object specifying +* the<div>
element of the menu bar. +* @param {HTMLSelectElement} p_oElement Object +* specifying the<select>
element to be used as the data +* source for the menu bar. +* @param {Object} p_oConfig Optional. Object literal specifying the +* configuration for the menu bar. See configuration class documentation for +* more details. +* @class Menubar +* @constructor +* @extends YAHOO.widget.Menu +* @namespace YAHOO.widget +*/ +YAHOO.widget.MenuBar = function(p_oElement, p_oConfig) { + + YAHOO.widget.MenuBar.superclass.constructor.call( + this, + p_oElement, + p_oConfig + ); + +}; + +YAHOO.extend(YAHOO.widget.MenuBar, YAHOO.widget.Menu, { + +/** +* @method init +* @description The MenuBar class's initialization method. This method is +* automatically called by the constructor, and sets up all DOM references for +* pre-existing markup, and creates required markup if it is not already present. +* @param {String} p_oElement String specifying the id attribute of the +*<div>
element of the menu bar. +* @param {String} p_oElement String specifying the id attribute of the +*<select>
element to be used as the data source for the +* menu bar. +* @param {HTMLDivElement} p_oElement Object specifying +* the<div>
element of the menu bar. +* @param {HTMLSelectElement} p_oElement Object +* specifying the<select>
element to be used as the data +* source for the menu bar. +* @param {Object} p_oConfig Optional. Object literal specifying the +* configuration for the menu bar. See configuration class documentation for +* more details. +*/ +init: function(p_oElement, p_oConfig) { + + if(!this.ITEM_TYPE) { + + this.ITEM_TYPE = YAHOO.widget.MenuBarItem; + + } + + // Call the init of the superclass (YAHOO.widget.Menu) + + YAHOO.widget.MenuBar.superclass.init.call(this, p_oElement); + + this.beforeInitEvent.fire(YAHOO.widget.MenuBar); + + if(p_oConfig) { + + this.cfg.applyConfig(p_oConfig, true); + + } + + this.initEvent.fire(YAHOO.widget.MenuBar); + +}, + +// Constants + +/** +* @property CSS_CLASS_NAME +* @description String representing the CSS class(es) to be applied to the menu +* bar's<div>
element. +* @default "yuimenubar" +* @final +* @type String +*/ +CSS_CLASS_NAME: "yuimenubar", + +// Protected event handlers + +/** +* @method _onKeyDown +* @description "keydown" Custom Event handler for the menu bar. +* @private +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.MenuBar} p_oMenuBar Object representing the menu bar +* that fired the event. +*/ +_onKeyDown: function(p_sType, p_aArgs, p_oMenuBar) { + + var Event = YAHOO.util.Event; + var oEvent = p_aArgs[0]; + var oItem = p_aArgs[1]; + var oItemCfg = oItem.cfg; + var oSubmenu; + + switch(oEvent.keyCode) { + + case 27: // Esc key + + if(this.cfg.getProperty("position") == "dynamic") { + + this.hide(); + + if(this.parent) { + + this.parent.focus(); + + } + + } + else if(this.activeItem) { + + oSubmenu = this.activeItem.cfg.getProperty("submenu"); + + if(oSubmenu && oSubmenu.cfg.getProperty("visible")) { + + oSubmenu.hide(); + this.activeItem.focus(); + + } + else { + + this.activeItem.cfg.setProperty("selected", false); + this.activeItem.blur(); + + } + + } + + + Event.preventDefault(oEvent); + + break; + + case 37: // Left arrow + case 39: // Right arrow + + if( + oItem == this.activeItem && + !oItemCfg.getProperty("selected") + ) { + + oItemCfg.setProperty("selected", true); + + } + else { + + var oNextItem = (oEvent.keyCode == 37) ? + oItem.getPreviousEnabledSibling() : + oItem.getNextEnabledSibling(); + + if(oNextItem) { + + this.clearActiveItem(); + + oNextItem.cfg.setProperty("selected", true); + + if(this.cfg.getProperty("autosubmenudisplay")) { + + oSubmenu = oNextItem.cfg.getProperty("submenu"); + + if(oSubmenu) { + + oSubmenu.show(); + oSubmenu.activeItem.blur(); + oSubmenu.activeItem = null; + + } + + } + + oNextItem.focus(); + + } + + } + + Event.preventDefault(oEvent); + + break; + + case 40: // Down arrow + + if(this.activeItem != oItem) { + + this.clearActiveItem(); + + oItemCfg.setProperty("selected", true); + oItem.focus(); + + } + + oSubmenu = oItemCfg.getProperty("submenu"); + + if(oSubmenu) { + + if(oSubmenu.cfg.getProperty("visible")) { + + oSubmenu.setInitialSelection(); + oSubmenu.setInitialFocus(); + + } + else { + + oSubmenu.show(); + + } + + } + + Event.preventDefault(oEvent); + + break; + + } + +}, + +/** +* @method _onClick +* @description "click" event handler for the menu bar. +* @protected +* @param {String} p_sType String representing the name of the event that +* was fired. +* @param {Array} p_aArgs Array of arguments sent when the event was fired. +* @param {YAHOO.widget.MenuBar} p_oMenuBar Object representing the menu bar +* that fired the event. +*/ +_onClick: function(p_sType, p_aArgs, p_oMenuBar) { + + YAHOO.widget.MenuBar.superclass._onClick.call( + this, + p_sType, + p_aArgs, + p_oMenuBar + ); + + var oItem = p_aArgs[1]; + + if(oItem) { + + var Event = YAHOO.util.Event; + var Dom = YAHOO.util.Dom; + + var oEvent = p_aArgs[0]; + var oTarget = Event.getTarget(oEvent); + + var oActiveItem = this.activeItem; + var oConfig = this.cfg; + + // Hide any other submenus that might be visible + + if(oActiveItem && oActiveItem != oItem) { + + this.clearActiveItem(); + + } + + + // Select and focus the current item + + oItem.cfg.setProperty("selected", true); + oItem.focus(); + + + // Show the submenu for the item + + var oSubmenu = oItem.cfg.getProperty("submenu"); + + if(oSubmenu && oTarget != oItem.submenuIndicator) { + + if(oSubmenu.cfg.getProperty("visible")) { + + oSubmenu.hide(); + + } + else { + + oSubmenu.show(); + + } + + } + + } + +}, + +// Public methods + +/** +* @method toString +* @description Returns a string representing the menu bar. +* @return {String} +*/ +toString: function() { + + return ("MenuBar " + this.id); + +}, + +/** +* @description Initializes the class's configurable properties which can be +* changed using the menu bar's Config object ("cfg"). +* @method initDefaultConfig +*/ +initDefaultConfig: function() { + + YAHOO.widget.MenuBar.superclass.initDefaultConfig.call(this); + + var oConfig = this.cfg; + + // Add configuration properties + + /* + Set the default value for the "position" configuration property + to "static" by re-adding the property. + */ + + /** + * @config position + * @description String indicating how a menu bar should be positioned on the + * screen. Possible values are "static" and "dynamic." Static menu bars + * are visible by default and reside in the normal flow of the document + * (CSS position: static). Dynamic menu bars are hidden by default, reside + * out of the normal flow of the document (CSS position: absolute), and can + * overlay other elements on the screen. + * @default static + * @type String + */ + oConfig.addProperty( + "position", + { + value: "static", + handler: this.configPosition, + validator: this._checkPosition, + supercedes: ["visible"] + } + ); + + /* + Set the default value for the "submenualignment" configuration property + to ["tl","bl"] by re-adding the property. + */ + + /** + * @config submenualignment + * @description Array defining how submenus should be aligned to their + * parent menu bar item. The format is: [itemCorner, submenuCorner]. + * @default ["tl","bl"] + * @type Array + */ + oConfig.addProperty("submenualignment", { value: ["tl","bl"] } ); + + /* + Change the default value for the "autosubmenudisplay" configuration + property to "false" by re-adding the property. + */ + + /** + * @config autosubmenudisplay + * @description Boolean indicating if submenus are automatically made + * visible when the user mouses over the menu bar's items. + * @default false + * @type Boolean + */ + oConfig.addProperty( + "autosubmenudisplay", + { value: false, validator: oConfig.checkBoolean } + ); + +} + +}); // END YAHOO.extend + +/** +* Creates an item for a menu bar. +* +* @param {String} p_oObject String specifying the text of the menu bar item. +* @param {HTMLLIElement} p_oObject Object specifying the +*<li>
element of the menu bar item. +* @param {HTMLOptGroupElement} p_oObject Object +* specifying the<optgroup>
element of the menu bar item. +* @param {HTMLOptionElement} p_oObject Object specifying +* the<option>
element of the menu bar item. +* @param {Object} p_oConfig Optional. Object literal specifying the +* configuration for the menu bar item. See configuration class documentation +* for more details. +* @class MenuBarItem +* @constructor +* @extends YAHOO.widget.MenuItem +*/ +YAHOO.widget.MenuBarItem = function(p_oObject, p_oConfig) { + + YAHOO.widget.MenuBarItem.superclass.constructor.call( + this, + p_oObject, + p_oConfig + ); + +}; + +YAHOO.extend(YAHOO.widget.MenuBarItem, YAHOO.widget.MenuItem, { + +/** +* @method init +* @description The MenuBarItem class's initialization method. This method is +* automatically called by the constructor, and sets up all DOM references for +* pre-existing markup, and creates required markup if it is not already present. +* @param {String} p_oObject String specifying the text of the menu bar item. +* @param {HTMLLIElement} p_oObject Object specifying the +*<li>
element of the menu bar item. +* @param {HTMLOptGroupElement} p_oObject Object +* specifying the<optgroup>
element of the menu bar item. +* @param {HTMLOptionElement} p_oObject Object specifying +* the<option>
element of the menu bar item. +* @param {Object} p_oConfig Optional. Object literal specifying the +* configuration for the menu bar item. See configuration class documentation +* for more details. +*/ +init: function(p_oObject, p_oConfig) { + + if(!this.SUBMENU_TYPE) { + + this.SUBMENU_TYPE = YAHOO.widget.Menu; + + } + + /* + Call the init of the superclass (YAHOO.widget.MenuItem) + Note: We don't pass the user config in here yet + because we only want it executed once, at the lowest + subclass level. + */ + + YAHOO.widget.MenuBarItem.superclass.init.call(this, p_oObject); + + var oConfig = this.cfg; + + if(p_oConfig) { + + oConfig.applyConfig(p_oConfig, true); + + } + + oConfig.fireQueue(); + +}, + +// Constants + +/** +* @property CSS_CLASS_NAME +* @description String representing the CSS class(es) to be applied to the +*<li>
element of the menu bar item. +* @default "yuimenubaritem" +* @final +* @type String +*/ +CSS_CLASS_NAME: "yuimenubaritem", + +/** +* @property SUBMENU_INDICATOR_IMAGE_PATH +* @description String representing the path to the image to be used for the +* menu bar item's submenu arrow indicator. +* @default "nt/ic/ut/alt1/menuarodwn8_nrm_1.gif" +* @final +* @type String +*/ +SUBMENU_INDICATOR_IMAGE_PATH: "nt/ic/ut/alt1/menuarodwn8_nrm_1.gif", + +/** +* @property SELECTED_SUBMENU_INDICATOR_IMAGE_PATH +* @description String representing the path to the image to be used for the +* submenu arrow indicator when the menu bar item is selected. +* @default "nt/ic/ut/alt1/menuarodwn8_hov_1.gif" +* @final +* @type String +*/ +SELECTED_SUBMENU_INDICATOR_IMAGE_PATH: "nt/ic/ut/alt1/menuarodwn8_hov_1.gif", + +/** +* @property DISABLED_SUBMENU_INDICATOR_IMAGE_PATH +* @description String representing the path to the image to be used for the +* submenu arrow indicator when the menu bar item is disabled. +* @default "nt/ic/ut/alt1/menuarodwn8_dim_1.gif" +* @final +* @type String +*/ +DISABLED_SUBMENU_INDICATOR_IMAGE_PATH: "nt/ic/ut/alt1/menuarodwn8_dim_1.gif", + +// Public methods + +/** +* @method toString +* @description Returns a string representing the menu bar item. +* @return {String} +*/ +toString: function() { + + return ("MenuBarItem: " + this.cfg.getProperty("text")); + +} + +}); // END YAHOO.extend + diff --git a/frontend/beta/js/YUI/slider.js b/frontend/beta/js/YUI/slider.js new file mode 100644 index 0000000..8d3cd62 --- a/dev/null +++ b/frontend/beta/js/YUI/slider.js @@ -0,0 +1,1113 @@ +/* +Copyright (c) 2006, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.net/yui/license.txt +version: 0.12.0 +*/ + +/** + * The Slider component is a UI control that enables the user to adjust + * values in a finite range along one or two axes. Typically, the Slider + * control is used in a web application as a rich, visual replacement + * for an input box that takes a number as input. The Slider control can + * also easily accommodate a second dimension, providing x,y output for + * a selection point chosen from a rectangular region. + * + * @module slider + * @title Slider Widget + * @namespace YAHOO.widget + * @requires yahoo,dom,dragdrop,event + * @optional animation + */ + +/** + * A DragDrop implementation that can be used as a background for a + * slider. It takes a reference to the thumb instance + * so it can delegate some of the events to it. The goal is to make the + * thumb jump to the location on the background when the background is + * clicked. + * + * @class Slider + * @extends YAHOO.util.DragDrop + * @constructor + * @param {String} id The id of the element linked to this instance + * @param {String} sGroup The group of related DragDrop items + * @param {String} sType The type of slider (horiz, vert, region) + */ +YAHOO.widget.Slider = function(sElementId, sGroup, oThumb, sType) { + if (sElementId) { + + /** + * The type of the slider (horiz, vert, region) + * @property type + * @type string + */ + this.type = sType; + + this.init(sElementId, sGroup, true); + + //this.removeInvalidHandleType("A"); + + + var self = this; + + /** + * Event the fires when the value of the control changes. If + * the control is animated the event will fire every point + * along the way. + * @event change + * @param {int} new + * @param {int} firstOffset the number of pixels the thumb has moved + * from its start position. Normal horizontal and vertical sliders will only + * have the firstOffset. Regions will have both, the first is the horizontal + * offset, the second the vertical. + * @param {int} secondOffset the y offset for region sliders + */ + this.createEvent("change", this); + + /** + * Event that fires at the end of a slider thumb move. + * @event slideStart + */ + this.createEvent("slideStart", this); + + /** + * Event that fires at the end of a slider thumb move + * @event slideEnd + */ + this.createEvent("slideEnd", this); + + /** + * A YAHOO.widget.SliderThumb instance that we will use to + * reposition the thumb when the background is clicked + * @property thumb + * @type YAHOO.widget.SliderThumb + */ + this.thumb = oThumb; + + // add handler for the handle onchange event + oThumb.onChange = function() { + self.handleThumbChange(); + }; + + /** + * Overrides the isTarget property in YAHOO.util.DragDrop + * @property isTarget + * @private + */ + this.isTarget = false; + + /** + * Flag that determines if the thumb will animate when moved + * @property animate + * @type boolean + */ + this.animate = YAHOO.widget.Slider.ANIM_AVAIL; + + /** + * Set to false to disable a background click thumb move + * @property backgroundEnabled + * @type boolean + */ + this.backgroundEnabled = true; + + /** + * Adjustment factor for tick animation, the more ticks, the + * faster the animation (by default) + * @property tickPause + * @type int + */ + this.tickPause = 40; + + /** + * Enables the arrow, home and end keys, defaults to true. + * @property enableKeys + * @type boolean + */ + this.enableKeys = true; + + /** + * Specifies the number of pixels the arrow keys will move the slider. + * Default is 25. + * @property keyIncrement + * @type int + */ + this.keyIncrement = 20; + + /** + * moveComplete is set to true when the slider has moved to its final + * destination. For animated slider, this value can be checked in + * the onChange handler to make it possible to execute logic only + * when the move is complete rather than at all points along the way. + * + * @property moveComplete + * @type Boolean + */ + this.moveComplete = true; + + /** + * If animation is configured, specifies the length of the animation + * in seconds. + * @property animationDuration + * @type int + * @default 0.2 + */ + this.animationDuration = 0.2; + + if (oThumb._isHoriz && oThumb.xTicks && oThumb.xTicks.length) { + this.tickPause = Math.round(360 / oThumb.xTicks.length); + } else if (oThumb.yTicks && oThumb.yTicks.length) { + this.tickPause = Math.round(360 / oThumb.yTicks.length); + } + + + // delegate thumb methods + oThumb.onMouseDown = function () { return self.focus(); }; + //oThumb.b4MouseDown = function () { return self.b4MouseDown(); }; + // oThumb.lock = function() { self.lock(); }; + // oThumb.unlock = function() { self.unlock(); }; + oThumb.onMouseUp = function() { self.thumbMouseUp(); }; + oThumb.onDrag = function() { self.fireEvents(); }; + oThumb.onAvailable = function() { return self.setStartSliderState(); }; + } +}; + +/** + * Factory method for creating a horizontal slider + * @method YAHOO.widget.Slider.getHorizSlider + * @static + * @param {String} sBGElId the id of the slider's background element + * @param {String} sHandleElId the id of the thumb element + * @param {int} iLeft the number of pixels the element can move left + * @param {int} iRight the number of pixels the element can move right + * @param {int} iTickSize optional parameter for specifying that the element + * should move a certain number pixels at a time. + * @return {Slider} a horizontal slider control + */ +YAHOO.widget.Slider.getHorizSlider = + function (sBGElId, sHandleElId, iLeft, iRight, iTickSize) { + return new YAHOO.widget.Slider(sBGElId, sBGElId, + new YAHOO.widget.SliderThumb(sHandleElId, sBGElId, + iLeft, iRight, 0, 0, iTickSize), "horiz"); +}; + +/** + * Factory method for creating a vertical slider + * @method YAHOO.widget.Slider.getVertSlider + * @static + * @param {String} sBGElId the id of the slider's background element + * @param {String} sHandleElId the id of the thumb element + * @param {int} iUp the number of pixels the element can move up + * @param {int} iDown the number of pixels the element can move down + * @param {int} iTickSize optional parameter for specifying that the element + * should move a certain number pixels at a time. + * @return {Slider} a vertical slider control + */ +YAHOO.widget.Slider.getVertSlider = + function (sBGElId, sHandleElId, iUp, iDown, iTickSize) { + return new YAHOO.widget.Slider(sBGElId, sBGElId, + new YAHOO.widget.SliderThumb(sHandleElId, sBGElId, 0, 0, + iUp, iDown, iTickSize), "vert"); +}; + +/** + * Factory method for creating a slider region like the one in the color + * picker example + * @method YAHOO.widget.Slider.getSliderRegion + * @static + * @param {String} sBGElId the id of the slider's background element + * @param {String} sHandleElId the id of the thumb element + * @param {int} iLeft the number of pixels the element can move left + * @param {int} iRight the number of pixels the element can move right + * @param {int} iUp the number of pixels the element can move up + * @param {int} iDown the number of pixels the element can move down + * @param {int} iTickSize optional parameter for specifying that the element + * should move a certain number pixels at a time. + * @return {Slider} a slider region control + */ +YAHOO.widget.Slider.getSliderRegion = + function (sBGElId, sHandleElId, iLeft, iRight, iUp, iDown, iTickSize) { + return new YAHOO.widget.Slider(sBGElId, sBGElId, + new YAHOO.widget.SliderThumb(sHandleElId, sBGElId, iLeft, iRight, + iUp, iDown, iTickSize), "region"); +}; + +/** + * By default, animation is available if the animation library is detected. + * @property YAHOO.widget.Slider.ANIM_AVAIL + * @static + * @type boolean + */ +YAHOO.widget.Slider.ANIM_AVAIL = true; + +YAHOO.extend(YAHOO.widget.Slider, YAHOO.util.DragDrop, { + + onAvailable: function() { + var Event = YAHOO.util.Event; + Event.on(this.id, "keydown", this.handleKeyDown, this, true); + Event.on(this.id, "keypress", this.handleKeyPress, this, true); + }, + + handleKeyPress: function(e) { + if (this.enableKeys) { + var Event = YAHOO.util.Event; + var kc = Event.getCharCode(e); + switch (kc) { + case 0x25: // left + case 0x26: // up + case 0x27: // right + case 0x28: // down + case 0x24: // home + case 0x23: // end + Event.preventDefault(e); + break; + default: + } + } + }, + + handleKeyDown: function(e) { + if (this.enableKeys) { + var Event = YAHOO.util.Event; + + var kc = Event.getCharCode(e), t=this.thumb; + var h=this.getXValue(),v=this.getYValue(); + + var horiz = false; + var changeValue = true; + switch (kc) { + + // left + case 0x25: h -= this.keyIncrement; break; + + // up + case 0x26: v -= this.keyIncrement; break; + + // right + case 0x27: h += this.keyIncrement; break; + + // down + case 0x28: v += this.keyIncrement; break; + + // home + case 0x24: h = t.leftConstraint; + v = t.topConstraint; + break; + + // end + case 0x23: h = t.rightConstraint; + v = t.bottomConstraint; + break; + + default: changeValue = false; + } + + if (changeValue) { + if (t._isRegion) { + this.setRegionValue(h, v, true); + } else { + var newVal = (t._isHoriz) ? h : v; + this.setValue(newVal, true); + } + Event.stopEvent(e); + } + + } + }, + + /** + * Initialization that sets up the value offsets once the elements are ready + * @method setSliderStartState + */ + setStartSliderState: function() { + + + this.setThumbCenterPoint(); + + /** + * The basline position of the background element, used + * to determine if the background has moved since the last + * operation. + * @property baselinePos + * @type [int, int] + */ + this.baselinePos = YAHOO.util.Dom.getXY(this.getEl()); + + this.thumb.startOffset = this.thumb.getOffsetFromParent(this.baselinePos); + + if (this.thumb._isRegion) { + if (this.deferredSetRegionValue) { + this.setRegionValue.apply(this, this.deferredSetRegionValue, true); + this.deferredSetRegionValue = null; + } else { + this.setRegionValue(0, 0, true); + } + } else { + if (this.deferredSetValue) { + this.setValue.apply(this, this.deferredSetValue, true); + this.deferredSetValue = null; + } else { + this.setValue(0, true, true); + } + } + }, + + /** + * When the thumb is available, we cache the centerpoint of the element so + * we can position the element correctly when the background is clicked + * @method setThumbCenterPoint + */ + setThumbCenterPoint: function() { + + var el = this.thumb.getEl(); + + if (el) { + /** + * The center of the slider element is stored so we can position + * place it in the correct position when the background is clicked + * @property thumbCenterPoint + * @type {"x": int, "y": int} + */ + this.thumbCenterPoint = { + x: parseInt(el.offsetWidth/2, 10), + y: parseInt(el.offsetHeight/2, 10) + }; + } + + }, + + /** + * Locks the slider, overrides YAHOO.util.DragDrop + * @method lock + */ + lock: function() { + this.thumb.lock(); + this.locked = true; + }, + + /** + * Unlocks the slider, overrides YAHOO.util.DragDrop + * @method unlock + */ + unlock: function() { + this.thumb.unlock(); + this.locked = false; + }, + + /** + * Handles mouseup event on the slider background + * @method thumbMouseUp + * @private + */ + thumbMouseUp: function() { + if (!this.isLocked() && !this.moveComplete) { + this.endMove(); + } + + }, + + /** + * Returns a reference to this slider's thumb + * @method getThumb + * @return {SliderThumb} this slider's thumb + */ + getThumb: function() { + return this.thumb; + }, + + /** + * Try to focus the element when clicked so we can add + * accessibility features + * @method focus + * @private + */ + focus: function() { + + // Focus the background element if possible + var el = this.getEl(); + + if (el.focus) { + try { + el.focus(); + } catch(e) { + // Prevent permission denied unhandled exception in FF that can + // happen when setting focus while another element is handling + // the blur. @TODO this is still writing to the error log + // (unhandled error) in FF1.5 with strict error checking on. + } + } + + this.verifyOffset(); + + if (this.isLocked()) { + return false; + } else { + this.onSlideStart(); + return true; + } + }, + + /** + * Event that fires when the value of the slider has changed + * @method onChange + * @param {int} firstOffset the number of pixels the thumb has moved + * from its start position. Normal horizontal and vertical sliders will only + * have the firstOffset. Regions will have both, the first is the horizontal + * offset, the second the vertical. + * @param {int} secondOffset the y offset for region sliders + * @deprecated use instance.subscribe("change") instead + */ + onChange: function (firstOffset, secondOffset) { + /* override me */ + }, + + /** + * Event that fires when the at the beginning of the slider thumb move + * @method onSlideStart + * @deprecated use instance.subscribe("slideStart") instead + */ + onSlideStart: function () { + /* override me */ + }, + + /** + * Event that fires at the end of a slider thumb move + * @method onSliderEnd + * @deprecated use instance.subscribe("slideEnd") instead + */ + onSlideEnd: function () { + /* override me */ + }, + + /** + * Returns the slider's thumb offset from the start position + * @method getValue + * @return {int} the current value + */ + getValue: function () { + return this.thumb.getValue(); + }, + + /** + * Returns the slider's thumb X offset from the start position + * @method getXValue + * @return {int} the current horizontal offset + */ + getXValue: function () { + return this.thumb.getXValue(); + }, + + /** + * Returns the slider's thumb Y offset from the start position + * @method getYValue + * @return {int} the current vertical offset + */ + getYValue: function () { + return this.thumb.getYValue(); + }, + + /** + * Internal handler for the slider thumb's onChange event + * @method handleThumbChange + * @private + */ + handleThumbChange: function () { + var t = this.thumb; + if (t._isRegion) { + t.onChange(t.getXValue(), t.getYValue()); + this.fireEvent("change", { x: t.getXValue(), y: t.getYValue() } ); + } else { + t.onChange(t.getValue()); + this.fireEvent("change", t.getValue()); + } + + }, + + /** + * Provides a way to set the value of the slider in code. + * @method setValue + * @param {int} newOffset the number of pixels the thumb should be + * positioned away from the initial start point + * @param {boolean} skipAnim set to true to disable the animation + * for this move action (but not others). + * @param {boolean} force ignore the locked setting and set value anyway + * @return {boolean} true if the move was performed, false if it failed + */ + setValue: function(newOffset, skipAnim, force) { + + if (!this.thumb.available) { + this.deferredSetValue = arguments; + return false; + } + + if (this.isLocked() && !force) { + return false; + } + + if ( isNaN(newOffset) ) { + return false; + } + + var t = this.thumb; + var newX, newY; + this.verifyOffset(); + if (t._isRegion) { + return false; + } else if (t._isHoriz) { + this.onSlideStart(); + // this.fireEvent("slideStart"); + newX = t.initPageX + newOffset + this.thumbCenterPoint.x; + this.moveThumb(newX, t.initPageY, skipAnim); + } else { + this.onSlideStart(); + // this.fireEvent("slideStart"); + newY = t.initPageY + newOffset + this.thumbCenterPoint.y; + this.moveThumb(t.initPageX, newY, skipAnim); + } + + return true; + }, + + /** + * Provides a way to set the value of the region slider in code. + * @method setRegionValue + * @param {int} newOffset the number of pixels the thumb should be + * positioned away from the initial start point (x axis for region) + * @param {int} newOffset2 the number of pixels the thumb should be + * positioned away from the initial start point (y axis for region) + * @param {boolean} skipAnim set to true to disable the animation + * for this move action (but not others). + * @param {boolean} force ignore the locked setting and set value anyway + * @return {boolean} true if the move was performed, false if it failed + */ + setRegionValue: function(newOffset, newOffset2, skipAnim) { + + if (!this.thumb.available) { + this.deferredSetRegionValue = arguments; + return false; + } + + if (this.isLocked() && !force) { + return false; + } + + if ( isNaN(newOffset) ) { + return false; + } + + var t = this.thumb; + if (t._isRegion) { + this.onSlideStart(); + var newX = t.initPageX + newOffset + this.thumbCenterPoint.x; + var newY = t.initPageY + newOffset2 + this.thumbCenterPoint.y; + this.moveThumb(newX, newY, skipAnim); + return true; + } + + return false; + + }, + + /** + * Checks the background position element position. If it has moved from the + * baseline position, the constraints for the thumb are reset + * @method verifyOffset + * @return {boolean} True if the offset is the same as the baseline. + */ + verifyOffset: function() { + + var newPos = YAHOO.util.Dom.getXY(this.getEl()); + + if (newPos[0] != this.baselinePos[0] || newPos[1] != this.baselinePos[1]) { + this.thumb.resetConstraints(); + this.baselinePos = newPos; + return false; + } + + return true; + }, + + /** + * Move the associated slider moved to a timeout to try to get around the + * mousedown stealing moz does when I move the slider element between the + * cursor and the background during the mouseup event + * @method moveThumb + * @param {int} x the X coordinate of the click + * @param {int} y the Y coordinate of the click + * @param {boolean} skipAnim don't animate if the move happend onDrag + * @private + */ + moveThumb: function(x, y, skipAnim) { + + + var t = this.thumb; + var self = this; + + if (!t.available) { + return; + } + + + // this.verifyOffset(); + + t.setDelta(this.thumbCenterPoint.x, this.thumbCenterPoint.y); + + var _p = t.getTargetCoord(x, y); + var p = [_p.x, _p.y]; + + this.fireEvent("slideStart"); + + if (this.animate && YAHOO.widget.Slider.ANIM_AVAIL && t._graduated && !skipAnim) { + // this.thumb._animating = true; + this.lock(); + + setTimeout( function() { self.moveOneTick(p); }, this.tickPause ); + + } else if (this.animate && YAHOO.widget.Slider.ANIM_AVAIL && !skipAnim) { + + // this.thumb._animating = true; + this.lock(); + + var oAnim = new YAHOO.util.Motion( + t.id, { points: { to: p } }, + this.animationDuration, + YAHOO.util.Easing.easeOut ); + + oAnim.onComplete.subscribe( function() { self.endMove(); } ); + oAnim.animate(); + } else { + t.setDragElPos(x, y); + // this.fireEvents(); + this.endMove(); + } + }, + + /** + * Move the slider one tick mark towards its final coordinate. Used + * for the animation when tick marks are defined + * @method moveOneTick + * @param {int[]} the destination coordinate + * @private + */ + moveOneTick: function(finalCoord) { + + var t = this.thumb; + var curCoord = YAHOO.util.Dom.getXY(t.getEl()); + var tmp; + + // var thresh = Math.min(t.tickSize + (Math.floor(t.tickSize/2)), 10); + // var thresh = 10; + // var thresh = t.tickSize + (Math.floor(t.tickSize/2)); + + var nextCoord = null; + + if (t._isRegion) { + nextCoord = this._getNextX(curCoord, finalCoord); + var tmpX = (nextCoord) ? nextCoord[0] : curCoord[0]; + nextCoord = this._getNextY([tmpX, curCoord[1]], finalCoord); + + } else if (t._isHoriz) { + nextCoord = this._getNextX(curCoord, finalCoord); + } else { + nextCoord = this._getNextY(curCoord, finalCoord); + } + + + if (nextCoord) { + + // move to the next coord + // YAHOO.util.Dom.setXY(t.getEl(), nextCoord); + + // var el = t.getEl(); + // YAHOO.util.Dom.setStyle(el, "left", (nextCoord[0] + this.thumb.deltaSetXY[0]) + "px"); + // YAHOO.util.Dom.setStyle(el, "top", (nextCoord[1] + this.thumb.deltaSetXY[1]) + "px"); + + this.thumb.alignElWithMouse(t.getEl(), nextCoord[0], nextCoord[1]); + + // check if we are in the final position, if not make a recursive call + if (!(nextCoord[0] == finalCoord[0] && nextCoord[1] == finalCoord[1])) { + var self = this; + setTimeout(function() { self.moveOneTick(finalCoord); }, + this.tickPause); + } else { + this.endMove(); + } + } else { + this.endMove(); + } + + //this.tickPause = Math.round(this.tickPause/2); + }, + + /** + * Returns the next X tick value based on the current coord and the target coord. + * @method _getNextX + * @private + */ + _getNextX: function(curCoord, finalCoord) { + var t = this.thumb; + var thresh; + var tmp = []; + var nextCoord = null; + if (curCoord[0] > finalCoord[0]) { + thresh = t.tickSize - this.thumbCenterPoint.x; + tmp = t.getTargetCoord( curCoord[0] - thresh, curCoord[1] ); + nextCoord = [tmp.x, tmp.y]; + } else if (curCoord[0] < finalCoord[0]) { + thresh = t.tickSize + this.thumbCenterPoint.x; + tmp = t.getTargetCoord( curCoord[0] + thresh, curCoord[1] ); + nextCoord = [tmp.x, tmp.y]; + } else { + // equal, do nothing + } + + return nextCoord; + }, + + /** + * Returns the next Y tick value based on the current coord and the target coord. + * @method _getNextY + * @private + */ + _getNextY: function(curCoord, finalCoord) { + var t = this.thumb; + var thresh; + var tmp = []; + var nextCoord = null; + + if (curCoord[1] > finalCoord[1]) { + thresh = t.tickSize - this.thumbCenterPoint.y; + tmp = t.getTargetCoord( curCoord[0], curCoord[1] - thresh ); + nextCoord = [tmp.x, tmp.y]; + } else if (curCoord[1] < finalCoord[1]) { + thresh = t.tickSize + this.thumbCenterPoint.y; + tmp = t.getTargetCoord( curCoord[0], curCoord[1] + thresh ); + nextCoord = [tmp.x, tmp.y]; + } else { + // equal, do nothing + } + + return nextCoord; + }, + + /** + * Resets the constraints before moving the thumb. + * @method b4MouseDown + * @private + */ + b4MouseDown: function(e) { + this.thumb.autoOffset(); + this.thumb.resetConstraints(); + }, + + /** + * Handles the mousedown event for the slider background + * @method onMouseDown + * @private + */ + onMouseDown: function(e) { + // this.resetConstraints(true); + // this.thumb.resetConstraints(true); + + if (! this.isLocked() && this.backgroundEnabled) { + var x = YAHOO.util.Event.getPageX(e); + var y = YAHOO.util.Event.getPageY(e); + + this.focus(); + this.moveThumb(x, y); + } + + }, + + /** + * Handles the onDrag event for the slider background + * @method onDrag + * @private + */ + onDrag: function(e) { + if (! this.isLocked()) { + var x = YAHOO.util.Event.getPageX(e); + var y = YAHOO.util.Event.getPageY(e); + this.moveThumb(x, y, true); + } + }, + + /** + * Fired when the slider movement ends + * @method endMove + * @private + */ + endMove: function () { + // this._animating = false; + this.unlock(); + this.moveComplete = true; + this.fireEvents(); + }, + + /** + * Fires the change event if the value has been changed. Ignored if we are in + * the middle of an animation as the event will fire when the animation is + * complete + * @method fireEvents + * @private + */ + fireEvents: function () { + + var t = this.thumb; + + t.cachePosition(); + + if (! this.isLocked()) { + if (t._isRegion) { + var newX = t.getXValue(); + var newY = t.getYValue(); + + if (newX != this.previousX || newY != this.previousY) { + this.onChange(newX, newY); + this.fireEvent("change", { x: newX, y: newY }); + } + + this.previousX = newX; + this.previousY = newY; + + } else { + var newVal = t.getValue(); + if (newVal != this.previousVal) { + this.onChange( newVal ); + this.fireEvent("change", newVal); + } + this.previousVal = newVal; + } + + if (this.moveComplete) { + this.onSlideEnd(); + this.fireEvent("slideEnd"); + this.moveComplete = false; + } + + } + }, + + /** + * Slider toString + * @method toString + * @return {string} string representation of the instance + */ + toString: function () { + return ("Slider (" + this.type +") " + this.id); + } + +}); + +YAHOO.augment(YAHOO.widget.Slider, YAHOO.util.EventProvider); + +/** + * A drag and drop implementation to be used as the thumb of a slider. + * @class SliderThumb + * @extends YAHOO.util.DD + * @constructor + * @param {String} id the id of the slider html element + * @param {String} sGroup the group of related DragDrop items + * @param {int} iLeft the number of pixels the element can move left + * @param {int} iRight the number of pixels the element can move right + * @param {int} iUp the number of pixels the element can move up + * @param {int} iDown the number of pixels the element can move down + * @param {int} iTickSize optional parameter for specifying that the element + * should move a certain number pixels at a time. + */ +YAHOO.widget.SliderThumb = function(id, sGroup, iLeft, iRight, iUp, iDown, iTickSize) { + + if (id) { + this.init(id, sGroup); + + /** + * The id of the thumbs parent HTML element (the slider background + * element). + * @property parentElId + * @type string + */ + this.parentElId = sGroup; + } + + //this.removeInvalidHandleType("A"); + + + /** + * Overrides the isTarget property in YAHOO.util.DragDrop + * @property isTarget + * @private + */ + this.isTarget = false; + + /** + * The tick size for this slider + * @property tickSize + * @type int + * @private + */ + this.tickSize = iTickSize; + + /** + * Informs the drag and drop util that the offsets should remain when + * resetting the constraints. This preserves the slider value when + * the constraints are reset + * @property maintainOffset + * @type boolean + * @private + */ + this.maintainOffset = true; + + this.initSlider(iLeft, iRight, iUp, iDown, iTickSize); + + /** + * Turns off the autoscroll feature in drag and drop + * @property scroll + * @private + */ + this.scroll = false; + +}; + +YAHOO.extend(YAHOO.widget.SliderThumb, YAHOO.util.DD, { + + /** + * The (X and Y) difference between the thumb location and its parent + * (the slider background) when the control is instantiated. + * @property startOffset + * @type [int, int] + */ + startOffset: null, + + /** + * Flag used to figure out if this is a horizontal or vertical slider + * @property _isHoriz + * @type boolean + * @private + */ + _isHoriz: false, + + /** + * Cache the last value so we can check for change + * @property _prevVal + * @type int + * @private + */ + _prevVal: 0, + + /** + * The slider is _graduated if there is a tick interval defined + * @property _graduated + * @type boolean + * @private + */ + _graduated: false, + + /** + * Returns the difference between the location of the thumb and its parent. + * @method getOffsetFromParent + * @param {[int, int]} parentPos Optionally accepts the position of the parent + * @type [int, int] + */ + getOffsetFromParent: function(parentPos) { + var myPos = YAHOO.util.Dom.getXY(this.getEl()); + var ppos = parentPos || YAHOO.util.Dom.getXY(this.parentElId); + + return [ (myPos[0] - ppos[0]), (myPos[1] - ppos[1]) ]; + }, + + /** + * Set up the slider, must be called in the constructor of all subclasses + * @method initSlider + * @param {int} iLeft the number of pixels the element can move left + * @param {int} iRight the number of pixels the element can move right + * @param {int} iUp the number of pixels the element can move up + * @param {int} iDown the number of pixels the element can move down + * @param {int} iTickSize the width of the tick interval. + */ + initSlider: function (iLeft, iRight, iUp, iDown, iTickSize) { + + this.setXConstraint(iLeft, iRight, iTickSize); + this.setYConstraint(iUp, iDown, iTickSize); + + if (iTickSize && iTickSize > 1) { + this._graduated = true; + } + + this._isHoriz = (iLeft || iRight); + this._isVert = (iUp || iDown); + this._isRegion = (this._isHoriz && this._isVert); + + }, + + /** + * Clear's the slider's ticks + * @method clearTicks + */ + clearTicks: function () { + YAHOO.widget.SliderThumb.superclass.clearTicks.call(this); + this._graduated = false; + }, + + /** + * Gets the current offset from the element's start position in + * pixels. + * @method getValue + * @return {int} the number of pixels (positive or negative) the + * slider has moved from the start position. + */ + getValue: function () { + if (!this.available) { return 0; } + var val = (this._isHoriz) ? this.getXValue() : this.getYValue(); + return val; + }, + + /** + * Gets the current X offset from the element's start position in + * pixels. + * @method getXValue + * @return {int} the number of pixels (positive or negative) the + * slider has moved horizontally from the start position. + */ + getXValue: function () { + if (!this.available) { return 0; } + var newOffset = this.getOffsetFromParent(); + return (newOffset[0] - this.startOffset[0]); + }, + + /** + * Gets the current Y offset from the element's start position in + * pixels. + * @method getYValue + * @return {int} the number of pixels (positive or negative) the + * slider has moved vertically from the start position. + */ + getYValue: function () { + if (!this.available) { return 0; } + var newOffset = this.getOffsetFromParent(); + return (newOffset[1] - this.startOffset[1]); + }, + + /** + * Thumb toString + * @method toString + * @return {string} string representation of the instance + */ + toString: function () { + return "SliderThumb " + this.id; + }, + + /** + * The onchange event for the handle/thumb is delegated to the YAHOO.widget.Slider + * instance it belongs to. + * @method onChange + * @private + */ + onChange: function (x, y) { + } + +}); + +if ("undefined" == typeof YAHOO.util.Anim) { + YAHOO.widget.Slider.ANIM_AVAIL = false; +} + diff --git a/frontend/beta/js/YUI/tabview.js b/frontend/beta/js/YUI/tabview.js new file mode 100644 index 0000000..34a09bb --- a/dev/null +++ b/frontend/beta/js/YUI/tabview.js @@ -0,0 +1,1950 @@ +/* +Copyright (c) 2006, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.net/yui/license.txt +version: 0.12.0 +*/ +(function() { + + YAHOO.util.Lang = { + isArray: function(val) { // frames lose type, so test constructor string + if (val.constructor && val.constructor.toString().indexOf('Array') > -1) { + return true; + } else { + return YAHOO.util.Lang.isObject(val) && val.constructor == Array; + } + }, + + isBoolean: function(val) { + return typeof val == 'boolean'; + }, + + isFunction: function(val) { + return typeof val == 'function'; + }, + + isNull: function(val) { + return val === null; + }, + + isNumber: function(val) { + return !isNaN(val); + }, + + isObject: function(val) { + return typeof val == 'object' || YAHOO.util.Lang.isFunction(val); + }, + + isString: function(val) { + return typeof val == 'string'; + }, + + isUndefined: function(val) { + return typeof val == 'undefined'; + } + }; +})();/** + * Provides Attribute configurations. + * @namespace YAHOO.util + * @class Attribute + * @constructor + * @param hash {Object} The intial Attribute. + * @param {YAHOO.util.AttributeProvider} The owner of the Attribute instance. + */ + +YAHOO.util.Attribute = function(hash, owner) { + if (owner) { + this.owner = owner; + this.configure(hash, true); + } +}; + +YAHOO.util.Attribute.prototype = { + /** + * The name of the attribute. + * @property name + * @type String + */ + name: undefined, + + /** + * The value of the attribute. + * @property value + * @type String + */ + value: null, + + /** + * The owner of the attribute. + * @property owner + * @type YAHOO.util.AttributeProvider + */ + owner: null, + + /** + * Whether or not the attribute is read only. + * @property readOnly + * @type Boolean + */ + readOnly: false, + + /** + * Whether or not the attribute can only be written once. + * @property writeOnce + * @type Boolean + */ + writeOnce: false, + + /** + * The attribute's initial configuration. + * @private + * @property _initialConfig + * @type Object + */ + _initialConfig: null, + + /** + * Whether or not the attribute's value has been set. + * @private + * @property _written + * @type Boolean + */ + _written: false, + + /** + * The method to use when setting the attribute's value. + * The method recieves the new value as the only argument. + * @property method + * @type Function + */ + method: null, + + /** + * The validator to use when setting the attribute's value. + * @property validator + * @type Function + * @return Boolean + */ + validator: null, + + /** + * Retrieves the current value of the attribute. + * @method getValue + * @return {any} The current value of the attribute. + */ + getValue: function() { + return this.value; + }, + + /** + * Sets the value of the attribute and fires beforeChange and change events. + * @method setValue + * @param {Any} value The value to apply to the attribute. + * @param {Boolean} silent If true the change events will not be fired. + * @return {Boolean} Whether or not the value was set. + */ + setValue: function(value, silent) { + var beforeRetVal; + var owner = this.owner; + var name = this.name; + + var event = { + type: name, + prevValue: this.getValue(), + newValue: value + }; + + if (this.readOnly || ( this.writeOnce && this._written) ) { + return false; // write not allowed + } + + if (this.validator && !this.validator.call(owner, value) ) { + return false; // invalid value + } + + if (!silent) { + beforeRetVal = owner.fireBeforeChangeEvent(event); + if (beforeRetVal === false) { + YAHOO.log('setValue ' + name + + 'cancelled by beforeChange event', 'info', 'Attribute'); + return false; + } + } + + if (this.method) { + this.method.call(owner, value); + } + + this.value = value; + this._written = true; + + event.type = name; + + if (!silent) { + this.owner.fireChangeEvent(event); + } + + return true; + }, + + /** + * Allows for configuring the Attribute's properties. + * @method configure + * @param {Object} map A key-value map of Attribute properties. + * @param {Boolean} init Whether or not this should become the initial config. + */ + configure: function(map, init) { + map = map || {}; + this._written = false; // reset writeOnce + this._initialConfig = this._initialConfig || {}; + + for (var key in map) { + if ( key && map.hasOwnProperty(key) ) { + this[key] = map[key]; + if (init) { + this._initialConfig[key] = map[key]; + } + } + } + }, + + /** + * Resets the value to the initial config value. + * @method resetValue + * @return {Boolean} Whether or not the value was set. + */ + resetValue: function() { + return this.setValue(this._initialConfig.value); + }, + + /** + * Resets the attribute config to the initial config state. + * @method resetConfig + */ + resetConfig: function() { + this.configure(this._initialConfig); + }, + + /** + * Resets the value to the current value. + * Useful when values may have gotten out of sync with actual properties. + * @method refresh + * @return {Boolean} Whether or not the value was set. + */ + refresh: function(silent) { + this.setValue(this.value, silent); + } +};(function() { + var Lang = YAHOO.util.Lang; + + /* + Copyright (c) 2006, Yahoo! Inc. All rights reserved. + Code licensed under the BSD License: + http://developer.yahoo.net/yui/license.txt + */ + + /** + * Provides and manages YAHOO.util.Attribute instances + * @namespace YAHOO.util + * @class AttributeProvider + * @uses YAHOO.util.EventProvider + */ + YAHOO.util.AttributeProvider = function() {}; + + YAHOO.util.AttributeProvider.prototype = { + + /** + * A key-value map of Attribute configurations + * @property _configs + * @protected (may be used by subclasses and augmentors) + * @private + * @type {Object} + */ + _configs: null, + /** + * Returns the current value of the attribute. + * @method get + * @param {String} key The attribute whose value will be returned. + */ + get: function(key){ + var configs = this._configs || {}; + var config = configs[key]; + + if (!config) { + YAHOO.log(key + ' not found', 'error', 'AttributeProvider'); + return undefined; + } + + return config.value; + }, + + /** + * Sets the value of a config. + * @method set + * @param {String} key The name of the attribute + * @param {Any} value The value to apply to the attribute + * @param {Boolean} silent Whether or not to suppress change events + * @return {Boolean} Whether or not the value was set. + */ + set: function(key, value, silent){ + var configs = this._configs || {}; + var config = configs[key]; + + if (!config) { + YAHOO.log('set failed: ' + key + ' not found', + 'error', 'AttributeProvider'); + return false; + } + + return config.setValue(value, silent); + }, + + /** + * Returns an array of attribute names. + * @method getAttributeKeys + * @return {Array} An array of attribute names. + */ + getAttributeKeys: function(){ + var configs = this._configs; + var keys = []; + var config; + for (var key in configs) { + config = configs[key]; + if ( configs.hasOwnProperty(key) && + !Lang.isUndefined(config) ) { + keys[keys.length] = key; + } + } + + return keys; + }, + + /** + * Sets multiple attribute values. + * @method setAttributes + * @param {Object} map A key-value map of attributes + * @param {Boolean} silent Whether or not to suppress change events + */ + setAttributes: function(map, silent){ + for (var key in map) { + if ( map.hasOwnProperty(key) ) { + this.set(key, map[key], silent); + } + } + }, + + /** + * Resets the specified attribute's value to its initial value. + * @method resetValue + * @param {String} key The name of the attribute + * @param {Boolean} silent Whether or not to suppress change events + * @return {Boolean} Whether or not the value was set + */ + resetValue: function(key, silent){ + var configs = this._configs || {}; + if (configs[key]) { + this.set(key, configs[key]._initialConfig.value, silent); + return true; + } + return false; + }, + + /** + * Sets the attribute's value to its current value. + * @method refresh + * @param {String | Array} key The attribute(s) to refresh + * @param {Boolean} silent Whether or not to suppress change events + */ + refresh: function(key, silent){ + var configs = this._configs; + + key = ( ( Lang.isString(key) ) ? [key] : key ) || + this.getAttributeKeys(); + + for (var i = 0, len = key.length; i < len; ++i) { + if ( // only set if there is a value and not null + configs[key[i]] && + ! Lang.isUndefined(configs[key[i]].value) && + ! Lang.isNull(configs[key[i]].value) ) { + configs[key[i]].refresh(silent); + } + } + }, + + /** + * Adds an Attribute to the AttributeProvider instance. + * @method register + * @param {String} key The attribute's name + * @param {Object} map A key-value map containing the + * attribute's properties. + */ + register: function(key, map) { + this._configs = this._configs || {}; + + if (this._configs[key]) { // dont override + return false; + } + + map.name = key; + this._configs[key] = new YAHOO.util.Attribute(map, this); + return true; + }, + + /** + * Returns the attribute's properties. + * @method getAttributeConfig + * @param {String} key The attribute's name + * @private + * @return {object} A key-value map containing all of the + * attribute's properties. + */ + getAttributeConfig: function(key) { + var configs = this._configs || {}; + var config = configs[key] || {}; + var map = {}; // returning a copy to prevent overrides + + for (key in config) { + if ( config.hasOwnProperty(key) ) { + map[key] = config[key]; + } + } + + return map; + }, + + /** + * Sets or updates an Attribute instance's properties. + * @method configureAttribute + * @param {String} key The attribute's name. + * @param {Object} map A key-value map of attribute properties + * @param {Boolean} init Whether or not this should become the intial config. + */ + configureAttribute: function(key, map, init) { + var configs = this._configs || {}; + + if (!configs[key]) { + YAHOO.log('unable to configure, ' + key + ' not found', + 'error', 'AttributeProvider'); + return false; + } + + configs[key].configure(map, init); + }, + + /** + * Resets an attribute to its intial configuration. + * @method resetAttributeConfig + * @param {String} key The attribute's name. + * @private + */ + resetAttributeConfig: function(key){ + var configs = this._configs || {}; + configs[key].resetConfig(); + }, + + /** + * Fires the attribute's beforeChange event. + * @method fireBeforeChangeEvent + * @param {String} key The attribute's name. + * @param {Obj} e The event object to pass to handlers. + */ + fireBeforeChangeEvent: function(e) { + var type = 'before'; + type += e.type.charAt(0).toUpperCase() + e.type.substr(1) + 'Change'; + e.type = type; + return this.fireEvent(e.type, e); + }, + + /** + * Fires the attribute's change event. + * @method fireChangeEvent + * @param {String} key The attribute's name. + * @param {Obj} e The event object to pass to the handlers. + */ + fireChangeEvent: function(e) { + e.type += 'Change'; + return this.fireEvent(e.type, e); + } + }; + + YAHOO.augment(YAHOO.util.AttributeProvider, YAHOO.util.EventProvider); +})();(function() { +// internal shorthand +var Dom = YAHOO.util.Dom, + Lang = YAHOO.util.Lang, + EventPublisher = YAHOO.util.EventPublisher, + AttributeProvider = YAHOO.util.AttributeProvider; + +/** + * Element provides an interface to an HTMLElement's attributes and common + * methods. Other commonly used attributes are added as well. + * @namespace YAHOO.util + * @class Element + * @uses YAHOO.util.AttributeProvider + * @constructor + * @param el {HTMLElement | String} The html element that + * represents the Element. + * @param {Object} map A key-value map of initial config names and values + */ +YAHOO.util.Element = function(el, map) { + if (arguments.length) { + this.init(el, map); + } +}; + +YAHOO.util.Element.prototype = { + /** + * Dom events supported by the Element instance. + * @property DOM_EVENTS + * @type Object + */ + DOM_EVENTS: null, + + /** + * Wrapper for HTMLElement method. + * @method appendChild + * @param {Boolean} deep Whether or not to do a deep clone + */ + appendChild: function(child) { + child = child.get ? child.get('element') : child; + this.get('element').appendChild(child); + }, + + /** + * Wrapper for HTMLElement method. + * @method getElementsByTagName + * @param {String} tag The tagName to collect + */ + getElementsByTagName: function(tag) { + return this.get('element').getElementsByTagName(tag); + }, + + /** + * Wrapper for HTMLElement method. + * @method hasChildNodes + * @return {Boolean} Whether or not the element has childNodes + */ + hasChildNodes: function() { + return this.get('element').hasChildNodes(); + }, + + /** + * Wrapper for HTMLElement method. + * @method insertBefore + * @param {HTMLElement} element The HTMLElement to insert + * @param {HTMLElement} before The HTMLElement to insert + * the element before. + */ + insertBefore: function(element, before) { + element = element.get ? element.get('element') : element; + before = (before && before.get) ? before.get('element') : before; + + this.get('element').insertBefore(element, before); + }, + + /** + * Wrapper for HTMLElement method. + * @method removeChild + * @param {HTMLElement} child The HTMLElement to remove + */ + removeChild: function(child) { + child = child.get ? child.get('element') : child; + this.get('element').removeChild(child); + return true; + }, + + /** + * Wrapper for HTMLElement method. + * @method replaceChild + * @param {HTMLElement} newNode The HTMLElement to insert + * @param {HTMLElement} oldNode The HTMLElement to replace + */ + replaceChild: function(newNode, oldNode) { + newNode = newNode.get ? newNode.get('element') : newNode; + oldNode = oldNode.get ? oldNode.get('element') : oldNode; + return this.get('element').replaceChild(newNode, oldNode); + }, + + + /** + * Registers Element specific attributes. + * @method initAttributes + * @param {Object} map A key-value map of initial attribute configs + */ + initAttributes: function(map) { + map = map || {}; + var element = Dom.get(map.element) || null; + + /** + * The HTMLElement the Element instance refers to. + * @config element + * @type HTMLElement + */ + this.register('element', { + value: element, + readOnly: true + }); + }, + + /** + * Adds a listener for the given event. These may be DOM or + * customEvent listeners. Any event that is fired via fireEvent + * can be listened for. All handlers receive an event object. + * @method addListener + * @param {String} type The name of the event to listen for + * @param {Function} fn The handler to call when the event fires + * @param {Any} obj A variable to pass to the handler + * @param {Object} scope The object to use for the scope of the handler + */ + addListener: function(type, fn, obj, scope) { + var el = this.get('element'); + var scope = scope || this; + + el = this.get('id') || el; + + if (!this._events[type]) { // create on the fly + if ( this.DOM_EVENTS[type] ) { + YAHOO.util.Event.addListener(el, type, function(e) { + if (e.srcElement && !e.target) { // supplement IE with target + e.target = e.srcElement; + } + this.fireEvent(type, e); + }, obj, scope); + } + + this.createEvent(type, this); + this._events[type] = true; + } + + this.subscribe.apply(this, arguments); // notify via customEvent + }, + + + /** + * Alias for addListener + * @method on + * @param {String} type The name of the event to listen for + * @param {Function} fn The function call when the event fires + * @param {Any} obj A variable to pass to the handler + * @param {Object} scope The object to use for the scope of the handler + */ + on: function() { this.addListener.apply(this, arguments); }, + + + /** + * Remove an event listener + * @method removeListener + * @param {String} type The name of the event to listen for + * @param {Function} fn The function call when the event fires + */ + removeListener: function(type, fn) { + this.unsubscribe.apply(this, arguments); + }, + + /** + * Wrapper for Dom method. + * @method addClass + * @param {String} className The className to add + */ + addClass: function(className) { + Dom.addClass(this.get('element'), className); + }, + + /** + * Wrapper for Dom method. + * @method getElementsByClassName + * @param {String} className The className to collect + * @param {String} tag (optional) The tag to use in + * conjunction with class name + * @return {Array} Array of HTMLElements + */ + getElementsByClassName: function(className, tag) { + return Dom.getElementsByClassName(className, tag, + this.get('element') ); + }, + + /** + * Wrapper for Dom method. + * @method hasClass + * @param {String} className The className to add + * @return {Boolean} Whether or not the element has the class name + */ + hasClass: function(className) { + return Dom.hasClass(this.get('element'), className); + }, + + /** + * Wrapper for Dom method. + * @method removeClass + * @param {String} className The className to remove + */ + removeClass: function(className) { + return Dom.removeClass(this.get('element'), className); + }, + + /** + * Wrapper for Dom method. + * @method replaceClass + * @param {String} oldClassName The className to replace + * @param {String} newClassName The className to add + */ + replaceClass: function(oldClassName, newClassName) { + return Dom.replaceClass(this.get('element'), + oldClassName, newClassName); + }, + + /** + * Wrapper for Dom method. + * @method setStyle + * @param {String} property The style property to set + * @param {String} value The value to apply to the style property + */ + setStyle: function(property, value) { + return Dom.setStyle(this.get('element'), property, value); + }, + + /** + * Wrapper for Dom method. + * @method getStyle + * @param {String} property The style property to retrieve + * @return {String} The current value of the property + */ + getStyle: function(property) { + return Dom.getStyle(this.get('element'), property); + }, + + /** + * Apply any queued set calls. + * @method fireQueue + */ + fireQueue: function() { + var queue = this._queue; + for (var i = 0, len = queue.length; i < len; ++i) { + this[queue[i][0]].apply(this, queue[i][1]); + } + }, + + /** + * Appends the HTMLElement into either the supplied parentNode. + * @method appendTo + * @param {HTMLElement | Element} parentNode The node to append to + * @param {HTMLElement | Element} before An optional node to insert before + */ + appendTo: function(parent, before) { + parent = (parent.get) ? parent.get('element') : Dom.get(parent); + + before = (before && before.get) ? + before.get('element') : Dom.get(before); + var element = this.get('element'); + + var newAddition = !Dom.inDocument(element); + + if (!element) { + YAHOO.log('appendTo failed: element not available', + 'error', 'Element'); + return false; + } + + if (!parent) { + YAHOO.log('appendTo failed: parent not available', + 'error', 'Element'); + return false; + } + + if (element.parent != parent) { + if (before) { + parent.insertBefore(element, before); + } else { + parent.appendChild(element); + } + } + + YAHOO.log(element + 'appended to ' + parent); + + if (!newAddition) { + return false; // note return; no refresh if in document + } + + // if a new addition, refresh HTMLElement any applied attributes + var keys = this.getAttributeKeys(); + + for (var key in keys) { // only refresh HTMLElement attributes + if ( !Lang.isUndefined(element[key]) ) { + this.refresh(key); + } + } + }, + + get: function(key) { + var configs = this._configs || {}; + var el = configs.element; // avoid loop due to 'element' + if (el && !configs[key] && !Lang.isUndefined(el.value[key]) ) { + return el.value[key]; + } + + return AttributeProvider.prototype.get.call(this, key); + }, + + set: function(key, value, silent) { + var el = this.get('element'); + if (!el) { + this._queue[key] = ['set', arguments]; + return false; + } + + // set it on the element if not a property + if ( !this._configs[key] && !Lang.isUndefined(el[key]) ) { + _registerHTMLAttr(this, key); + } + + return AttributeProvider.prototype.set.apply(this, arguments); + }, + + register: function(key) { // protect html attributes + var configs = this._configs || {}; + var element = this.get('element') || null; + + if ( element && !Lang.isUndefined(element[key]) ) { + YAHOO.log(key + ' is reserved for ' + element, + 'error', 'Element'); + return false; + } + + return AttributeProvider.prototype.register.apply(this, arguments); + }, + + configureAttribute: function(property, map, init) { // protect html attributes + if (!this._configs[property] && this._configs.element && + !Lang.isUndefined(this._configs.element[property]) ) { + _registerHTMLAttr(this, property, map); + return false; + } + + return AttributeProvider.prototype.configure.apply(this, arguments); + }, + + getAttributeKeys: function() { + var el = this.get('element'); + var keys = AttributeProvider.prototype.getAttributeKeys.call(this); + + //add any unconfigured element keys + for (var key in el) { + if (!this._configs[key]) { + keys[key] = keys[key] || el[key]; + } + } + + return keys; + }, + + init: function(el, attr) { + this._queue = this._queue || []; + this._events = this._events || {}; + this._configs = this._configs || {}; + attr = attr || {}; + attr.element = attr.element || el || null; + + this.DOM_EVENTS = { + 'click': true, + 'keydown': true, + 'keypress': true, + 'keyup': true, + 'mousedown': true, + 'mousemove': true, + 'mouseout': true, + 'mouseover': true, + 'mouseup': true + }; + + var readyHandler = function() { + this.initAttributes(attr); + + this.setAttributes(attr, true); + this.fireQueue(); + this.fireEvent('contentReady', { + type: 'contentReady', + target: attr.element + }); + }; + + if ( Lang.isString(el) ) { + _registerHTMLAttr(this, 'id', { value: el }); + YAHOO.util.Event.onAvailable(el, function() { + attr.element = Dom.get(el); + this.fireEvent('available', { + type: 'available', + target: attr.element + }); + }, this, true); + + YAHOO.util.Event.onContentReady(el, function() { + readyHandler.call(this); + }, this, true); + } else { + readyHandler.call(this); + } + } +}; + +/** + * Sets the value of the property and fires beforeChange and change events. + * @private + * @method _registerHTMLAttr + * @param {YAHOO.util.Element} element The Element instance to + * register the config to. + * @param {String} key The name of the config to register + * @param {Object} map A key-value map of the config's params + */ +var _registerHTMLAttr = function(self, key, map) { + var el = self.get('element'); + map = map || {}; + map.name = key; + map.method = map.method || function(value) { + el[key] = value; + }; + map.value = map.value || el[key]; + self._configs[key] = new YAHOO.util.Attribute(map, self); +}; + +/** + * Fires when the Element's HTMLElement can be retrieved by Id. + *See: Element.addListener
+ *Event fields:
+ *<String> type
available
+ *<HTMLElement> + * target
the HTMLElement bound to this Element instance
+ *Usage:
+ * @event available + */ + +/** + * Fires when the Element's HTMLElement subtree is rendered. + *
+ *var handler = function(e) {var target = e.target};
+ * myTabs.addListener('available', handler);See: Element.addListener
+ *Event fields:
+ *<String> type
contentReady
+ *<HTMLElement> + * target
the HTMLElement bound to this Element instance
+ *Usage:
+ * @event contentReady + */ + +YAHOO.augment(YAHOO.util.Element, AttributeProvider); +})();(function() { + var Dom = YAHOO.util.Dom, + Event = YAHOO.util.Event, + Lang = YAHOO.util.Lang; + + /** + * A representation of a Tab's label and content. + * @namespace YAHOO.widget + * @class Tab + * @extends YAHOO.util.Element + * @constructor + * @param element {HTMLElement | String} (optional) The html element that + * represents the TabView. An element will be created if none provided. + * @param {Object} properties A key map of initial properties + */ + Tab = function(el, attr) { + attr = attr || {}; + if (arguments.length == 1 && !Lang.isString(el) && !el.nodeName) { + attr = el; + el = attr.element; + } + + if (!el && !attr.element) { + el = _createTabElement.call(this, attr); + } + + this.loadHandler = { + success: function(o) { + this.set('content', o.responseText); + }, + failure: function(o) { + YAHOO.log('loading failed: ' + o.statusText, + 'error', 'Tab'); + } + }; + + Tab.superclass.constructor.call(this, el, attr); + + this.DOM_EVENTS = {}; // delegating to tabView + }; + + YAHOO.extend(Tab, YAHOO.util.Element); + var proto = Tab.prototype; + + /** + * The default tag name for a Tab's inner element. + * @property LABEL_INNER_TAGNAME + * @type String + * @default "em" + */ + proto.LABEL_TAGNAME = 'em'; + + /** + * The class name applied to active tabs. + * @property ACTIVE_CLASSNAME + * @type String + * @default "on" + */ + proto.ACTIVE_CLASSNAME = 'selected'; + + /** + * The class name applied to disabled tabs. + * @property DISABLED_CLASSNAME + * @type String + * @default "disabled" + */ + proto.DISABLED_CLASSNAME = 'disabled'; + + /** + * The class name applied to dynamic tabs while loading. + * @property LOADING_CLASSNAME + * @type String + * @default "disabled" + */ + proto.LOADING_CLASSNAME = 'loading'; + + /** + * Provides a reference to the connection request object when data is + * loaded dynamically. + * @property dataConnection + * @type Object + */ + proto.dataConnection = null; + + /** + * Object containing success and failure callbacks for loading data. + * @property loadHandler + * @type object + */ + proto.loadHandler = null; + + /** + * Provides a readable name for the tab. + * @method toString + * @return String + */ + proto.toString = function() { + var el = this.get('element'); + var id = el.id || el.tagName; + return "Tab " + id; + }; + + /** + * Registers TabView specific properties. + * @method initAttributes + * @param {Object} attr Hash of initial attributes + */ + proto.initAttributes = function(attr) { + attr = attr || {}; + Tab.superclass.initAttributes.call(this, attr); + + var el = this.get('element'); + + /** + * The event that triggers the tab's activation. + * @config activationEvent + * @type String + */ + this.register('activationEvent', { + value: attr.activationEvent || 'click' + }); + + /** + * The element that contains the tab's label. + * @config labelEl + * @type HTMLElement + */ + this.register('labelEl', { + value: attr.labelEl || _getlabelEl.call(this), + method: function(value) { + var current = this.get('labelEl'); + + if (current) { + if (current == value) { + return false; // already set + } + + this.replaceChild(value, current); + } else if (el.firstChild) { // ensure label is firstChild by default + this.insertBefore(value, el.firstChild); + } else { + this.appendChild(value); + } + } + }); + + /** + * The tab's label text (or innerHTML). + * @config label + * @type String + */ + this.register('label', { + value: attr.label || _getLabel.call(this), + method: function(value) { + var labelEl = this.get('labelEl'); + if (!labelEl) { // create if needed + this.set('labelEl', _createlabelEl.call(this)); + } + + _setLabel.call(this, value); + } + }); + + /** + * The HTMLElement that contains the tab's content. + * @config contentEl + * @type HTMLElement + */ + this.register('contentEl', { // TODO: apply className? + value: attr.contentEl || document.createElement('div'), + method: function(value) { + var current = this.get('contentEl'); + + if (current) { + if (current == value) { + return false; // already set + } + this.replaceChild(value, current); + } + } + }); + + /** + * The tab's content. + * @config content + * @type String + */ + this.register('content', { + value: attr.content, // TODO: what about existing? + method: function(value) { + this.get('contentEl').innerHTML = value; + } + }); + + var _dataLoaded = false; + + /** + * The tab's data source, used for loading content dynamically. + * @config dataSrc + * @type String + */ + this.register('dataSrc', { + value: attr.dataSrc + }); + + /** + * Whether or not content should be reloaded for every view. + * @config cacheData + * @type Boolean + * @default false + */ + this.register('cacheData', { + value: attr.cacheData || false, + validator: Lang.isBoolean + }); + + /** + * The method to use for the data request. + * @config loadMethod + * @type String + * @default "GET" + */ + this.register('loadMethod', { + value: attr.loadMethod || 'GET', + validator: Lang.isString + }); + + /** + * Whether or not any data has been loaded from the server. + * @config dataLoaded + * @type Boolean + */ + this.register('dataLoaded', { + value: false, + validator: Lang.isBoolean, + writeOnce: true + }); + + /** + * Number if milliseconds before aborting and calling failure handler. + * @config dataTimeout + * @type Number + * @default null + */ + this.register('dataTimeout', { + value: attr.dataTimeout || null, + validator: Lang.isNumber + }); + + /** + * Whether or not the tab is currently active. + * If a dataSrc is set for the tab, the content will be loaded from + * the given source. + * @config active + * @type Boolean + */ + this.register('active', { + value: attr.active || this.hasClass(this.ACTIVE_CLASSNAME), + method: function(value) { + if (value === true) { + this.addClass(this.ACTIVE_CLASSNAME); + this.set('title', 'active'); + } else { + this.removeClass(this.ACTIVE_CLASSNAME); + this.set('title', ''); + } + }, + validator: function(value) { + return Lang.isBoolean(value) && !this.get('disabled') ; + } + }); + + /** + * Whether or not the tab is disabled. + * @config disabled + * @type Boolean + */ + this.register('disabled', { + value: attr.disabled || this.hasClass(this.DISABLED_CLASSNAME), + method: function(value) { + if (value === true) { + Dom.addClass(this.get('element'), this.DISABLED_CLASSNAME); + } else { + Dom.removeClass(this.get('element'), this.DISABLED_CLASSNAME); + } + }, + validator: Lang.isBoolean + }); + + /** + * The href of the tab's anchor element. + * @config href + * @type String + * @default '#' + */ + this.register('href', { + value: attr.href || '#', + method: function(value) { + this.getElementsByTagName('a')[0].href = value; + }, + validator: Lang.isString + }); + + /** + * The Whether or not the tab's content is visible. + * @config contentVisible + * @type Boolean + * @default false + */ + this.register('contentVisible', { + value: attr.contentVisible, + method: function(value) { + if (value == true) { + this.get('contentEl').style.display = 'block'; + + if ( this.get('dataSrc') ) { + // load dynamic content unless already loaded and caching + if ( !this.get('dataLoaded') || !this.get('cacheData') ) { + _dataConnect.call(this); + } + } + } else { + this.get('contentEl').style.display = 'none'; + } + }, + validator: Lang.isBoolean + }); + }; + + var _createTabElement = function(attr) { + var el = document.createElement('li'); + var a = document.createElement('a'); + + a.href = attr.href || '#'; + + el.appendChild(a); + + var label = attr.label || null; + var labelEl = attr.labelEl || null; + + if (labelEl) { // user supplied labelEl + if (!label) { // user supplied label + label = _getLabel.call(this, labelEl); + } + } else { + labelEl = _createlabelEl.call(this); + } + + a.appendChild(labelEl); + + return el; + }; + + var _getlabelEl = function() { + return this.getElementsByTagName(this.LABEL_TAGNAME)[0]; + }; + + var _createlabelEl = function() { + var el = document.createElement(this.LABEL_TAGNAME); + return el; + }; + + var _setLabel = function(label) { + var el = this.get('labelEl'); + el.innerHTML = label; + }; + + var _getLabel = function() { + var label, + el = this.get('labelEl'); + + if (!el) { + return undefined; + } + + return el.innerHTML; + }; + + var _dataConnect = function() { + if (!YAHOO.util.Connect) { + YAHOO.log('YAHOO.util.Connect dependency not met', + 'error', 'Tab'); + return false; + } + + Dom.addClass(this.get('contentEl').parentNode, this.LOADING_CLASSNAME); + + this.dataConnection = YAHOO.util.Connect.asyncRequest( + this.get('loadMethod'), + this.get('dataSrc'), + { + success: function(o) { + this.loadHandler.success.call(this, o); + this.set('dataLoaded', true); + this.dataConnection = null; + Dom.removeClass(this.get('contentEl').parentNode, + this.LOADING_CLASSNAME); + }, + failure: function(o) { + this.loadHandler.failure.call(this, o); + this.dataConnection = null; + Dom.removeClass(this.get('contentEl').parentNode, + this.LOADING_CLASSNAME); + }, + scope: this, + timeout: this.get('dataTimeout') + } + ); + }; + + YAHOO.widget.Tab = Tab; + + /** + * Fires before the active state is changed. + *
+ *var handler = function(e) {var target = e.target};
+ * myTabs.addListener('contentReady', handler);See: Element.addListener
+ *If handler returns false, the change will be cancelled, and the value will not + * be set.
+ *Event fields:
+ *
+ *<String> type
beforeActiveChange
+ *<Boolean> + * prevValue
the current value
+ *<Boolean> + * newValue
the new valueUsage:
+ * @event beforeActiveChange + */ + + /** + * Fires after the active state is changed. + *
+ *var handler = function(e) {var previous = e.prevValue};
+ * myTabs.addListener('beforeActiveChange', handler);See: Element.addListener
+ *Event fields:
+ *
+ *<String> type
activeChange
+ *<Boolean> + * prevValue
the previous value
+ *<Boolean> + * newValue
the updated valueUsage:
+ * @event activeChange + */ + + /** + * Fires before the tab label is changed. + *
+ *var handler = function(e) {var previous = e.prevValue};
+ * myTabs.addListener('activeChange', handler);See: Element.addListener
+ *If handler returns false, the change will be cancelled, and the value will not + * be set.
+ *Event fields:
+ *
+ *<String> type
beforeLabelChange
+ *<String> + * prevValue
the current value
+ *<String> + * newValue
the new valueUsage:
+ * @event beforeLabelChange + */ + + /** + * Fires after the tab label is changed. + *
+ *var handler = function(e) {var previous = e.prevValue};
+ * myTabs.addListener('beforeLabelChange', handler);See: Element.addListener
+ *Event fields:
+ *
+ *<String> type
labelChange
+ *<String> + * prevValue
the previous value
+ *<String> + * newValue
the updated valueUsage:
+ * @event labelChange + */ + + /** + * Fires before the tab content is changed. + *
+ *var handler = function(e) {var previous = e.prevValue};
+ * myTabs.addListener('labelChange', handler);See: Element.addListener
+ *If handler returns false, the change will be cancelled, and the value will not + * be set.
+ *Event fields:
+ *
+ *<String> type
beforeContentChange
+ *<String> + * prevValue
the current value
+ *<String> + * newValue
the new valueUsage:
+ * @event beforeContentChange + */ + + /** + * Fires after the tab content is changed. + *
+ *var handler = function(e) {var previous = e.prevValue};
+ * myTabs.addListener('beforeContentChange', handler);See: Element.addListener
+ *Event fields:
+ *
+ *<String> type
contentChange
+ *<String> + * prevValue
the previous value
+ *<Boolean> + * newValue
the updated valueUsage:
+ * @event contentChange + */ +})();(function() { + + /** + * The tabview module provides a widget for managing content bound to tabs. + * @module tabview + * + */ + /** + * A widget to control tabbed views. + * @namespace YAHOO.widget + * @class TabView + * @extends YAHOO.util.Element + * @constructor + * @param {HTMLElement | String | Object} el(optional) The html + * element that represents the TabView, or the attribute object to use. + * An element will be created if none provided. + * @param {Object} attr (optional) A key map of the tabView's + * initial attributes. Ignored if first arg is attributes object. + */ + YAHOO.widget.TabView = function(el, attr) { + attr = attr || {}; + if (arguments.length == 1 && !Lang.isString(el) && !el.nodeName) { + attr = el; // treat first arg as attr object + el = attr.element || null; + } + + if (!el && !attr.element) { // create if we dont have one + el = _createTabViewElement.call(this, attr); + } + YAHOO.widget.TabView.superclass.constructor.call(this, el, attr); + }; + + YAHOO.extend(YAHOO.widget.TabView, YAHOO.util.Element); + + var proto = YAHOO.widget.TabView.prototype; + var Dom = YAHOO.util.Dom; + var Lang = YAHOO.util.Lang; + var Event = YAHOO.util.Event; + var Tab = YAHOO.widget.Tab; + + + /** + * The className to add when building from scratch. + * @property CLASSNAME + * @default "navset" + */ + proto.CLASSNAME = 'yui-navset'; + + /** + * The className of the HTMLElement containing the TabView's tab elements + * to look for when building from existing markup, or to add when building + * from scratch. + * All childNodes of the tab container are treated as Tabs when building + * from existing markup. + * @property TAB_PARENT_CLASSNAME + * @default "nav" + */ + proto.TAB_PARENT_CLASSNAME = 'yui-nav'; + + /** + * The className of the HTMLElement containing the TabView's label elements + * to look for when building from existing markup, or to add when building + * from scratch. + * All childNodes of the content container are treated as content elements when + * building from existing markup. + * @property CONTENT_PARENT_CLASSNAME + * @default "nav-content" + */ + proto.CONTENT_PARENT_CLASSNAME = 'yui-content'; + + proto._tabParent = null; + proto._contentParent = null; + + /** + * Adds a Tab to the TabView instance. + * If no index is specified, the tab is added to the end of the tab list. + * @method addTab + * @param {YAHOO.widget.Tab} tab A Tab instance to add. + * @param {Integer} index The position to add the tab. + * @return void + */ + proto.addTab = function(tab, index) { + var tabs = this.get('tabs'); + if (!tabs) { // not ready yet + this._queue[this._queue.length] = ['addTab', arguments]; + return false; + } + + index = (index === undefined) ? tabs.length : index; + + var before = this.getTab(index); + + var self = this; + var el = this.get('element'); + var tabParent = this._tabParent; + var contentParent = this._contentParent; + + var tabElement = tab.get('element'); + var contentEl = tab.get('contentEl'); + + if ( before ) { + tabParent.insertBefore(tabElement, before.get('element')); + } else { + tabParent.appendChild(tabElement); + } + + if ( contentEl && !Dom.isAncestor(contentParent, contentEl) ) { + contentParent.appendChild(contentEl); + } + + if ( !tab.get('active') ) { + tab.set('contentVisible', false, true); /* hide if not active */ + } else { + this.set('activeTab', tab, true); + + } + + var activate = function(e) { + YAHOO.util.Event.preventDefault(e); + self.set('activeTab', this); + }; + + tab.addListener( tab.get('activationEvent'), activate); + + tab.addListener('activationEventChange', function(e) { + if (e.prevValue != e.newValue) { + tab.removeListener(e.prevValue, activate); + tab.addListener(e.newValue, activate); + } + }); + + tabs.splice(index, 0, tab); + }; + + /** + * Routes childNode events. + * @method DOMEventHandler + * @param {event} e The Dom event that is being handled. + * @return void + */ + proto.DOMEventHandler = function(e) { + var el = this.get('element'); + var target = YAHOO.util.Event.getTarget(e); + var tabParent = this._tabParent; + + if (Dom.isAncestor(tabParent, target) ) { + var tabEl; + var tab = null; + var contentEl; + var tabs = this.get('tabs'); + + for (var i = 0, len = tabs.length; i < len; i++) { + tabEl = tabs[i].get('element'); + contentEl = tabs[i].get('contentEl'); + + if ( target == tabEl || Dom.isAncestor(tabEl, target) ) { + tab = tabs[i]; + break; // note break + } + } + + if (tab) { + tab.fireEvent(e.type, e); + } + } + }; + + /** + * Returns the Tab instance at the specified index. + * @method getTab + * @param {Integer} index The position of the Tab. + * @return YAHOO.widget.Tab + */ + proto.getTab = function(index) { + return this.get('tabs')[index]; + }; + + /** + * Returns the index of given tab. + * @method getTabIndex + * @param {YAHOO.widget.Tab} tab The tab whose index will be returned. + * @return int + */ + proto.getTabIndex = function(tab) { + var index = null; + var tabs = this.get('tabs'); + for (var i = 0, len = tabs.length; i < len; ++i) { + if (tab == tabs[i]) { + index = i; + break; + } + } + + return index; + }; + + /** + * Removes the specified Tab from the TabView. + * @method removeTab + * @param {YAHOO.widget.Tab} item The Tab instance to be removed. + * @return void + */ + proto.removeTab = function(tab) { + var tabCount = this.get('tabs').length; + + var index = this.getTabIndex(tab); + var nextIndex = index + 1; + if ( tab == this.get('activeTab') ) { // select next tab + if (tabCount > 1) { + if (index + 1 == tabCount) { + this.set('activeIndex', index - 1); + } else { + this.set('activeIndex', index + 1); + } + } + } + + this._tabParent.removeChild( tab.get('element') ); + this._contentParent.removeChild( tab.get('contentEl') ); + this._configs.tabs.value.splice(index, 1); + + }; + + /** + * Provides a readable name for the TabView instance. + * @method toString + * @return String + */ + proto.toString = function() { + var name = this.get('id') || this.get('tagName'); + return "TabView " + name; + }; + + /** + * The transiton to use when switching between tabs. + * @method contentTransition + */ + proto.contentTransition = function(newTab, oldTab) { + newTab.set('contentVisible', true); + oldTab.set('contentVisible', false); + }; + + /** + * Registers TabView specific properties. + * @method initAttributes + * @param {Object} attr Hash of initial attributes + */ + proto.initAttributes = function(attr) { + YAHOO.widget.TabView.superclass.initAttributes.call(this, attr); + + if (!attr.orientation) { + attr.orientation = 'top'; + } + + var el = this.get('element'); + + /** + * The Tabs belonging to the TabView instance. + * @config tabs + * @type Array + */ + this.register('tabs', { + value: [], + readOnly: true + }); + + /** + * The container of the tabView's label elements. + * @property _tabParent + * @private + * @type HTMLElement + */ + this._tabParent = + this.getElementsByClassName(this.TAB_PARENT_CLASSNAME, + 'ul' )[0] || _createTabParent.call(this); + + /** + * The container of the tabView's content elements. + * @property _contentParent + * @type HTMLElement + * @private + */ + this._contentParent = + this.getElementsByClassName(this.CONTENT_PARENT_CLASSNAME, + 'div')[0] || _createContentParent.call(this); + + /** + * How the Tabs should be oriented relative to the TabView. + * @config orientation + * @type String + * @default "top" + */ + this.register('orientation', { + value: attr.orientation, + method: function(value) { + var current = this.get('orientation'); + this.addClass('yui-navset-' + value); + + if (current != value) { + this.removeClass('yui-navset-' + current); + } + + switch(value) { + case 'bottom': + this.appendChild(this._tabParent); + break; + } + } + }); + + /** + * The index of the tab currently active. + * @config activeIndex + * @type Int + */ + this.register('activeIndex', { + value: attr.activeIndex, + method: function(value) { + this.set('activeTab', this.getTab(value)); + }, + validator: function(value) { + return !this.getTab(value).get('disabled'); // cannot activate if disabled + } + }); + + /** + * The tab currently active. + * @config activeTab + * @type YAHOO.widget.Tab + */ + this.register('activeTab', { + value: attr.activeTab, + method: function(tab) { + var activeTab = this.get('activeTab'); + + if (tab) { + tab.set('active', true); + } + + if (activeTab && activeTab != tab) { + activeTab.set('active', false); + } + + if (activeTab && tab != activeTab) { // no transition if only 1 + this.contentTransition(tab, activeTab); + } else if (tab) { + tab.set('contentVisible', true); + } + }, + validator: function(value) { + return !value.get('disabled'); // cannot activate if disabled + } + }); + + if ( this._tabParent ) { + _initTabs.call(this); + } + + for (var type in this.DOM_EVENTS) { + if ( this.DOM_EVENTS.hasOwnProperty(type) ) { + this.addListener.call(this, type, this.DOMEventHandler); + } + } + }; + + /** + * Creates Tab instances from a collection of HTMLElements. + * @method createTabs + * @private + * @param {Array|HTMLCollection} elements The elements to use for Tabs. + * @return void + */ + var _initTabs = function() { + var tab, + attr, + contentEl; + + var el = this.get('element'); + var tabs = _getChildNodes(this._tabParent); + var contentElements = _getChildNodes(this._contentParent); + + for (var i = 0, len = tabs.length; i < len; ++i) { + attr = {}; + + if (contentElements[i]) { + attr.contentEl = contentElements[i]; + } + + tab = new YAHOO.widget.Tab(tabs[i], attr); + this.addTab(tab); + + if (tab.hasClass(tab.ACTIVE_CLASSNAME) ) { + this._configs.activeTab.value = tab; // dont invoke method + } + } + }; + + var _createTabViewElement = function(attr) { + var el = document.createElement('div'); + + if ( this.CLASSNAME ) { + el.className = this.CLASSNAME; + } + + return el; + }; + + var _createTabParent = function(attr) { + var el = document.createElement('ul'); + + if ( this.TAB_PARENT_CLASSNAME ) { + el.className = this.TAB_PARENT_CLASSNAME; + } + + this.get('element').appendChild(el); + + return el; + }; + + var _createContentParent = function(attr) { + var el = document.createElement('div'); + + if ( this.CONTENT_PARENT_CLASSNAME ) { + el.className = this.CONTENT_PARENT_CLASSNAME; + } + + this.get('element').appendChild(el); + + return el; + }; + + var _getChildNodes = function(el) { + var nodes = []; + var childNodes = el.childNodes; + + for (var i = 0, len = childNodes.length; i < len; ++i) { + if (childNodes[i].nodeType == 1) { + nodes[nodes.length] = childNodes[i]; + } + } + + return nodes; + }; + +/** + * Fires before the activeTab is changed. + *
+ *var handler = function(e) {var previous = e.prevValue};
+ * myTabs.addListener('contentChange', handler);See: Element.addListener
+ *If handler returns false, the change will be cancelled, and the value will not + * be set.
+ *Event fields:
+ *
+ *<String> type
beforeActiveTabChange
+ *<YAHOO.widget.Tab> + * prevValue
the currently active tab
+ *<YAHOO.widget.Tab> + * newValue
the tab to be made activeUsage:
+ * @event beforeActiveTabChange + */ + +/** + * Fires after the activeTab is changed. + *
+ *var handler = function(e) {var previous = e.prevValue};
+ * myTabs.addListener('beforeActiveTabChange', handler);See: Element.addListener
+ *Event fields:
+ *
+ *<String> type
activeTabChange
+ *<YAHOO.widget.Tab> + * prevValue
the formerly active tab
+ *<YAHOO.widget.Tab> + * newValue
the new active tabUsage:
+ * @event activeTabChange + */ + +/** + * Fires before the orientation is changed. + *
+ *var handler = function(e) {var previous = e.prevValue};
+ * myTabs.addListener('activeTabChange', handler);See: Element.addListener
+ *If handler returns false, the change will be cancelled, and the value will not + * be set.
+ *Event fields:
+ *
+ *<String> type
beforeOrientationChange
+ *<String> + * prevValue
the current orientation
+ *<String> + * newValue
the new orientation to be appliedUsage:
+ * @event beforeOrientationChange + */ + +/** + * Fires after the orientation is changed. + *
+ *var handler = function(e) {var previous = e.prevValue};
+ * myTabs.addListener('beforeOrientationChange', handler);See: Element.addListener
+ *Event fields:
+ *
+ *<String> type
orientationChange
+ *<String> + * prevValue
the former orientation
+ *<String> + * newValue
the new orientationUsage:
+ * @event orientationChange + */ +})(); \ No newline at end of file diff --git a/frontend/beta/js/YUI/treeview.js b/frontend/beta/js/YUI/treeview.js new file mode 100644 index 0000000..ea6b6ef --- a/dev/null +++ b/frontend/beta/js/YUI/treeview.js @@ -0,0 +1,2182 @@ +/* +Copyright (c) 2006, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.net/yui/license.txt +version: 0.12.0 +*/ + +/** + * The treeview widget is a generic tree building tool. + * @module treeview + * @title TreeView Widget + * @requires yahoo + * @optional animation + * @namespace YAHOO.widget + */ + +/** + * Contains the tree view state data and the root node. + * + * @class TreeView + * @constructor + * @param {string|HTMLElement} id The id of the element, or the element + * itself that the tree will be inserted into. + */ +YAHOO.widget.TreeView = function(id) { + if (id) { this.init(id); } +}; + +YAHOO.widget.TreeView.prototype = { + + /** + * The id of tree container element + * @property id + * @type String + */ + id: null, + + /** + * The host element for this tree + * @property _el + * @private + */ + _el: null, + + /** + * Flat collection of all nodes in this tree + * @property _nodes + * @type Node[] + * @private + */ + _nodes: null, + + /** + * We lock the tree control while waiting for the dynamic loader to return + * @property locked + * @type boolean + */ + locked: false, + + /** + * The animation to use for expanding children, if any + * @property _expandAnim + * @type string + * @private + */ + _expandAnim: null, + + /** + * The animation to use for collapsing children, if any + * @property _collapseAnim + * @type string + * @private + */ + _collapseAnim: null, + + /** + * The current number of animations that are executing + * @property _animCount + * @type int + * @private + */ + _animCount: 0, + + /** + * The maximum number of animations to run at one time. + * @property maxAnim + * @type int + */ + maxAnim: 2, + + /** + * Sets up the animation for expanding children + * @method setExpandAnim + * @param {string} type the type of animation (acceptable values defined + * in YAHOO.widget.TVAnim) + */ + setExpandAnim: function(type) { + if (YAHOO.widget.TVAnim.isValid(type)) { + this._expandAnim = type; + } + }, + + /** + * Sets up the animation for collapsing children + * @method setCollapseAnim + * @param {string} the type of animation (acceptable values defined in + * YAHOO.widget.TVAnim) + */ + setCollapseAnim: function(type) { + if (YAHOO.widget.TVAnim.isValid(type)) { + this._collapseAnim = type; + } + }, + + /** + * Perform the expand animation if configured, or just show the + * element if not configured or too many animations are in progress + * @method animateExpand + * @param el {HTMLElement} the element to animate + * @param node {YAHOO.util.Node} the node that was expanded + * @return {boolean} true if animation could be invoked, false otherwise + */ + animateExpand: function(el, node) { + + if (this._expandAnim && this._animCount < this.maxAnim) { + // this.locked = true; + var tree = this; + var a = YAHOO.widget.TVAnim.getAnim(this._expandAnim, el, + function() { tree.expandComplete(node); }); + if (a) { + ++this._animCount; + this.fireEvent("animStart", { + "node": node, + "type": "expand" + }); + a.animate(); + } + + return true; + } + + return false; + }, + + /** + * Perform the collapse animation if configured, or just show the + * element if not configured or too many animations are in progress + * @method animateCollapse + * @param el {HTMLElement} the element to animate + * @param node {YAHOO.util.Node} the node that was expanded + * @return {boolean} true if animation could be invoked, false otherwise + */ + animateCollapse: function(el, node) { + + if (this._collapseAnim && this._animCount < this.maxAnim) { + // this.locked = true; + var tree = this; + var a = YAHOO.widget.TVAnim.getAnim(this._collapseAnim, el, + function() { tree.collapseComplete(node); }); + if (a) { + ++this._animCount; + this.fireEvent("animStart", { + "node": node, + "type": "collapse" + }); + a.animate(); + } + + return true; + } + + return false; + }, + + /** + * Function executed when the expand animation completes + * @method expandComplete + */ + expandComplete: function(node) { + --this._animCount; + this.fireEvent("animComplete", { + "node": node, + "type": "expand" + }); + // this.locked = false; + }, + + /** + * Function executed when the collapse animation completes + * @method collapseComplete + */ + collapseComplete: function(node) { + --this._animCount; + this.fireEvent("animComplete", { + "node": node, + "type": "collapse" + }); + // this.locked = false; + }, + + /** + * Initializes the tree + * @method init + * @parm {string|HTMLElement} id the id of the element that will hold the tree + * @private + */ + init: function(id) { + + this.id = id; + + if ("string" !== typeof id) { + this._el = id; + this.id = this.generateId(id); + } + + /** + * When animation is enabled, this event fires when the animation + * starts + * @event animStart + * @type CustomEvent + * @param {YAHOO.widget.Node} node the node that is expanding/collapsing + * @parm {String} type the type of animation ("expand" or "collapse") + */ + this.createEvent("animStart", this); + + /** + * When animation is enabled, this event fires when the animation + * completes + * @event animComplete + * @type CustomEvent + * @param {YAHOO.widget.Node} node the node that is expanding/collapsing + * @parm {String} type the type of animation ("expand" or "collapse") + */ + this.createEvent("animComplete", this); + + /** + * Fires when a node is going to be expanded. Return false to stop + * the expand. + * @event collapse + * @type CustomEvent + * @param {YAHOO.widget.Node} node the node that is expanding/collapsing + */ + this.createEvent("collapse", this); + + /** + * Fires when a node is going to be collapsed. Return false to stop + * the collapse. + * @event expand + * @type CustomEvent + * @param {YAHOO.widget.Node} node the node that is expanding/collapsing + */ + this.createEvent("expand", this); + + this._nodes = []; + + // store a global reference + YAHOO.widget.TreeView.trees[this.id] = this; + + // Set up the root node + this.root = new YAHOO.widget.RootNode(this); + + + }, + + /** + * Renders the tree boilerplate and visible nodes + * @method draw + */ + draw: function() { + var html = this.root.getHtml(); + this.getEl().innerHTML = html; + this.firstDraw = false; + }, + + /** + * Returns the tree's host element + * @method getEl + * @return {HTMLElement} the host element + */ + getEl: function() { + if (! this._el) { + this._el = document.getElementById(this.id); + } + return this._el; + }, + + /** + * Nodes register themselves with the tree instance when they are created. + * @method regNode + * @param node {Node} the node to register + * @private + */ + regNode: function(node) { + this._nodes[node.index] = node; + }, + + /** + * Returns the root node of this tree + * @method getRoot + * @return {Node} the root node + */ + getRoot: function() { + return this.root; + }, + + /** + * Configures this tree to dynamically load all child data + * @method setDynamicLoad + * @param {function} fnDataLoader the function that will be called to get the data + * @param iconMode {int} configures the icon that is displayed when a dynamic + * load node is expanded the first time without children. By default, the + * "collapse" icon will be used. If set to 1, the leaf node icon will be + * displayed. + */ + setDynamicLoad: function(fnDataLoader, iconMode) { + this.root.setDynamicLoad(fnDataLoader, iconMode); + }, + + /** + * Expands all child nodes. Note: this conflicts with the "multiExpand" + * node property. If expand all is called in a tree with nodes that + * do not allow multiple siblings to be displayed, only the last sibling + * will be expanded. + * @method expandAll + */ + expandAll: function() { + if (!this.locked) { + this.root.expandAll(); + } + }, + + /** + * Collapses all expanded child nodes in the entire tree. + * @method collapseAll + */ + collapseAll: function() { + if (!this.locked) { + this.root.collapseAll(); + } + }, + + /** + * Returns a node in the tree that has the specified index (this index + * is created internally, so this function probably will only be used + * in html generated for a given node.) + * @method getNodeByIndex + * @param {int} nodeIndex the index of the node wanted + * @return {Node} the node with index=nodeIndex, null if no match + */ + getNodeByIndex: function(nodeIndex) { + var n = this._nodes[nodeIndex]; + return (n) ? n : null; + }, + + /** + * Returns a node that has a matching property and value in the data + * object that was passed into its constructor. + * @method getNodeByProperty + * @param {object} property the property to search (usually a string) + * @param {object} value the value we want to find (usuall an int or string) + * @return {Node} the matching node, null if no match + */ + getNodeByProperty: function(property, value) { + for (var i in this._nodes) { + var n = this._nodes[i]; + if (n.data && value == n.data[property]) { + return n; + } + } + + return null; + }, + + /** + * Returns a collection of nodes that have a matching property + * and value in the data object that was passed into its constructor. + * @method getNodesByProperty + * @param {object} property the property to search (usually a string) + * @param {object} value the value we want to find (usuall an int or string) + * @return {Array} the matching collection of nodes, null if no match + */ + getNodesByProperty: function(property, value) { + var values = []; + for (var i in this._nodes) { + var n = this._nodes[i]; + if (n.data && value == n.data[property]) { + values.push(n); + } + } + + return (values.length) ? values : null; + }, + + /** + * Removes the node and its children, and optionally refreshes the + * branch of the tree that was affected. + * @method removeNode + * @param {Node} The node to remove + * @param {boolean} autoRefresh automatically refreshes branch if true + * @return {boolean} False is there was a problem, true otherwise. + */ + removeNode: function(node, autoRefresh) { + + // Don't delete the root node + if (node.isRoot()) { + return false; + } + + // Get the branch that we may need to refresh + var p = node.parent; + if (p.parent) { + p = p.parent; + } + + // Delete the node and its children + this._deleteNode(node); + + // Refresh the parent of the parent + if (autoRefresh && p && p.childrenRendered) { + p.refresh(); + } + + return true; + }, + + /** + * Deletes this nodes child collection, recursively. Also collapses + * the node, and resets the dynamic load flag. The primary use for + * this method is to purge a node and allow it to fetch its data + * dynamically again. + * @method removeChildren + * @param {Node} node the node to purge + */ + removeChildren: function(node) { + while (node.children.length) { + this._deleteNode(node.children[0]); + } + + node.childrenRendered = false; + node.dynamicLoadComplete = false; + if (node.expanded) { + node.collapse(); + } else { + node.updateIcon(); + } + }, + + /** + * Deletes the node and recurses children + * @method _deleteNode + * @private + */ + _deleteNode: function(node) { + // Remove all the child nodes first + this.removeChildren(node); + + // Remove the node from the tree + this.popNode(node); + }, + + /** + * Removes the node from the tree, preserving the child collection + * to make it possible to insert the branch into another part of the + * tree, or another tree. + * @method popNode + * @param {Node} the node to remove + */ + popNode: function(node) { + var p = node.parent; + + // Update the parent's collection of children + var a = []; + + for (var i=0, len=p.children.length;i
+ *var handler = function(e) {var previous = e.prevValue};
+ * myTabs.addListener('orientationChange', handler);'; + } + + var f = document.createElement("div"); + var s = f.style; + s.position = "absolute"; + s.top = "-1000px"; + s.left = "-1000px"; + f.innerHTML = sb.join(""); + + document.body.appendChild(f); + + YAHOO.widget.TreeView.removeHandler(window, + "load", YAHOO.widget.TreeView.preload); + +}; + +YAHOO.widget.TreeView.addHandler(window, + "load", YAHOO.widget.TreeView.preload); + +/** + * The base class for all tree nodes. The node's presentation and behavior in + * response to mouse events is handled in Node subclasses. + * @namespace YAHOO.widget + * @class Node + * @param oData {object} a string or object containing the data that will + * be used to render this node + * @param oParent {Node} this node's parent node + * @param expanded {boolean} the initial expanded/collapsed state + * @constructor + */ +YAHOO.widget.Node = function(oData, oParent, expanded) { + if (oData) { this.init(oData, oParent, expanded); } +}; + +YAHOO.widget.Node.prototype = { + + /** + * The index for this instance obtained from global counter in YAHOO.widget.TreeView. + * @property index + * @type int + */ + index: 0, + + /** + * This node's child node collection. + * @property children + * @type Node[] + */ + children: null, + + /** + * Tree instance this node is part of + * @property tree + * @type TreeView + */ + tree: null, + + /** + * The data linked to this node. This can be any object or primitive + * value, and the data can be used in getNodeHtml(). + * @property data + * @type object + */ + data: null, + + /** + * Parent node + * @property parent + * @type Node + */ + parent: null, + + /** + * The depth of this node. We start at -1 for the root node. + * @property depth + * @type int + */ + depth: -1, + + /** + * The href for the node's label. If one is not specified, the href will + * be set so that it toggles the node. + * @property href + * @type string + */ + href: null, + + /** + * The label href target, defaults to current window + * @property target + * @type string + */ + target: "_self", + + /** + * The node's expanded/collapsed state + * @property expanded + * @type boolean + */ + expanded: false, + + /** + * Can multiple children be expanded at once? + * @property multiExpand + * @type boolean + */ + multiExpand: true, + + /** + * Should we render children for a collapsed node? It is possible that the + * implementer will want to render the hidden data... @todo verify that we + * need this, and implement it if we do. + * @property renderHidden + * @type boolean + */ + renderHidden: false, + + /** + * This flag is set to true when the html is generated for this node's + * children, and set to false when new children are added. + * @property childrenRendered + * @type boolean + */ + childrenRendered: false, + + /** + * Dynamically loaded nodes only fetch the data the first time they are + * expanded. This flag is set to true once the data has been fetched. + * @property dynamicLoadComplete + * @type boolean + */ + dynamicLoadComplete: false, + + /** + * This node's previous sibling + * @property previousSibling + * @type Node + */ + previousSibling: null, + + /** + * This node's next sibling + * @property nextSibling + * @type Node + */ + nextSibling: null, + + /** + * We can set the node up to call an external method to get the child + * data dynamically. + * @property _dynLoad + * @type boolean + * @private + */ + _dynLoad: false, + + /** + * Function to execute when we need to get this node's child data. + * @property dataLoader + * @type function + */ + dataLoader: null, + + /** + * This is true for dynamically loading nodes while waiting for the + * callback to return. + * @property isLoading + * @type boolean + */ + isLoading: false, + + /** + * The toggle/branch icon will not show if this is set to false. This + * could be useful if the implementer wants to have the child contain + * extra info about the parent, rather than an actual node. + * @property hasIcon + * @type boolean + */ + hasIcon: true, + + /** + * Used to configure what happens when a dynamic load node is expanded + * and we discover that it does not have children. By default, it is + * treated as if it still could have children (plus/minus icon). Set + * iconMode to have it display like a leaf node instead. + * @property iconMode + * @type int + */ + iconMode: 0, + + /** + * The node type + * @property _type + * @private + */ + _type: "Node", + + /* + spacerPath: "http://us.i1.yimg.com/us.yimg.com/i/space.gif", + expandedText: "Expanded", + collapsedText: "Collapsed", + loadingText: "Loading", + */ + + /** + * Initializes this node, gets some of the properties from the parent + * @method init + * @param oData {object} a string or object containing the data that will + * be used to render this node + * @param oParent {Node} this node's parent node + * @param expanded {boolean} the initial expanded/collapsed state + */ + init: function(oData, oParent, expanded) { + + this.data = oData; + this.children = []; + this.index = YAHOO.widget.TreeView.nodeCount; + ++YAHOO.widget.TreeView.nodeCount; + this.expanded = expanded; + + /** + * The parentChange event is fired when a parent element is applied + * to the node. This is useful if you need to apply tree-level + * properties to a tree that need to happen if a node is moved from + * one tre to another. + * + * @event parentChange + * @type CustomEvent + */ + this.createEvent("parentChange", this); + + // oParent should never be null except when we create the root node. + if (oParent) { + oParent.appendChild(this); + } + }, + + /** + * Certain properties for the node cannot be set until the parent + * is known. This is called after the node is inserted into a tree. + * the parent is also applied to this node's children in order to + * make it possible to move a branch from one tree to another. + * @method applyParent + * @param {Node} parentNode this node's parent node + * @return {boolean} true if the application was successful + */ + applyParent: function(parentNode) { + if (!parentNode) { + return false; + } + + this.tree = parentNode.tree; + this.parent = parentNode; + this.depth = parentNode.depth + 1; + + if (!this.href) { + this.href = "javascript:" + this.getToggleLink(); + } + + if (! this.multiExpand) { + this.multiExpand = parentNode.multiExpand; + } + + this.tree.regNode(this); + parentNode.childrenRendered = false; + + // cascade update existing children + for (var i=0, len=this.children.length;i 0 || + (checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete) ); + }, + + /** + * Expands if node is collapsed, collapses otherwise. + * @method toggle + */ + toggle: function() { + if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) { + if (this.expanded) { this.collapse(); } else { this.expand(); } + } + }, + + /** + * Returns the markup for this node and its children. + * @method getHtml + * @return {string} the markup for this node and its expanded children. + */ + getHtml: function() { + + this.childrenRendered = false; + + var sb = []; + sb[sb.length] = ' '; + sb[sb.length] = this.getNodeHtml(); + sb[sb.length] = this.getChildrenHtml(); + sb[sb.length] = ''; + return sb.join(""); + }, + + /** + * Called when first rendering the tree. We always build the div that will + * contain this nodes children, but we don't render the children themselves + * unless this node is expanded. + * @method getChildrenHtml + * @return {string} the children container div html and any expanded children + * @private + */ + getChildrenHtml: function() { + + var sb = []; + sb[sb.length] = ' '; + + return sb.join(""); + }, + + /** + * Generates the markup for the child nodes. This is not done until the node + * is expanded. + * @method renderChildren + * @return {string} the html for this node's children + * @private + */ + renderChildren: function() { + + + var node = this; + + if (this.isDynamic() && !this.dynamicLoadComplete) { + this.isLoading = true; + this.tree.locked = true; + + if (this.dataLoader) { + + setTimeout( + function() { + node.dataLoader(node, + function() { + node.loadComplete(); + }); + }, 10); + + } else if (this.tree.root.dataLoader) { + + setTimeout( + function() { + node.tree.root.dataLoader(node, + function() { + node.loadComplete(); + }); + }, 10); + + } else { + return "Error: data loader not found or not specified."; + } + + return ""; + + } else { + return this.completeRender(); + } + }, + + /** + * Called when we know we have all the child data. + * @method completeRender + * @return {string} children html + */ + completeRender: function() { + var sb = []; + + for (var i=0; i < this.children.length; ++i) { + // this.children[i].childrenRendered = false; + sb[sb.length] = this.children[i].getHtml(); + } + + this.childrenRendered = true; + + return sb.join(""); + }, + + /** + * Load complete is the callback function we pass to the data provider + * in dynamic load situations. + * @method loadComplete + */ + loadComplete: function() { + this.getChildrenEl().innerHTML = this.completeRender(); + this.dynamicLoadComplete = true; + this.isLoading = false; + this.expand(); + this.tree.locked = false; + }, + + /** + * Returns this node's ancestor at the specified depth. + * @method getAncestor + * @param {int} depth the depth of the ancestor. + * @return {Node} the ancestor + */ + getAncestor: function(depth) { + if (depth >= this.depth || depth < 0) { + return null; + } + + var p = this.parent; + + while (p.depth > depth) { + p = p.parent; + } + + return p; + }, + + /** + * Returns the css class for the spacer at the specified depth for + * this node. If this node's ancestor at the specified depth + * has a next sibling the presentation is different than if it + * does not have a next sibling + * @method getDepthStyle + * @param {int} depth the depth of the ancestor. + * @return {string} the css class for the spacer + */ + getDepthStyle: function(depth) { + return (this.getAncestor(depth).nextSibling) ? + "ygtvdepthcell" : "ygtvblankdepthcell"; + }, + + /** + * Get the markup for the node. This is designed to be overrided so that we can + * support different types of nodes. + * @method getNodeHtml + * @return {string} The HTML that will render this node. + */ + getNodeHtml: function() { + return ""; + }, + + /** + * Regenerates the html for this node and its children. To be used when the + * node is expanded and new children have been added. + * @method refresh + */ + refresh: function() { + // this.loadComplete(); + this.getChildrenEl().innerHTML = this.completeRender(); + + if (this.hasIcon) { + var el = this.getToggleEl(); + if (el) { + el.className = this.getStyle(); + } + } + }, + + /** + * Node toString + * @method toString + * @return {string} string representation of the node + */ + toString: function() { + return "Node (" + this.index + ")"; + } + +}; + +YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider); + +/** + * A custom YAHOO.widget.Node that handles the unique nature of + * the virtual, presentationless root node. + * @namespace YAHOO.widget + * @class RootNode + * @extends YAHOO.widget.Node + * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to + * @constructor + */ +YAHOO.widget.RootNode = function(oTree) { + // Initialize the node with null params. The root node is a + // special case where the node has no presentation. So we have + // to alter the standard properties a bit. + this.init(null, null, true); + + /* + * For the root node, we get the tree reference from as a param + * to the constructor instead of from the parent element. + */ + this.tree = oTree; +}; + +YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, { + + // overrides YAHOO.widget.Node + getNodeHtml: function() { + return ""; + }, + + toString: function() { + return "RootNode"; + }, + + loadComplete: function() { + this.tree.draw(); + } + +}); +/** + * The default node presentation. The first parameter should be + * either a string that will be used as the node's label, or an object + * that has a string propery called label. By default, the clicking the + * label will toggle the expanded/collapsed state of the node. By + * changing the href property of the instance, this behavior can be + * changed so that the label will go to the specified href. + * @namespace YAHOO.widget + * @class TextNode + * @extends YAHOO.widget.Node + * @constructor + * @param oData {object} a string or object containing the data that will + * be used to render this node + * @param oParent {YAHOO.widget.Node} this node's parent node + * @param expanded {boolean} the initial expanded/collapsed state + */ +YAHOO.widget.TextNode = function(oData, oParent, expanded) { + + if (oData) { + this.init(oData, oParent, expanded); + this.setUpLabel(oData); + } + +}; + +YAHOO.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, { + + /** + * The CSS class for the label href. Defaults to ygtvlabel, but can be + * overridden to provide a custom presentation for a specific node. + * @property labelStyle + * @type string + */ + labelStyle: "ygtvlabel", + + /** + * The derived element id of the label for this node + * @property labelElId + * @type string + */ + labelElId: null, + + /** + * The text for the label. It is assumed that the oData parameter will + * either be a string that will be used as the label, or an object that + * has a property called "label" that we will use. + * @property label + * @type string + */ + label: null, + + textNodeParentChange: function() { + + /** + * Custom event that is fired when the text node label is clicked. The + * custom event is defined on the tree instance, so there is a single + * event that handles all nodes in the tree. The node clicked is + * provided as an argument + * + * @event labelClick + * @for YAHOO.widget.TreeView + * @param {YAHOO.widget.Node} node the node clicked + */ + if (this.tree && !this.tree.hasEvent("labelClick")) { + this.tree.createEvent("labelClick", this.tree); + } + + }, + + /** + * Sets up the node label + * @method setUpLabel + * @param oData string containing the label, or an object with a label property + */ + setUpLabel: function(oData) { + + // set up the custom event on the tree + this.textNodeParentChange(); + this.subscribe("parentChange", this.textNodeParentChange); + + if (typeof oData == "string") { + oData = { label: oData }; + } + this.label = oData.label; + + // update the link + if (oData.href) { + this.href = oData.href; + } + + // set the target + if (oData.target) { + this.target = oData.target; + } + + if (oData.style) { + this.labelStyle = oData.style; + } + + this.labelElId = "ygtvlabelel" + this.index; + }, + + /** + * Returns the label element + * @for YAHOO.widget.TextNode + * @method getLabelEl + * @return {object} the element + */ + getLabelEl: function() { + return document.getElementById(this.labelElId); + }, + + // overrides YAHOO.widget.Node + getNodeHtml: function() { + var sb = []; + + sb[sb.length] = ''; + sb[sb.length] = '
'; + + return sb.join(""); + }, + + /** + * Executed when the label is clicked. Fires the labelClick custom event. + * @method onLabelClick + * @param me {Node} this node + * @scope the anchor tag clicked + * @return false to cancel the anchor click + */ + onLabelClick: function(me) { + return me.tree.fireEvent("labelClick", me); + //return true; + }, + + toString: function() { + return "TextNode (" + this.index + ") " + this.label; + } + +}); +/** + * A menu-specific implementation that differs from TextNode in that only + * one sibling can be expanded at a time. + * @namespace YAHOO.widget + * @class MenuNode + * @extends YAHOO.widget.TextNode + * @param oData {object} a string or object containing the data that will + * be used to render this node + * @param oParent {YAHOO.widget.Node} this node's parent node + * @param expanded {boolean} the initial expanded/collapsed state + * @constructor + */ +YAHOO.widget.MenuNode = function(oData, oParent, expanded) { + if (oData) { + this.init(oData, oParent, expanded); + this.setUpLabel(oData); + } + + /* + * Menus usually allow only one branch to be open at a time. + */ + this.multiExpand = false; + + +}; + +YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, { + + toString: function() { + return "MenuNode (" + this.index + ") " + this.label; + } + +}); +/** + * This implementation takes either a string or object for the + * oData argument. If is it a string, we will use it for the display + * of this node (and it can contain any html code). If the parameter + * is an object, we look for a parameter called "html" that will be + * used for this node's display. + * @namespace YAHOO.widget + * @class HTMLNode + * @extends YAHOO.widget.Node + * @constructor + * @param oData {object} a string or object containing the data that will + * be used to render this node + * @param oParent {YAHOO.widget.Node} this node's parent node + * @param expanded {boolean} the initial expanded/collapsed state + * @param hasIcon {boolean} specifies whether or not leaf nodes should + * have an icon + */ +YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) { + if (oData) { + this.init(oData, oParent, expanded); + this.initContent(oData, hasIcon); + } +}; + +YAHOO.extend(YAHOO.widget.HTMLNode, YAHOO.widget.Node, { + + /** + * The CSS class for the html content container. Defaults to ygtvhtml, but + * can be overridden to provide a custom presentation for a specific node. + * @property contentStyle + * @type string + */ + contentStyle: "ygtvhtml", + + /** + * The generated id that will contain the data passed in by the implementer. + * @property contentElId + * @type string + */ + contentElId: null, + + /** + * The HTML content to use for this node's display + * @property content + * @type string + */ + content: null, + + /** + * Sets up the node label + * @property initContent + * @param {object} An html string or object containing an html property + * @param {boolean} hasIcon determines if the node will be rendered with an + * icon or not + */ + initContent: function(oData, hasIcon) { + if (typeof oData == "string") { + oData = { html: oData }; + } + + this.html = oData.html; + this.contentElId = "ygtvcontentel" + this.index; + this.hasIcon = hasIcon; + + }, + + /** + * Returns the outer html element for this node's content + * @method getContentEl + * @return {HTMLElement} the element + */ + getContentEl: function() { + return document.getElementById(this.contentElId); + }, + + // overrides YAHOO.widget.Node + getNodeHtml: function() { + var sb = []; + + sb[sb.length] = ''; + + for (var i=0;i '; + sb[sb.length] = ''; + sb[sb.length] = ' '; + } + + var getNode = 'YAHOO.widget.TreeView.getNode(\'' + + this.tree.id + '\',' + this.index + ')'; + + sb[sb.length] = ' '; + + /* + sb[sb.length] = ' '; + sb[sb.length] = ''; + */ + + sb[sb.length] = ' '; + + sb[sb.length] = '
'; + sb[sb.length] = ''; + sb[sb.length] = this.label; + sb[sb.length] = ''; + sb[sb.length] = ' '; + sb[sb.length] = ''; + sb[sb.length] = '
'; + + return sb.join(""); + }, + + toString: function() { + return "HTMLNode (" + this.index + ")"; + } + +}); +/** + * A static factory class for tree view expand/collapse animations + * @class TVAnim + * @static + */ +YAHOO.widget.TVAnim = function() { + return { + /** + * Constant for the fade in animation + * @property FADE_IN + * @type string + * @static + */ + FADE_IN: "TVFadeIn", + + /** + * Constant for the fade out animation + * @property FADE_OUT + * @type string + * @static + */ + FADE_OUT: "TVFadeOut", + + /** + * Returns a ygAnim instance of the given type + * @method getAnim + * @param type {string} the type of animation + * @param el {HTMLElement} the element to element (probably the children div) + * @param callback {function} function to invoke when the animation is done. + * @return {YAHOO.util.Animation} the animation instance + * @static + */ + getAnim: function(type, el, callback) { + if (YAHOO.widget[type]) { + return new YAHOO.widget[type](el, callback); + } else { + return null; + } + }, + + /** + * Returns true if the specified animation class is available + * @method isValid + * @param type {string} the type of animation + * @return {boolean} true if valid, false if not + * @static + */ + isValid: function(type) { + return (YAHOO.widget[type]); + } + }; +} (); + +/** + * A 1/2 second fade-in animation. + * @class TVFadeIn + * @constructor + * @param el {HTMLElement} the element to animate + * @param callback {function} function to invoke when the animation is finished + */ +YAHOO.widget.TVFadeIn = function(el, callback) { + /** + * The element to animate + * @property el + * @type HTMLElement + */ + this.el = el; + + /** + * the callback to invoke when the animation is complete + * @property callback + * @type function + */ + this.callback = callback; + +}; + +YAHOO.widget.TVFadeIn.prototype = { + /** + * Performs the animation + * @method animate + */ + animate: function() { + var tvanim = this; + + var s = this.el.style; + s.opacity = 0.1; + s.filter = "alpha(opacity=10)"; + s.display = ""; + + var dur = 0.4; + var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}, dur); + a.onComplete.subscribe( function() { tvanim.onComplete(); } ); + a.animate(); + }, + + /** + * Clean up and invoke callback + * @method onComplete + */ + onComplete: function() { + this.callback(); + }, + + /** + * toString + * @method toString + * @return {string} the string representation of the instance + */ + toString: function() { + return "TVFadeIn"; + } +}; + +/** + * A 1/2 second fade out animation. + * @class TVFadeOut + * @constructor + * @param el {HTMLElement} the element to animate + * @param callback {Function} function to invoke when the animation is finished + */ +YAHOO.widget.TVFadeOut = function(el, callback) { + /** + * The element to animate + * @property el + * @type HTMLElement + */ + this.el = el; + + /** + * the callback to invoke when the animation is complete + * @property callback + * @type function + */ + this.callback = callback; + +}; + +YAHOO.widget.TVFadeOut.prototype = { + /** + * Performs the animation + * @method animate + */ + animate: function() { + var tvanim = this; + var dur = 0.4; + var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur); + a.onComplete.subscribe( function() { tvanim.onComplete(); } ); + a.animate(); + }, + + /** + * Clean up and invoke callback + * @method onComplete + */ + onComplete: function() { + var s = this.el.style; + s.display = "none"; + // s.opacity = 1; + s.filter = "alpha(opacity=100)"; + this.callback(); + }, + + /** + * toString + * @method toString + * @return {string} the string representation of the instance + */ + toString: function() { + return "TVFadeOut"; + } +}; + diff --git a/frontend/beta/js/YUI/yahoo.js b/frontend/beta/js/YUI/yahoo.js new file mode 100644 index 0000000..8a44a91 --- a/dev/null +++ b/frontend/beta/js/YUI/yahoo.js @@ -0,0 +1,145 @@ +/* +Copyright (c) 2006, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.net/yui/license.txt +version: 0.12.0 +*/ + +/** + * The YAHOO object is the single global object used by YUI Library. It + * contains utility function for setting up namespaces, inheritance, and + * logging. YAHOO.util, YAHOO.widget, and YAHOO.example are namespaces + * created automatically for and used by the library. + * @module yahoo + * @title YAHOO Global + */ + +/** + * The YAHOO global namespace object + * @class YAHOO + * @static + */ +if (typeof YAHOO == "undefined") { + var YAHOO = {}; +} + +/** + * Returns the namespace specified and creates it if it doesn't exist + *'; + + for (var i=0;i '; + sb[sb.length] = ''; + } + + if (this.hasIcon) { + sb[sb.length] = ' '; + } + + sb[sb.length] = ' '; + sb[sb.length] = this.html; + sb[sb.length] = ' '; + sb[sb.length] = '+ * YAHOO.namespace("property.package"); + * YAHOO.namespace("YAHOO.property.package"); + *+ * Either of the above would create YAHOO.property, then + * YAHOO.property.package + * + * Be careful when naming packages. Reserved words may work in some browsers + * and not others. For instance, the following will fail in Safari: + *+ * YAHOO.namespace("really.long.nested.namespace"); + *+ * This fails because "long" is a future reserved word in ECMAScript + * + * @method namespace + * @static + * @param {String*} arguments 1-n namespaces to create + * @return {Object} A reference to the last namespace object created + */ +YAHOO.namespace = function() { + var a=arguments, o=null, i, j, d; + for (i=0; i