summaryrefslogtreecommitdiff
path: root/frontend/beta/js/YUI-extensions
Side-by-side diff
Diffstat (limited to 'frontend/beta/js/YUI-extensions') (more/less context) (ignore whitespace changes)
-rw-r--r--frontend/beta/js/YUI-extensions/Bench.js40
-rw-r--r--frontend/beta/js/YUI-extensions/CSS.js208
-rw-r--r--frontend/beta/js/YUI-extensions/CompositeElement.js140
-rw-r--r--frontend/beta/js/YUI-extensions/CustomTagReader.js40
-rw-r--r--frontend/beta/js/YUI-extensions/Date.js407
-rw-r--r--frontend/beta/js/YUI-extensions/DomHelper.js416
-rw-r--r--frontend/beta/js/YUI-extensions/Element.js2157
-rw-r--r--frontend/beta/js/YUI-extensions/EventManager.js456
-rw-r--r--frontend/beta/js/YUI-extensions/JSON.js132
-rw-r--r--frontend/beta/js/YUI-extensions/KeyMap.js135
-rw-r--r--frontend/beta/js/YUI-extensions/Layer.js246
-rw-r--r--frontend/beta/js/YUI-extensions/MixedCollection.js344
-rw-r--r--frontend/beta/js/YUI-extensions/State.js264
-rw-r--r--frontend/beta/js/YUI-extensions/UpdateManager.js484
-rw-r--r--frontend/beta/js/YUI-extensions/anim/Actor.js759
-rw-r--r--frontend/beta/js/YUI-extensions/anim/Animator.js482
-rw-r--r--frontend/beta/js/YUI-extensions/data/AbstractDataModel.js226
-rw-r--r--frontend/beta/js/YUI-extensions/data/DefaultDataModel.js339
-rw-r--r--frontend/beta/js/YUI-extensions/data/JSONDataModel.js81
-rw-r--r--frontend/beta/js/YUI-extensions/data/LoadableDataModel.js330
-rw-r--r--frontend/beta/js/YUI-extensions/data/Tree.js412
-rw-r--r--frontend/beta/js/YUI-extensions/data/XMLDataModel.js274
-rw-r--r--frontend/beta/js/YUI-extensions/dd/DragSource.js218
-rw-r--r--frontend/beta/js/YUI-extensions/dd/DragZone.js64
-rw-r--r--frontend/beta/js/YUI-extensions/dd/DropTarget.js45
-rw-r--r--frontend/beta/js/YUI-extensions/dd/DropZone.js81
-rw-r--r--frontend/beta/js/YUI-extensions/dd/Registry.js80
-rw-r--r--frontend/beta/js/YUI-extensions/dd/ScrollManager.js171
-rw-r--r--frontend/beta/js/YUI-extensions/dd/StatusProxy.js110
-rw-r--r--frontend/beta/js/YUI-extensions/grid/AbstractColumnModel.js131
-rw-r--r--frontend/beta/js/YUI-extensions/grid/DefaultColumnModel.js325
-rw-r--r--frontend/beta/js/YUI-extensions/grid/EditorGrid.js16
-rw-r--r--frontend/beta/js/YUI-extensions/grid/EditorSelectionModel.js182
-rw-r--r--frontend/beta/js/YUI-extensions/grid/Grid.js965
-rw-r--r--frontend/beta/js/YUI-extensions/grid/GridDD.js101
-rw-r--r--frontend/beta/js/YUI-extensions/grid/GridView.js790
-rw-r--r--frontend/beta/js/YUI-extensions/grid/PagedGridView.js194
-rw-r--r--frontend/beta/js/YUI-extensions/grid/SelectionModel.js445
-rw-r--r--frontend/beta/js/YUI-extensions/grid/editor/CellEditor.js91
-rw-r--r--frontend/beta/js/YUI-extensions/grid/editor/CheckboxEditor.js60
-rw-r--r--frontend/beta/js/YUI-extensions/grid/editor/DateEditor.js268
-rw-r--r--frontend/beta/js/YUI-extensions/grid/editor/NumberEditor.js166
-rw-r--r--frontend/beta/js/YUI-extensions/grid/editor/SelectEditor.js37
-rw-r--r--frontend/beta/js/YUI-extensions/grid/editor/TextEditor.js110
-rw-r--r--frontend/beta/js/YUI-extensions/layout/BasicLayoutRegion.js265
-rw-r--r--frontend/beta/js/YUI-extensions/layout/BorderLayout.js281
-rw-r--r--frontend/beta/js/YUI-extensions/layout/BorderLayoutRegions.js207
-rw-r--r--frontend/beta/js/YUI-extensions/layout/ContentPanels.js325
-rw-r--r--frontend/beta/js/YUI-extensions/layout/LayoutManager.js135
-rw-r--r--frontend/beta/js/YUI-extensions/layout/LayoutRegion.js496
-rw-r--r--frontend/beta/js/YUI-extensions/layout/LayoutStateManager.js68
-rw-r--r--frontend/beta/js/YUI-extensions/layout/SplitLayoutRegion.js282
-rw-r--r--frontend/beta/js/YUI-extensions/tree/AsyncTreeNode.js58
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeDragZone.js43
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeDropZone.js228
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeFilter.js105
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeLoader.js107
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeNode.js300
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeNodeUI.js452
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreePanel.js213
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeSelectionModel.js195
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeSorter.js49
-rw-r--r--frontend/beta/js/YUI-extensions/widgets/BasicDialog.js1046
-rw-r--r--frontend/beta/js/YUI-extensions/widgets/Button.js185
-rw-r--r--frontend/beta/js/YUI-extensions/widgets/DatePicker.js344
-rw-r--r--frontend/beta/js/YUI-extensions/widgets/InlineEditor.js216
-rw-r--r--frontend/beta/js/YUI-extensions/widgets/MessageBox.js230
-rw-r--r--frontend/beta/js/YUI-extensions/widgets/QuickTips.js311
-rw-r--r--frontend/beta/js/YUI-extensions/widgets/Resizable.js586
-rw-r--r--frontend/beta/js/YUI-extensions/widgets/SplitBar.js468
-rw-r--r--frontend/beta/js/YUI-extensions/widgets/TabPanel.js756
-rw-r--r--frontend/beta/js/YUI-extensions/widgets/TaskPanel.js0
-rw-r--r--frontend/beta/js/YUI-extensions/widgets/TemplateView.js766
-rw-r--r--frontend/beta/js/YUI-extensions/widgets/Toolbar.js296
-rw-r--r--frontend/beta/js/YUI-extensions/yutil.js637
75 files changed, 22672 insertions, 0 deletions
diff --git a/frontend/beta/js/YUI-extensions/Bench.js b/frontend/beta/js/YUI-extensions/Bench.js
new file mode 100644
index 0000000..6921131
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/Bench.js
@@ -0,0 +1,40 @@
+// @deprecated
+// Use YAHOO.timer() instead
+YAHOO.ext.util.Bench = function(){
+ this.timers = {};
+ this.lastKey = null;
+};
+YAHOO.ext.util.Bench.prototype = {
+ start : function(key){
+ this.lastKey = key;
+ this.timers[key] = {};
+ this.timers[key].startTime = new Date().getTime();
+ },
+
+ stop : function(key){
+ key = key || this.lastKey;
+ this.timers[key].endTime = new Date().getTime();
+ },
+
+ getElapsed : function(key){
+ key = key || this.lastKey;
+ return this.timers[key].endTime - this.timers[key].startTime;
+ },
+
+ toString : function(html){
+ var results = "";
+ for(var key in this.timers){
+ if(typeof this.timers[key] != 'function'){
+ results += key + ":\t" + (this.getElapsed(key) / 1000) + " seconds\n";
+ }
+ }
+ if(html){
+ results = results.replace("\n", '<br>');
+ }
+ 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.
+ * <br><br>
+ * <b>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.</b>
+ * <br><br>
+ * All methods return <i>this</i> and can be chained.
+ <pre><code>
+ 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);
+ </code></pre>
+ */
+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 <i>this</i> 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.
+ * <br><br>
+ * <b>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.</b>
+ */
+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 <a href="http://www.php.net/date">PHP's date() function</a>.
+ */
+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 <a href="http://www.php.net/date">PHP's date() function</a>.
+ */
+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 <a href="http://www.jackslocum.com/yui/2006/10/06/domhelper-create-elements-using-dom-html-fragments-or-templates/">this blog post with examples</a>.
+ * @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 = '<table><tbody>'+html+'</tbody></table>';
+ node = tempTableEl.firstChild.firstChild.firstChild;
+ }else{
+ tempTableEl.innerHTML = '<table><tbody><tr>'+html+'</tr></tbody></table>';
+ 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 {<i>Boolean</i>} 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 {<i>Boolean</i>} 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 {<i>Boolean</i>} 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 {<i>Boolean</i>} 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 <a href="http://www.jackslocum.com/yui/2006/10/06/domhelper-create-elements-using-dom-html-fragments-or-templates/">this blog post with examples</a>.
+* <br>
+* <b>This class is also available as YAHOO.ext.Template</b>.
+* @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 {<i>Boolean</i>} 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 {<i>Boolean</i>} 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 {<i>Boolean</i>} 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 {<i>Boolean</i>} 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).<br><br>
+ * Usage:<br>
+ * <pre><code>
+ * var el = YAHOO.ext.Element.get('myElementId');
+ * // or the shorter
+ * var el = getEl('myElementId');
+ * </code></pre>
+ * 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.<br><br>
+ * For working with collections of Elements, see <a href="YAHOO.ext.CompositeElement.html">YAHOO.ext.CompositeElement</a>
+ * @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 {<i>Boolean</i>} 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 {<i>Float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeBoth)
+ * @param {<i>Function</i>} 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 {<i>String/HTMLElement/Element</i>} 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 {<i>Boolean</i>} animate (optional) Animate the transition (Default is false)
+ * @param {<i>Float</i>} duration (optional) Length of the animation. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} 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 {<i>Boolean</i>} animate (optional) Fade the element in or out (Default is false)
+ * @param {<i>Float</i>} duration (optional) How long the fade effect lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} animate (optional) Fade the element in or out (Default is false)
+ * @param {<i>float</i>} duration (optional) How long the fade effect lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} animate (optional) Animate the transition (Default is false)
+ * @param {<i>float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} animate (optional) Animate the transition (Default is false)
+ * @param {<i>float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} animate (optional) Animate the transition (Default is false)
+ * @param {<i>float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} animate (optional) Animate the transition (Default is false)
+ * @param {<i>float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} animate (optional) Animate the transition (Default is false)
+ * @param {<i>float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} animate (optional) Animate the transition (Default is false)
+ * @param {<i>float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} animate (optional) Animate the transition (Default is false)
+ * @param {<i>float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} animate (optional) Animate the transition (Default is false)
+ * @param {<i>float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} animate (optional) Animate the transition (Default is false)
+ * @param {<i>float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} animate (optional) Animate the transition (Default is false)
+ * @param {<i>float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Object</i>} scope (optional) An arbitrary object that will be
+ * passed as a parameter to the handler
+ * @param {<i>boolean</i>} 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 {<i>Object</i>} scope (optional) The scope (this object) for the handler
+ * @param {<i>Number</i>} 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 {<i>Object</i>} scope (optional) An arbitrary object that will be
+ * passed as a parameter to the handler
+ * @param {<i>boolean</i>} 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 {<i>Object</i>} scope (optional) An arbitrary object that will be
+ * passed as a parameter to the handler
+ * @param {<i>boolean</i>} 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 {<i>Object</i>} scope (optional) An arbitrary object that will be
+ * passed as a parameter to the handler
+ * @param {<i>boolean</i>} 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 {<i>Object</i>} scope (optional) An arbitrary object that will be
+ * passed as a parameter to the handler
+ * @param {<i>boolean</i>} 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 {<i>Boolean</i>} animate (optional) Animate (fade) the transition (Default is false)
+ * @param {<i>Float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Number</i>} 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 {<i>Number</i>} 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 {<i>Boolean</i>} animate (optional) Animate the movement (Default is false)
+ * @param {<i>Float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Array</i>} offsets (optional) Offset the positioning by [x, y]
+ * @param {<i>Boolean</i>} animate (optional) Animate the movement (Default is false)
+ * @param {<i>Float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} animate (optional) Animate (fade) the transition (Default is false)
+ * @param {<i>Float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} animate (optional) Animate (fade in) the transition (Default is false)
+ * @param {<i>Float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} 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 {<i>Boolean</i>} 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 += '<span id="' + id + '"></span>';
+
+ YAHOO.util.Event.onAvailable(id, function(){
+ var hd = document.getElementsByTagName("head")[0];
+ var re = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/img;
+ var srcRe = /\ssrc=([\'\"])(.*?)\1/i;
+ var match;
+ while(match = re.exec(html)){
+ var srcMatch = match[0].match(srcRe);
+ if(srcMatch && srcMatch[2]){
+ var s = document.createElement("script");
+ s.src = srcMatch[2];
+ hd.appendChild(s);
+ }else if(match[1] && match[1].length > 0){
+ eval(match[1]);
+ }
+ }
+ var el = document.getElementById(id);
+ if(el){el.parentNode.removeChild(el);}
+ if(typeof callback == 'function'){
+ callback();
+ }
+ });
+ dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/img, '');
+ return this;
+ },
+
+ /**
+ * Direct access to the UpdateManager update() method (takes the same parameters).
+ * @param {String/Function} url The url for this request or a function to call to get the url
+ * @param {<i>String/Object</i>} params (optional) The parameters to pass as either a url encoded string "param1=1&amp;param2=2" or an object {param1: 1, param2: 2}
+ * @param {<i>Function</i>} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess)
+ * @param {<i>Boolean</i>} 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.
+ * @return {YAHOO.ext.Element} this
+ */
+ load : function(){
+ var um = this.getUpdateManager();
+ um.update.apply(um, arguments);
+ return this;
+ },
+
+ /**
+ * Gets this elements UpdateManager
+ * @return {YAHOO.ext.UpdateManager} The UpdateManager
+ */
+ getUpdateManager : function(){
+ if(!this.updateManager){
+ this.updateManager = new YAHOO.ext.UpdateManager(this);
+ }
+ return this.updateManager;
+ },
+
+ /**
+ * Disables text selection for this element (normalized across browsers)
+ * @return {YAHOO.ext.Element} this
+ */
+ unselectable : function(){
+ this.dom.unselectable = 'on';
+ this.swallowEvent('selectstart', true);
+ this.applyStyles('-moz-user-select:none;-khtml-user-select:none;');
+ return this;
+ },
+
+ /**
+ * Calculates the x, y to center this element on the screen
+ * @param {Boolean} offsetScroll True to offset the documents current scroll position
+ * @return {Array} The x, y values [x, y]
+ */
+ getCenterXY : function(offsetScroll){
+ var centerX = Math.round((YAHOO.util.Dom.getViewportWidth()-this.getWidth())/2);
+ var centerY = Math.round((YAHOO.util.Dom.getViewportHeight()-this.getHeight())/2);
+ if(!offsetScroll){
+ return [centerX, centerY];
+ }else{
+ var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft || 0;
+ var scrollY = document.documentElement.scrollTop || document.body.scrollTop || 0;
+ return[centerX + scrollX, centerY + scrollY];
+ }
+ },
+
+ /**
+ * Centers the Element in either the viewport, or another Element.
+ * @param {String/HTMLElement/YAHOO.ext.Element} centerIn (optional) The element in which to center the element.
+ */
+ center : function(centerIn) {
+ if(!centerIn){
+ this.setXY(this.getCenterXY(true));
+ }else{
+ var box = YAHOO.ext.Element.get(centerIn).getBox();
+ this.setXY([box.x + (box.width / 2) - (this.getWidth() / 2),
+ box.y + (box.height / 2) - (this.getHeight() / 2)]);
+ }
+ return this;
+ },
+
+ /**
+ * Gets an array of child YAHOO.ext.Element objects by tag name
+ * @param {String} tagName
+ * @return {Array} The children
+ */
+ getChildrenByTagName : function(tagName){
+ var children = this.dom.getElementsByTagName(tagName);
+ var len = children.length;
+ var ce = new Array(len);
+ for(var i = 0; i < len; ++i){
+ ce[i] = YAHOO.ext.Element.get(children[i], true);
+ }
+ return ce;
+ },
+
+ /**
+ * Gets an array of child YAHOO.ext.Element objects by class name and optional tagName
+ * @param {String} className
+ * @param {<i>String</i>} tagName (optional)
+ * @return {Array} The children
+ */
+ getChildrenByClassName : function(className, tagName){
+ var children = YAHOO.util.Dom.getElementsByClassName(className, tagName, this.dom);
+ var len = children.length;
+ var ce = new Array(len);
+ for(var i = 0; i < len; ++i){
+ ce[i] = YAHOO.ext.Element.get(children[i], true);
+ }
+ return ce;
+ },
+
+ /**
+ * Tests various css rules/browsers to determine if this element uses a border box
+ * @return {Boolean}
+ */
+ isBorderBox : function(){
+ if(typeof this.bbox == 'undefined'){
+ var el = this.dom;
+ var b = YAHOO.ext.util.Browser;
+ var strict = YAHOO.ext.Strict;
+ this.bbox = ((b.isIE && !strict && el.style.boxSizing != 'content-box') ||
+ (b.isGecko && YAHOO.util.Dom.getStyle(el, "-moz-box-sizing") == 'border-box') ||
+ (!b.isSafari && YAHOO.util.Dom.getStyle(el, "box-sizing") == 'border-box'));
+ }
+ return this.bbox;
+ },
+
+ /**
+ * Return a box {x, y, width, height} that can be used to set another elements
+ * size/location to match this element.
+ * @param {Boolean} contentBox (optional) If true a box for the content of the element is returned.
+ * @param {Boolean} local (optional) If true the element's left and top are returned instead of page x/y.
+ * @return {Object}
+ */
+ getBox : function(contentBox, local){
+ var xy;
+ if(!local){
+ xy = this.getXY();
+ }else{
+ var left = parseInt(YAHOO.util.Dom.getStyle('left'), 10) || 0;
+ var top = parseInt(YAHOO.util.Dom.getStyle('top'), 10) || 0;
+ xy = [left, top];
+ }
+ var el = this.dom;
+ var w = el.offsetWidth;
+ var h = el.offsetHeight;
+ if(!contentBox){
+ return {x: xy[0], y: xy[1], width: w, height: h};
+ }else{
+ var l = this.getBorderWidth('l')+this.getPadding('l');
+ var r = this.getBorderWidth('r')+this.getPadding('r');
+ var t = this.getBorderWidth('t')+this.getPadding('t');
+ var b = this.getBorderWidth('b')+this.getPadding('b');
+ return {x: xy[0]+l, y: xy[1]+t, width: w-(l+r), height: h-(t+b)};
+ }
+ },
+
+ /**
+ * Sets the element's box. Use getBox() on another element to get a box obj. If animate is true then width, height, x and y will be animated concurrently.
+ * @param {Object} box The box to fill {x, y, width, height}
+ * @param {<i>Boolean</i>} adjust (optional) Whether to adjust for box-model issues automatically
+ * @param {<i>Boolean</i>} animate (optional) Animate the transition (Default is false)
+ * @param {<i>float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} easing (optional) YAHOO.util.Easing method to use. (Defaults to YAHOO.util.Easing.easeBoth)
+ * @return {YAHOO.ext.Element} this
+ */
+ setBox : function(box, adjust, animate, duration, onComplete, easing){
+ var w = box.width, h = box.height;
+ if((adjust && !this.autoBoxAdjust) && !this.isBorderBox()){
+ w -= (this.getBorderWidth('lr') + this.getPadding('lr'));
+ h -= (this.getBorderWidth('tb') + this.getPadding('tb'));
+ }
+ this.setBounds(box.x, box.y, w, h, animate, duration, onComplete, easing);
+ return this;
+ },
+
+ /**
+ * Forces the browser to repaint this element
+ * @return {YAHOO.ext.Element} this
+ */
+ repaint : function(){
+ var dom = this.dom;
+ YAHOO.util.Dom.addClass(dom, 'yui-ext-repaint');
+ setTimeout(function(){
+ YAHOO.util.Dom.removeClass(dom, 'yui-ext-repaint');
+ }, 1);
+ return this;
+ },
+
+ /**
+ * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
+ * then it returns the calculated width of the sides (see getPadding)
+ * @param {String} sides (optional) Any combination of l, r, t, b to get the sum of those sides
+ * @return {Object/Number}
+ */
+ getMargins : function(side){
+ if(!side){
+ return {
+ top: parseInt(this.getStyle('margin-top'), 10) || 0,
+ left: parseInt(this.getStyle('margin-left'), 10) || 0,
+ bottom: parseInt(this.getStyle('margin-bottom'), 10) || 0,
+ right: parseInt(this.getStyle('margin-right'), 10) || 0
+ };
+ }else{
+ return this.addStyles(side, YAHOO.ext.Element.margins);
+ }
+ },
+
+ addStyles : function(sides, styles){
+ var val = 0;
+ for(var i = 0, len = sides.length; i < len; i++){
+ var w = parseInt(this.getStyle(styles[sides.charAt(i)]), 10);
+ if(!isNaN(w)) val += w;
+ }
+ return val;
+ },
+
+ /**
+ * Creates a proxy element of this element
+ * @param {String/Object} config The class name of the proxy element or a DomHelper config object
+ * @param {<i>String/HTMLElement</i>} renderTo (optional) The element or element id to render the proxy to (defaults to document.body)
+ * @param {<i>Boolean</i>} matchBox (optional) True to align and size the proxy to this element now (defaults to false)
+ * @return {YAHOO.ext.Element} The new proxy element
+ */
+ createProxy : function(config, renderTo, matchBox){
+ if(renderTo){
+ renderTo = YAHOO.util.Dom.get(renderTo);
+ }else{
+ renderTo = document.body;
+ }
+ config = typeof config == 'object' ?
+ config : {tag : 'div', cls: config};
+ var proxy = YAHOO.ext.DomHelper.append(renderTo, config, true);
+ if(matchBox){
+ proxy.setBox(this.getBox());
+ }
+ return proxy;
+ },
+
+ /**
+ * Puts a mask over this element to disable user interaction. Requires core.css.
+ * This method can only be applied to elements which accept child nodes.
+ * @return {Element} The message element
+ */
+ mask : function(){
+ if(this.getStyle('position') == 'static'){
+ this.setStyle('position', 'relative');
+ }
+ if(!this._mask){
+ this._mask = YAHOO.ext.DomHelper.append(this.dom, {tag:'div', cls:'ext-el-mask'}, true);
+ }
+ this.addClass('ext-masked');
+ this._mask.setDisplayed(true);
+ return this._mask;
+ },
+
+ /**
+ * Removes a previously applied mask. If removeEl is true the mask overlay is destroyed, otherwise
+ * it is cached for reuse.
+ */
+ unmask : function(removeEl){
+ if(this._mask){
+ removeEl === true ?
+ this._mask.remove() : this._mask.setDisplayed(false);
+ }
+ this.removeClass('ext-masked');
+ },
+
+ /**
+ * Creates an iframe shim for this element to keep selects and other windowed objects from
+ * showing through.
+ * @return {YAHOO.ext.Element} The new shim element
+ */
+ createShim : function(){
+ var config = {
+ tag : 'iframe',
+ frameBorder:'no',
+ cls: 'yiframe-shim',
+ style: 'position:absolute;visibility:hidden;left:0;top:0;overflow:hidden;',
+ src: YAHOO.ext.SSL_SECURE_URL
+ };
+ var shim = YAHOO.ext.DomHelper.insertBefore(this.dom, config, true);
+ shim.setOpacity(.01);
+ shim.setBox(this.getBox());
+ return shim;
+ },
+
+ /**
+ * Removes this element from the DOM and deletes it from the cache
+ */
+ remove : function(){
+ this.dom.parentNode.removeChild(this.dom);
+ delete YAHOO.ext.Element.cache[this.dom.id];
+ },
+
+ /**
+ * Sets up event handlers to add and remove a css class when the mouse is over this element
+ * @param {String} className
+ * @return {YAHOO.ext.Element} this
+ */
+ addClassOnOver : function(className){
+ this.on('mouseover', function(){
+ this.addClass(className);
+ }, this, true);
+ this.on('mouseout', function(){
+ this.removeClass(className);
+ }, this, true);
+ return this;
+ },
+
+ /**
+ * Stops the specified event from bubbling and optionally prevent's the default action
+ * @param {String} eventName
+ * @param {Boolean} preventDefault (optional) true to prevent the default action too
+ * @return {YAHOO.ext.Element} this
+ */
+ swallowEvent : function(eventName, preventDefault){
+ var fn = function(e){
+ e.stopPropagation();
+ if(preventDefault){
+ e.preventDefault();
+ }
+ };
+ this.mon(eventName, fn);
+ return this;
+ },
+
+ /**
+ * Sizes this element to it's parent element's dimensions performing
+ * neccessary box adjustments.
+ * @param {Boolean} monitorResize (optional) If true maintains the fit when the browser window is resized.
+ * @param {String/HTMLElment/Element} targetParent (optional) The target parent, default to the parentNode.
+ * @return {YAHOO.ext.Element} this
+ */
+ fitToParent : function(monitorResize, targetParent){
+ var p = getEl(targetParent || this.dom.parentNode);
+ p.beginMeasure(); // in case parent is display:none
+ var box = p.getBox(true, true);
+ p.endMeasure();
+ this.setSize(box.width, box.height);
+ if(monitorResize === true){
+ YAHOO.ext.EventManager.onWindowResize(this.fitToParent, this, true);
+ }
+ return this;
+ },
+
+ /**
+ * Gets the next sibling, skipping text nodes
+ * @return {HTMLElement} The next sibling or null
+ */
+ getNextSibling : function(){
+ var n = this.dom.nextSibling;
+ while(n && n.nodeType != 1){
+ n = n.nextSibling;
+ }
+ return n;
+ },
+
+ /**
+ * Gets the previous sibling, skipping text nodes
+ * @return {HTMLElement} The previous sibling or null
+ */
+ getPrevSibling : function(){
+ var n = this.dom.previousSibling;
+ while(n && n.nodeType != 1){
+ n = n.previousSibling;
+ }
+ return n;
+ },
+
+
+ /**
+ * Appends the passed element(s) to this element
+ * @param {String/HTMLElement/Array/Element/CompositeElement} el
+ * @return {YAHOO.ext.Element} this
+ */
+ appendChild: function(el){
+ el = getEl(el);
+ el.appendTo(this);
+ return this;
+ },
+
+ /**
+ * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
+ * @param {Object} config DomHelper element config object
+ * @param {<i>HTMLElement</i>} insertBefore (optional) a child element of this element
+ * @return {YAHOO.ext.Element} The new child element
+ */
+ createChild: function(config, insertBefore){
+ var c;
+ if(insertBefore){
+ c = YAHOO.ext.DomHelper.insertBefore(insertBefore, config, true);
+ }else{
+ c = YAHOO.ext.DomHelper.append(this.dom, config, true);
+ }
+ return c;
+ },
+
+ /**
+ * Appends this element to the passed element
+ * @param {String/HTMLElement/Element} el The new parent element
+ * @return {YAHOO.ext.Element} this
+ */
+ appendTo: function(el){
+ var node = getEl(el).dom;
+ node.appendChild(this.dom);
+ return this;
+ },
+
+ /**
+ * Inserts this element before the passed element in the DOM
+ * @param {String/HTMLElement/Element} el The element to insert before
+ * @return {YAHOO.ext.Element} this
+ */
+ insertBefore: function(el){
+ var node = getEl(el).dom;
+ node.parentNode.insertBefore(this.dom, node);
+ return this;
+ },
+
+ /**
+ * Inserts this element after the passed element in the DOM
+ * @param {String/HTMLElement/Element} el The element to insert after
+ * @return {YAHOO.ext.Element} this
+ */
+ insertAfter: function(el){
+ var node = getEl(el).dom;
+ node.parentNode.insertBefore(this.dom, node.nextSibling);
+ return this;
+ },
+
+ /**
+ * Creates and wraps this element with another element
+ * @param {Object} config (optional) DomHelper element config object for the wrapper element or null for an empty div
+ * @return {Element} The newly created wrapper element
+ */
+ wrap: function(config){
+ if(!config){
+ config = {tag: 'div'};
+ }
+ var newEl = YAHOO.ext.DomHelper.insertBefore(this.dom, config, true);
+ newEl.dom.appendChild(this.dom);
+ return newEl;
+ },
+
+ /**
+ * Replaces the passed element with this element
+ * @param {String/HTMLElement/Element} el The element to replace
+ * @return {YAHOO.ext.Element} this
+ */
+ replace: function(el){
+ el = getEl(el);
+ this.insertBefore(el);
+ el.remove();
+ return this;
+ },
+
+ /**
+ * Inserts an html fragment into this element
+ * @param {String} where Where to insert the html in relation to the this element - beforeBegin, afterBegin, beforeEnd, afterEnd.
+ * @param {String} html The HTML fragment
+ * @return {HTMLElement} The inserted node (or nearest related if more than 1 inserted)
+ */
+ insertHtml : function(where, html){
+ return YAHOO.ext.DomHelper.insertHtml(where, this.dom, html);
+ },
+
+ /**
+ * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function)
+ * @param {Object} o The object with the attributes
+ * @return {YAHOO.ext.Element} this
+ */
+ set : function(o){
+ var el = this.dom;
+ var useSet = el.setAttribute ? true : false;
+ for(var attr in o){
+ if(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);
+ return this;
+ },
+
+ /**
+ * Convenience method for constructing a KeyMap
+ * @param {Number/Array/Object/String} key Either a string with the keys to listen for, the numeric key code, array of key codes or an object with the following options:
+ * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
+ * @param {Function} fn The function to call
+ * @param {Object} scope (optional) The scope of the function
+ * @return {YAHOO.ext.KeyMap} The KeyMap created
+ */
+ addKeyListener : function(key, fn, scope){
+ var config;
+ if(typeof key != 'object' || key instanceof Array){
+ config = {
+ key: key,
+ fn: fn,
+ scope: scope
+ };
+ }else{
+ config = {
+ key : key.key,
+ shift : key.shift,
+ ctrl : key.ctrl,
+ alt : key.alt,
+ fn: fn,
+ scope: scope
+ };
+ }
+ var map = new YAHOO.ext.KeyMap(this, config);
+ return map;
+ },
+
+ /**
+ * Creates a KeyMap for this element
+ * @param {Object} config The KeyMap config. See {@link YAHOO.ext.KeyMap} for more details
+ * @return {YAHOO.ext.KeyMap} The KeyMap created
+ */
+ addKeyMap : function(config){
+ return new YAHOO.ext.KeyMap(this, config);
+ },
+
+ /**
+ * Returns true if this element is scrollable.
+ * @return {Boolean}
+ */
+ isScrollable : function(){
+ var dom = this.dom;
+ return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
+ },
+
+ /**
+ * Scrolls this element the specified scroll point. It does NOT do bounds checking so if you scroll to a weird value it will try to do it. For auto bounds checking, use scroll().
+ * @param {String} side Either 'left' for scrollLeft values or 'top' for scrollTop values.
+ * @param {Number} value The new scroll value
+ * @param {<i>Boolean</i>} animate (optional) Animate the scroll (Default is false)
+ * @param {<i>Float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} easing (optional) YAHOO.util.Easing method to use.
+ * @return {Element} this
+ */
+
+ scrollTo : function(side, value, animate, duration, onComplete, easing){
+ var prop = side.toLowerCase() == 'left' ? 'scrollLeft' : 'scrollTop';
+ if(!animate || !YAHOO.util.Anim){
+ this.dom[prop] = value;
+ }else{
+ var to = prop == 'scrollLeft' ? [value, this.dom.scrollTop] : [this.dom.scrollLeft, value];
+ this.anim({scroll: {'to': to}}, duration, onComplete, easing || YAHOO.util.Easing.easeOut, YAHOO.util.Scroll);
+ }
+ return this;
+ },
+
+ /**
+ * Scrolls this element the specified direction. Does bounds checking to make sure the scroll is
+ * within this elements scrollable range.
+ * @param {String} direction Possible values are: 'l','left' - 'r','right' - 't','top','up' - 'b','bottom','down'.
+ * @param {Number} distance How far to scroll the element in pixels
+ * @param {<i>Boolean</i>} animate (optional) Animate the scroll (Default is false)
+ * @param {<i>Float</i>} duration (optional) How long the animation lasts. (Defaults to .35 seconds)
+ * @param {<i>Function</i>} onComplete (optional) Function to call when animation completes.
+ * @param {<i>Function</i>} easing (optional) YAHOO.util.Easing method to use.
+ * @return {Boolean} Returns true if a scroll was triggered or false if the element
+ * was scrolled as far as it could go.
+ */
+ scroll : function(direction, distance, animate, duration, onComplete, easing){
+ if(!this.isScrollable()){
+ return;
+ }
+ var el = this.dom;
+ var l = el.scrollLeft, t = el.scrollTop;
+ var w = el.scrollWidth, h = el.scrollHeight;
+ var cw = el.clientWidth, ch = el.clientHeight;
+ direction = direction.toLowerCase();
+ var scrolled = false;
+ switch(direction){
+ case 'l':
+ case 'left':
+ if(w - l > cw){
+ var v = Math.min(l + distance, w-cw);
+ this.scrollTo('left', v, animate, duration, onComplete, easing);
+ scrolled = true;
+ }
+ break;
+ case 'r':
+ case 'right':
+ if(l > 0){
+ var v = Math.max(l - distance, 0);
+ this.scrollTo('left', v, animate, duration, onComplete, easing);
+ scrolled = true;
+ }
+ break;
+ case 't':
+ case 'top':
+ case 'up':
+ if(t > 0){
+ var v = Math.max(t - distance, 0);
+ this.scrollTo('top', v, animate, duration, onComplete, easing);
+ scrolled = true;
+ }
+ break;
+ case 'b':
+ case 'bottom':
+ case 'down':
+ if(h - t > ch){
+ var v = Math.min(t + distance, h-ch);
+ this.scrollTo('top', v, animate, duration, onComplete, easing);
+ scrolled = true;
+ }
+ break;
+ }
+ return scrolled;
+ },
+
+ /**
+ * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like #fff) and valid values
+ * are convert to standard 6 digit hex color.
+ * @param {String} attr The css attribute
+ * @param {String} defaultValue The default value to use when a valid color isn't found
+ * @param {String} prefix (optional) defaults to #. Use an empty string when working with
+ * YUI color anims.
+ */
+ getColor : function(attr, defaultValue, prefix){
+ var v = this.getStyle(attr);
+ if(!v || v == 'transparent' || v == 'inherit') {
+ return defaultValue;
+ }
+ var color = typeof prefix == 'undefined' ? '#' : prefix;
+ if(v.substr(0, 4) == 'rgb('){
+ var rvs = v.slice(4, v.length -1).split(',');
+ for(var i = 0; i < 3; i++){
+ var h = parseInt(rvs[i]).toString(16);
+ if(h < 16){
+ h = '0' + h;
+ }
+ color += h;
+ }
+ } else {
+ if(v.substr(0, 1) == '#'){
+ if(v.length == 4) {
+ for(var i = 1; i < 4; i++){
+ var c = v.charAt(i);
+ color += c + c;
+ }
+ }else if(v.length == 7){
+ color += v.slice(1, 6);
+ }
+ }
+ }
+ return(color.length > 5 ? color.toLowerCase() : defaultValue);
+ },
+
+ /**
+ * Highlights the Element by setting a color (defaults to background-color) and then
+ * fading back to the original color. If no original color is available, you should
+ * provide an "endColor" option which will be cleared after the animation. The available options
+ * for the "options" parameter are listed below (with their default values): <br/>
+<pre><code>
+el.highlight('ff0000', {<br/>
+ attr: 'background-color',<br/>
+ endColor: (current color) or 'ffffff'<br/>
+ callback: yourFunction,<br/>
+ scope: yourObject,<br/>
+ easing: YAHOO.util.Easing.easeNone, <br/>
+ duration: .75<br/>
+});
+</code></pre>
+ * @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:<pre><code>YAHOO.ext.Element.prototype.autoDisplayMode = true;</code></pre>
+ */
+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('<s'+'cript id="ie-deferred-loader" defer="defer" src="' +
+ (YAHOO.ext.EventManager.ieDeferSrc || YAHOO.ext.SSL_SECURE_URL) + '"></s'+'cript>');
+ 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}.<br>
+ * Example:
+ * <pre><code>
+ 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);
+ </code></pre>
+ * @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.<br />
+ * Usage:
+ <pre><code>
+ // 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.'); }
+ }
+]);
+ </code></pre>
+* <b>Note: A KepMap starts enabled</b>
+* @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.<br>
+ * @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.
+ <pre><code>
+ // 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
+ </code>
+ * @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 <b>typed</b> 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.
+ <pre><code>
+// 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();
+ </code></pre>
+ * @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.
+ <pre><code>
+ 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);
+ </code></pre>
+ * @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.<br><br>
+ * Usage:<br>
+ * <pre><code>
+ * // 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&amp;param2=2');
+ * ...
+ * mgr.formUpdate('myFormId', 'http://myserver.com/index.php');
+ * <br>
+ * // 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);
+ * <br>
+ * </code></pre>
+ * @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 {<i>Boolean</i>} 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 '&lt;div class="loading-indicator"&gt;Loading...&lt;/div&gt;').
+ * @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:
+<pre><code>
+um.update({<br/>
+ url: 'your-url.php',<br/>
+ params: {param1: 'foo', param2: 'bar'}, // or a URL encoded string<br/>
+ callback: yourFunction,<br/>
+ scope: yourObject, //(optional scope) <br/>
+ discardUrl: false, <br/>
+ nocache: false,<br/>
+ text: 'Loading...',<br/>
+ timeout: 30,<br/>
+ scripts: false<br/>
+});
+</code></pre>
+ * 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 {<i>String/Object</i>} params (optional) The parameters to pass as either a url encoded string "param1=1&amp;param2=2" or an object {param1: 1, param2: 2}
+ * @param {<i>Function</i>} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess, oResponse)
+ * @param {<i>Boolean</i>} 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 = '<div class="loading-indicator">'+cfg.text+'</div>'};
+ if(typeof cfg.scripts != 'undefined'){this.loadScripts = cfg.scripts};
+ if(typeof cfg.timeout != 'undefined'){this.timeout = cfg.timeout};
+ }
+ this.showLoading();
+ if(!discardUrl){
+ this.defaultUrl = url;
+ }
+ if(typeof url == 'function'){
+ url = url();
+ }
+ if(typeof params == 'function'){
+ params = params();
+ }
+ 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 callback = {
+ success: this.successDelegate,
+ failure: this.failureDelegate,
+ timeout: (this.timeout*1000),
+ argument: {'url': url, 'form': null, 'callback': callback, 'params': params}
+ };
+ var method = params ? 'POST' : 'GET';
+ if(method == 'GET'){
+ url = this.prepareUrl(url);
+ }
+ this.transaction = YAHOO.util.Connect.asyncRequest(method, url, callback, params);
+ }
+ },
+
+ /**
+ * Performs an async form post, updating this element with the response. If the form has the attribute enctype="multipart/form-data", it assumes it's a file upload.
+ * Uses this.sslBlankUrl for SSL file uploads to prevent IE security warning. See YUI docs for more info.
+ * @param {String/HTMLElement} form The form Id or form element
+ * @param {<i>String</i>} url (optional) The url to pass the form to. If omitted the action attribute on the form will be used.
+ * @param {<i>Boolean</i>} reset (optional) Whether to try to reset the form after the update
+ * @param {<i>Function</i>} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess, oResponse)
+ */
+ formUpdate : function(form, url, reset, callback){
+ if(this.beforeUpdate.fireDirect(this.el, form, url) !== false){
+ formEl = YAHOO.util.Dom.get(form);
+ this.showLoading();
+ if(typeof url == 'function'){
+ url = url();
+ }
+ if(typeof params == 'function'){
+ params = params();
+ }
+ url = url || formEl.action;
+ var callback = {
+ success: this.successDelegate,
+ failure: this.failureDelegate,
+ timeout: (this.timeout*1000),
+ argument: {'url': url, 'form': form, 'callback': callback, 'reset': reset}
+ };
+ var isUpload = false;
+ var enctype = formEl.getAttribute('enctype');
+ if(enctype && enctype.toLowerCase() == 'multipart/form-data'){
+ isUpload = true;
+ }
+ YAHOO.util.Connect.setForm(form, isUpload, this.sslBlankUrl);
+ this.transaction = YAHOO.util.Connect.asyncRequest('POST', url, callback);
+ }
+ },
+
+ /**
+ * Refresh the element with the last used url or defaultUrl. If there is no url, it returns immediately
+ * @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess)
+ */
+ refresh : function(callback){
+ if(this.defaultUrl == null){
+ return;
+ }
+ this.update(this.defaultUrl, null, callback, true);
+ },
+
+ /**
+ * Set this element to auto refresh.
+ * @param {Number} interval How often to update (in seconds).
+ * @param {<i>String/Function</i>} url (optional) The url for this request or a function to call to get the url (Defaults to the last used url)
+ * @param {<i>String/Object</i>} params (optional) The parameters to pass as either a url encoded string "&param1=1&param2=2" or as an object {param1: 1, param2: 2}
+ * @param {<i>Function</i>} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess)
+ * @param {<i>Boolean</i>} refreshNow (optional) Whether to execute the refresh now, or wait the interval
+ */
+ startAutoRefresh : function(interval, url, params, callback, refreshNow){
+ if(refreshNow){
+ this.update(url || this.defaultUrl, params, callback, true);
+ }
+ if(this.autoRefreshProcId){
+ clearInterval(this.autoRefreshProcId);
+ }
+ this.autoRefreshProcId = setInterval(this.update.createDelegate(this, [url || this.defaultUrl, params, callback, true]), interval*1000);
+ },
+
+ /**
+ * Stop auto refresh on this element.
+ */
+ stopAutoRefresh : function(){
+ if(this.autoRefreshProcId){
+ clearInterval(this.autoRefreshProcId);
+ }
+ },
+
+ /**
+ * Called to update the element to "Loading" state. Override to perform custom action.
+ */
+ showLoading : function(){
+ if(this.showLoadIndicator){
+ this.el.update(this.indicatorText);
+ }
+ },
+
+ /**
+ * Adds unique parameter to query string if disableCaching = true
+ * @private
+ */
+ prepareUrl : function(url){
+ if(this.disableCaching){
+ var append = '_dc=' + (new Date().getTime());
+ if(url.indexOf('?') !== -1){
+ url += '&' + append;
+ }else{
+ url += '?' + append;
+ }
+ }
+ return url;
+ },
+
+ /**
+ * @private
+ */
+ processSuccess : function(response){
+ this.transaction = null;
+ if(response.argument.form && response.argument.reset){
+ try{ // put in try/catch since some older FF releases had problems with this
+ response.argument.form.reset();
+ }catch(e){}
+ }
+ if(this.loadScripts){
+ this.renderer.render(this.el, response, this,
+ this.updateComplete.createDelegate(this, [response]));
+ }else{
+ this.renderer.render(this.el, response, this);
+ this.updateComplete(response);
+ }
+ },
+
+ updateComplete : function(response){
+ this.fireEvent('update', this.el, response);
+ if(typeof response.argument.callback == 'function'){
+ response.argument.callback(this.el, true, response);
+ }
+ },
+
+ /**
+ * @private
+ */
+ processFailure : function(response){
+ this.transaction = null;
+ this.onFailure.fireDirect(this.el, response);
+ if(typeof response.argument.callback == 'function'){
+ response.argument.callback(this.el, false, response);
+ }
+ },
+
+ /**
+ * Set the content renderer for this UpdateManager. See {@link YAHOO.ext.UpdateManager.BasicRenderer#render} for more details.
+ * @param {Object} renderer The object implementing the render() method
+ */
+ setRenderer : function(renderer){
+ this.renderer = renderer;
+ },
+
+ getRenderer : function(){
+ return this.renderer;
+ },
+
+ /**
+ * Set the defaultUrl used for updates
+ * @param {String/Function} defaultUrl The url or a function to call to get the url
+ */
+ setDefaultUrl : function(defaultUrl){
+ this.defaultUrl = defaultUrl;
+ },
+
+ /**
+ * Aborts the executing transaction
+ */
+ abort : function(){
+ if(this.transaction){
+ YAHOO.util.Connect.abort(this.transaction);
+ }
+ },
+
+ /**
+ * Returns true if an update is in progress
+ * @return {Boolean}
+ */
+ isUpdating : function(){
+ if(this.transaction){
+ return YAHOO.util.Connect.isCallInProgress(this.transaction);
+ }
+ return false;
+ }
+};
+
+/**
+ * @class YAHOO.ext.UpdateManager.defaults
+ * The defaults collection enables customizing the default properties of UpdateManager
+ */
+ YAHOO.ext.UpdateManager.defaults = {
+ /**
+ * Timeout for requests or form posts in seconds (Defaults 30 seconds).
+ * @type Number
+ */
+ timeout : 30,
+
+ /**
+ * True to process scripts by default (Defaults to false).
+ * @type Boolean
+ */
+ loadScripts : false,
+
+ /**
+ * Blank page URL to use with SSL file uploads (Defaults to 'javascript:false').
+ * @type String
+ */
+ sslBlankUrl : (YAHOO.ext.SSL_SECURE_URL || 'javascript:false'),
+ /**
+ * Whether to append unique parameter on get request to disable caching (Defaults to false).
+ * @type Boolean
+ */
+ disableCaching : false,
+ /**
+ * Whether to show indicatorText when loading (Defaults to true).
+ * @type Boolean
+ */
+ showLoadIndicator : true,
+ /**
+ * Text for loading indicator (Defaults to '&lt;div class="loading-indicator"&gt;Loading...&lt;/div&gt;').
+ * @type String
+ */
+ indicatorText : '<div class="loading-indicator">Loading...</div>'
+ };
+
+/**
+ * Static convenience method, Usage:
+ * <pre><code>YAHOO.ext.UpdateManager.updateElement('my-div', 'stuff.php');</code></pre>
+ * @param {String/HTMLElement/YAHOO.ext.Element} el The element to update
+ * @param {String} url The url
+ * @param {<i>String/Object</i>} params (optional) Url encoded param string or an object of name/value pairs
+ * @param {<i>Object</i>} 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.<br><br>
+ * <b>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.</b>
+ * <br><br>Usage:<br>
+ * <pre><code>
+ * var actor = new YAHOO.ext.Actor('myElementId');
+ * actor.startCapture(true);
+ * actor.moveTo(100, 100, true);
+ * actor.squish();
+ * actor.play();
+ * <br>
+ * // or to start capturing immediately, with no Animator (the null second param)
+ * <br>
+ * var actor = new YAHOO.ext.Actor('myElementId', null, true);
+ * actor.moveTo(100, 100, true);
+ * actor.squish();
+ * actor.play();
+ * </code></pre>
+ * @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 {<i>YAHOO.ext.Animator</i>} animator (optional) The Animator that will capture this Actor's actions
+ * @param {<i>Boolean</i>} 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 {<i>Function</i>} 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 {<i>Array</i>} args (optional) The arguments to call the function with
+ * @param {<i>Object</i>} 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 {<i>Array</i>} args The arguments to call the function with
+ * @param {<i>Object</i>} 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 {<i>Number</i>} newSize (optional) The size to animate to. (Default to current size)
+* @param {<i>Float</i>} duration (optional) How long the effect lasts (in seconds)
+* @param {<i>Function</i>} 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 {<i>Float</i>} duration (optional) How long the effect lasts (in seconds)
+* @param {<i>Function</i>} 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 {<i>Number</i>} newSize (optional) The size to animate to. (Default to current size)
+* @param {<i>Float</i>} duration (optional) How long the effect lasts (in seconds)
+* @param {<i>Function</i>} 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 {<i>Float</i>} duration (optional) How long the effect lasts (in seconds)
+* @param {<i>Function</i>} 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 {<i>Float</i>} 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 {<i>Float</i>} 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 {<i>Float</i>} 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 {<i>Float</i>} 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 {<i>String</i>} fromColor (optional) If the element does not currently have a background color, you will need to pass in a color to animate from
+* @param {<i>Float</i>} duration (optional) How long the effect lasts (in seconds)
+* @param {<i>String</i>} 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 {<i>Number</i>} count (optional) How many times to pulse (Defaults to 3)
+* @param {<i>Float</i>} 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 {<i>Float</i>} 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 {<i>Float</i>} duration (optional) How long the effect lasts (in seconds)
+* @param {<i>Function</i>} 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 {<i>Array</i>} to (optional) Array of x and y position to move to like [x, y] (Defaults to center screen)
+* @param {<i>Float</i>} duration (optional) How long the effect lasts (in seconds)
+* @param {<i>Function</i>} 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 {<i>Number<i>} color (optional) The color of the border.
+* @param {<i>Number</i>} count (optional) How many ripples.
+* @param {<i>Float</i>} 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.<br><br>
+* <br><br>This example can be seen in action <a href="http://www.jackslocum.com/yui/2006/08/19/a-splitbar-component-for-yahoo-ui/" target="_new">here</a>
+* by clicking on "Click here and I will point it out" at the end of the first paragraph.<br>
+ * <pre><code>
+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);
+ * </code></pre>
+ * @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 {<i>YAHOO.ext.Animator</i>} animator (optional) The Animator that will capture this Actor's actions
+ * @param {<i>Boolean</i>} 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 {<i>Boolean</i>} 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 {<i>Function</i>} 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 {<i>Array</i>} args The arguments to call the function with
+ * @param {<i>Object</i>} 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 {<i>Array</i>} args The arguments to call the function with
+ * @param {<i>Object</i>} 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 <i>this</i> 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 {<i>Array</i>} args (optional) The arguments to call the function with
+ * @param {<i>Object</i>} 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. <b>A CALLBACK IS REQUIRED</b>.
+ * @param {<i>Array</i>} args (optional) The arguments to call the function with
+ * @param {<i>Object</i>} 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. <br><br>
+ * @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.
+ * <br>Usage:<br>
+ * <pre><code>
+ * 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);
+ * </code></pre>
+ * @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 {<i>Number</i>} 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...
+ * <pre><code>
+ * // column 1 starts with Jack, column 2 filtered by myFcn, column 3 equals 'Fred'
+ * dataModel.filter({1: /^Jack.+/i}, 2: myFcn, 3: 'Fred'});
+ * </code></pre>
+ * @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 <b>don't</b> 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.
+ * <br>Example schema:
+ * <pre><code>
+ * var schema = {
+ * root: 'Results.Result',
+ * id: 'ASIN',
+ * fields: ['Author', 'Title', 'Manufacturer', 'ProductGroup']
+ * };
+ * </code></pre>
+ * @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.<br><br>
+ * @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 {<i>String/Object</i>} params (optional) The parameters to pass as either a url encoded string "param1=1&amp;param2=2" or as an object {param1: 1, param2: 2}
+ * @param {<i>Function</i>} callback (optional) Callback when load is complete - called with signature (this, true for success, false for failure)
+ * @param {<i>Number</i>} 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.
+ * <br>Example schema from Amazon search:
+ * <pre><code>
+ * var schema = {
+ * tagName: 'Item',
+ * id: 'ASIN',
+ * fields: ['Author', 'Title', 'Manufacturer', 'ProductGroup']
+ * };
+ * </code></pre>
+ * @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 {<i>Function</i>} callback (optional) callback to call when loading is complete
+ * @param {<i>Boolean</i>} keepExisting (optional) true to keep existing data
+ * @param {<i>Number</i>} 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.<br />
+ * 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.<br><br>
+ * <b>Note: This class uses "Point Mode" and is untested in "Intersect Mode".</b>
+ * @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.
+ * <br>Usage:<br>
+ <pre><code>
+ 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);
+ </code></pre>
+ * @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:
+ * <br>&nbsp;&nbsp;sortTypes.none - sorts data as it is without casting or parsing (the default)
+ * <br>&nbsp;&nbsp;sortTypes.asUCString - case insensitive string
+ * <br>&nbsp;&nbsp;sortTypes.asDate - attempts to parse data as a date
+ * <br>&nbsp;&nbsp;sortTypes.asFloat
+ * <br>&nbsp;&nbsp;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 '&#160;';
+ }
+ 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. <br><br>
+ @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.
+ * <br><br>Usage:<pre><code>
+ 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();
+ * </code></pre>
+ * <b>Common Problems:</b><br/>
+ * - Grid does not resize properly when going smaller: Setting overflow hidden on the container
+ * element will correct this<br/>
+ * - 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.<br/>
+ * - 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.<br/>
+ * @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 <b>on initial render</b>
+ * @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 <code>/INPUT|TEXTAREA|SELECT/i</code>) */
+ 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 <small>{@link YAHOO.ext.grid.DefaultSelectionModel#getSelectedRows}</small> for more details.
+ * @return {Array}
+ */
+ getSelectedRows : function(){
+ return this.selModel.getSelectedRows();
+ },
+
+ /**
+ * Convenience method for getSelectionModel().getSelectedRows()[0] -
+ * See <small>{@link YAHOO.ext.grid.DefaultSelectionModel#getSelectedRows}</small> 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 <small>{@link YAHOO.ext.grid.DefaultSelectionModel#getSelectedRowIds}</small> for more details.
+ * @return {String}
+ */
+ getSelectedRowId : function(){
+ if(this.selModel.hasSelection()){
+ return this.selModel.getSelectedRowIds()[0];
+ }
+ return null;
+ },
+
+ /**
+ * Convenience method for getSelectionModel().getSelectedRowIds() -
+ * See <small>{@link YAHOO.ext.grid.DefaultSelectionModel#getSelectedRowIds}</small> for more details.
+ * @return {Array}
+ */
+ getSelectedRowIds : function(){
+ return this.selModel.getSelectedRowIds();
+ },
+
+ /**
+ * Convenience method for getSelectionModel().clearSelections() -
+ * See <small>{@link YAHOO.ext.grid.DefaultSelectionModel#clearSelections}</small> for more details.
+ */
+ clearSelections : function(){
+ this.selModel.clearSelections();
+ },
+
+
+ /**
+ * Convenience method for getSelectionModel().selectAll() -
+ * See <small>{@link YAHOO.ext.grid.DefaultSelectionModel#selectAll}</small> for more details.
+ */
+ selectAll : function(){
+ this.selModel.selectAll();
+ },
+
+
+ /**
+ * Convenience method for getSelectionModel().getCount() -
+ * See <small>{@link YAHOO.ext.grid.DefaultSelectionModel#getCount}</small> for more details.
+ * @return {Number}
+ */
+ getSelectionCount : function(){
+ return this.selModel.getCount();
+ },
+
+ /**
+ * Convenience method for getSelectionModel().hasSelection() -
+ * See <small>{@link YAHOO.ext.grid.DefaultSelectionModel#hasSelection}</small> 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 = "&#160;";
+ 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 = '&#160;';
+ 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 = '&#160;';
+ 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 = '&#160;';
+ 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: '<table border="0" cellpadding="0" cellspacing="0" title="{2}">' +
+ '<tbody><tr><td><span>{1}</span></td>' +
+ '<td><span class="sort-desc"></span><span class="sort-asc"></span></td>' +
+ '</tr></tbody></table>'
+ }]
+ });
+ 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. <br><br>
+ @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 {<i>Boolean</i>} 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 {<i>Boolean</i>} 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 {<i>Boolean</i>} 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 {<i>Boolean</i>} 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 {<i>Boolean</i>} 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 {<i>Boolean</i>} 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.<br><br>
+For more information on using this editor, see <a href="http://www.jackslocum.com/yui/2006/09/10/adding-built-in-editing-support-to-the-yahoo-ui-extensions-grid/">this blog post</a>.
+* @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:
+<ul class="list">
+<li><i>format</i> - The date format for the editor. The format is identical to <a href="http://www.php.net/date">PHP date()</a> and text is allowed. Credit for that goes to <a style="font-weight:normal;" href="http://www.xaprb.com/blog/2006/05/14/javascript-date-formatting-benchmarks/">this fantastic date library</a>. This format is for the editor only and doesn't affect the rendering of the cell when not in edit mode. Your rendering function can use any date format it wants.</li>
+<li><i>minValue</i> - The minimum allowed date. Can be either a Javascript date object or a string date in the specified format.</li>
+<li><i>maxValue</i> - The maximum allowed date. Can be either a Javascript date object or a string date in the specified format.</li>
+<li><i>minText</i> - The tooltip to display when the date in the cell is before minValue.</li>
+<li><i>maxText</i> - The tooltip to display when the date in the cell is after maxValue.</li>
+<li><i>invalidText</i> - The text to display when the date in the field is invalid (for example: 02/31/06)</li>
+<li><i>disabledDays</i> - An array of days to disable, 0 based. For example, [0, 6] disables Sunday and Saturday.</li>
+<li><i>disabledDaysText</i> - The tooltip to display when the date in the cell (or DatePicker) falls on a disabled day.</li>
+<li><i>disabledDates</i> - An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular expression so they are very powerful. For example, ["03/08/2003", "09/16/2003"] would disable those dates, but ["03/08", "09/16"] would disable them for every year. If you are using short years, you will want to use ^ to tell the regular expression to only match the beginning like ["^03/08"]. To disable March of 2006: ["03/../2006"] or every March ["^03"]. In order to support regular expressions, if you are using a date format that has "." in it, you will have to escape the dot when restricting dates. For example: ["03\\.08\\.03"].</li>
+<li><i>disabledDatesText</i> - The tooltip to display when the date in the cell (or DatePicker) falls on a disabled date.</li>
+<li><i>allowBlank</i> - True if the cell is allowed to be empty.</li>
+<li><i>blankText</i> - The tooltip (error message) to display when the cell is empty and is not allowed to be.</li>
+<li><i>validator</i> - Any custom validation function you want called. The function must return true if the data is valid or an error message otherwise.</li>
+<li><i>validationDelay</i> - The delay in milliseconds for validation. Each time the user types something the field is validated after a specified delay, setting this value allows you to customize that delay (for example, if your custom validation routine is slow).</li>
+</ul>
+For more information on using this editor, see <a href="http://www.jackslocum.com/yui/2006/09/10/adding-built-in-editing-support-to-the-yahoo-ui-extensions-grid/">this blog post</a>.
+* @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:
+<ul class="list">
+<li><i>allowDecimals</i> - True if the cell can have decimal values.</li>
+<li><i>decimalSeparator</i> - Character(s) to allow as the decimal separator.</li>
+<li><i>decimalPrecision</i> - Set the maximum decimal precision.</li>
+<li><i>decimalPrecisionFcn</i> - Define the function to call to remove extra precision (ie. Math.floor, Math.round, Math.ceil or your own function).</li>
+<li><i>allowNegative</i> - True if the cell allows negative values.</li>
+<li><i>selectOnFocus</i> - True to select the text when the editor is activated.</li>
+<li><i>minValue</i> - The minimum value the cell will allow.</li>
+<li><i>maxValue</i> - The maximum value the cell will allow.</li>
+<li><i>minText</i> - The tooltip to display when the value in the cell is below the minimum.</li>
+<li><i>maxText</i> - The tooltip to display when the value in the cell is above the maximum.</li>
+<li><i>nanText</i> - The tooltip to display when the value in the cell is not a valid number (for example, negatives are allowed and the value in the cell is just "-" with no numbers).</li>
+<li><i>allowBlank</i> - True if the cell is allowed to be empty.</li>
+<li><i>blankText</i> - The tooltip (error message) to display when the cell is empty and is not allowed to be.</li>
+<li><i>validator</i> - Any custom validation function you want called. The function must return true if the data is valid or an error message otherwise.</li>
+<li><i>validationDelay</i> - The delay in milliseconds for validation. Each time the user types something the field is validated after a specified delay, setting this value allows you to customize that delay (for example, if your custom validation routine is slow).</li>
+</ul>
+For more information on using this editor, see <a href="http://www.jackslocum.com/yui/2006/09/10/adding-built-in-editing-support-to-the-yahoo-ui-extensions-grid/">this blog post</a>.
+* @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 <b>or</b> an easier way is like this:
+<br><br>
+Define the select field in your document, giving it the ygrid-editor class.
+<pre><code>
+&lt;select id="light" class="ygrid-editor"&gt;
+ &lt;option value="Shade"&gt;Shade&lt;/option&gt;
+ &lt;option value="Mostly Shady"&gt;Mostly Shady&lt;/option&gt;
+ &lt;option value="Sun or Shade"&gt;Sun or Shade&lt;/option&gt;
+ &lt;option value="Mostly Sunny"&gt;Mostly Sunny&lt;/option&gt;
+ &lt;option value="Sunny"&gt;Sunny&lt;/option&gt;
+&lt;/select&gt;
+</code></pre>
+Create the SelectEditor object, passing in the id of your select field.
+<pre><code>
+var editor = new YAHOO.ext.grid.SelectEditor('light');
+</code></pre>
+For more information on using this editor, see <a href="http://www.jackslocum.com/yui/2006/09/10/adding-built-in-editing-support-to-the-yahoo-ui-extensions-grid/">this blog post</a>.
+* @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:
+<ul class="list">
+<li><i>allowBlank</i> - True if the cell is allowed to be empty.</li>
+<li><i>minLength</i> - The minimum length the cell will accept.</li>
+<li><i>maxLength</i> - The maximum length the cell will allow.</li>
+<li><i>minText</i> - The tooltip to display when the length of the value in the cell is below the minimum.</li>
+<li><i>maxText</i> - The tooltip to display when the length of the value in the cell is above the maximum.</li>
+<li><i>selectOnFocus</i> - True to select the text when the editor is activated.</li>
+<li><i>blankText</i> - The tooltip (error message) to display when the cell is empty and is not allowed to be.</li>
+<li><i>regex</i> - A regular expression to match if the value is valid. If the regex.test(value) returns false, the value is considered invalid.</li>
+<li><i>regexText</i> - The tooltip (error message) to display when regex does not match.</li>
+<li><i>validator</i> - Any custom validation function you want called. The function must return true if the data is valid or an error message otherwise.</li>
+<li><i>validationDelay</i> - The delay in milliseconds for validation. Each time the user types something the field is validated after a specified delay, setting this value allows you to customize that delay (for example, if your custom validation routine is slow).</li>
+</ul>
+For more information on using this editor, see <a href="http://www.jackslocum.com/yui/2006/09/10/adding-built-in-editing-support-to-the-yahoo-ui-extensions-grid/">this blog post</a>.
+* @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: <br><br>
+ * <a href="http://www.jackslocum.com/yui/2006/10/19/cross-browser-web-20-layouts-with-yahoo-ui/">Cross Browser Layouts - Part 1</a><br>
+ * <a href="http://www.jackslocum.com/yui/2006/10/28/cross-browser-web-20-layouts-part-2-ajax-feed-viewer-20/">Cross Browser Layouts - Part 2</a><br><br>
+ * Example:
+ <pre><code>
+ 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();
+</code></pre>
+* @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 <b>add</b> 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 {<i>Boolean</i>} 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 {<i>String/Object</i>} 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 {<i>Boolean</i>} 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: '&#160;'},
+ {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 : "&#160;");
+ }
+ },
+
+ 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('&#160;');
+ 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: '&#160;'}]}, 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: '&#160;'});
+ /** 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 = ['<li class="ytree-node"><div class="ytree-node-el ', n.attributes.cls,'">',
+ '<span class="ytree-node-indent">',this.indentMarkup,'</span>',
+ '<img src="', this.emptyIcon, '" class="ytree-ec-icon">',
+ '<img src="', a.icon || this.emptyIcon, '" class="ytree-node-icon',(a.icon ? ' ytree-node-inline-icon' : ''),'" unselectable="on">',
+ '<a href="',a.href ? a.href : '#','" tabIndex="1" ',
+ a.hrefTarget ? ' target="'+a.hrefTarget+'"' : '','><span unselectable="on">',n.text,'</span></a></div>',
+ '<ul class="ytree-node-ct" style="display:none;"></ul>',
+ '</li>'];
+
+ if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
+ this.wrap = YAHOO.ext.DomHelper.insertHtml('beforeBegin',
+ n.nextSibling.ui.getEl(), buf.join(''));
+ }else{
+ this.wrap = YAHOO.ext.DomHelper.insertHtml('beforeEnd', targetNode, buf.join(''));
+ }
+ this.elNode = this.wrap.childNodes[0];
+ this.ctNode = this.wrap.childNodes[1];
+ var cs = this.elNode.childNodes;
+ this.indentNode = cs[0];
+ this.ecNode = cs[1];
+ this.iconNode = cs[2];
+ this.anchor = cs[3];
+ this.textNode = cs[3].firstChild;
+ if(a.qtip){
+ if(this.textNode.setAttributeNS){
+ this.textNode.setAttributeNS('y', 'qtip', a.qtip);
+ if(a.qtipTitle){
+ this.textNode.setAttributeNS('y', 'qtitle', a.qtipTitle);
+ }
+ }else{
+ this.textNode.setAttribute('y:qtip', a.qtip);
+ if(a.qtipTitle){
+ this.textNode.setAttribute('y:qtitle', a.qtipTitle);
+ }
+ }
+ }
+ this.initEvents();
+ //this.renderIndent(); cached above now instead call updateExpandIcon
+ this.updateExpandIcon();
+ }else{
+ if(bulkRender === true) {
+ targetNode.appendChild(this.wrap);
+ }
+ }
+ },
+
+ getAnchor : function(){
+ return this.anchor;
+ },
+
+ getTextEl : function(){
+ return this.textNode;
+ },
+
+ getIconEl : function(){
+ return this.iconNode;
+ },
+
+ updateExpandIcon : function(){
+ if(this.rendered){
+ var n = this.node;
+ var cls = n.isLast() ? "ytree-elbow-end" : "ytree-elbow";
+ var hasChild = n.hasChildNodes();
+ if(hasChild){
+ cls += n.expanded ? '-minus' : '-plus';
+ var c1 = n.expanded ? 'ytree-node-collapsed' : 'ytree-node-expanded';
+ var c2 = n.expanded ? 'ytree-node-expanded' : 'ytree-node-collapsed';
+ YAHOO.util.Dom.removeClass(this.elNode, 'ytree-node-leaf');
+ YAHOO.util.Dom.replaceClass(this.elNode, c1, c2);
+ }else{
+ YAHOO.util.Dom.replaceClass(this.elNode, 'ytree-node-expanded', 'ytree-node-leaf');
+ }
+ this.ecNode.className = 'ytree-ec-icon '+cls;
+ }
+ },
+
+ getChildIndent : function(){
+ if(!this.childIndent){
+ var buf = [];
+ var p = this.node;
+ while(p){
+ if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){
+ if(!p.isLast()) {
+ buf.unshift('<img src="'+this.emptyIcon+'" class="ytree-elbow-line">');
+ } else {
+ buf.unshift('<img src="'+this.emptyIcon+'" class="ytree-icon">');
+ }
+ }
+ 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 = '<div class="ytree-root-node"></div>';
+ 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:
+ * <pre><code>
+ 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
+ });
+ </code></pre>
+ * @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: '<div class="ydlg-bg-left"><div class="ydlg-bg-right"><div class="ydlg-bg-center">&#160;</div></div></div>'
+ });
+ this.centerBg = getEl(this.bg.dom.firstChild.firstChild.firstChild);
+
+
+ if(this.autoScroll !== false && !this.autoTabs){
+ this.body.setStyle('overflow', 'auto');
+ }
+ if(this.closable !== false){
+ this.el.addClass('ydlg-closable');
+ this.close = dh.append(el.dom, {tag: 'div', cls:'ydlg-close'}, true);
+ this.close.mon('click', this.closeClick, this, true);
+ this.close.addClassOnOver('ydlg-close-over');
+ }
+ if(this.resizable !== false){
+ this.el.addClass('ydlg-resizable');
+ this.resizer = new YAHOO.ext.Resizable(el, {
+ minWidth: this.minWidth || 80,
+ minHeight:this.minHeight || 80,
+ handles: 'all',
+ pinned: true
+ });
+ this.resizer.on('beforeresize', this.beforeResize, this, true);
+ this.resizer.on('resize', this.onResize, this, true);
+ }
+ if(this.draggable !== false){
+ el.addClass('ydlg-draggable');
+ if (!this.proxyDrag) {
+ var dd = new YAHOO.util.DD(el.dom.id, 'WindowDrag');
+ }
+ else {
+ var dd = new YAHOO.util.DDProxy(el.dom.id, 'WindowDrag', {dragElId: this.proxy.id});
+ }
+ dd.setHandleElId(this.header.id);
+ dd.endDrag = this.endMove.createDelegate(this);
+ dd.startDrag = this.startMove.createDelegate(this);
+ dd.onDrag = this.onDrag.createDelegate(this);
+ this.dd = dd;
+ }
+ if(this.modal){
+ this.mask = dh.append(document.body, {tag: 'div', cls:'ydlg-mask'}, true);
+ this.mask.enableDisplayMode('block');
+ this.mask.hide();
+ this.el.addClass('ydlg-modal');
+ }
+ if(this.shadow){
+ this.shadow = el.createProxy({tag: 'div', cls:'ydlg-shadow'});
+ this.shadow.setOpacity(.3);
+ this.shadow.setVisibilityMode(YAHOO.ext.Element.VISIBILITY);
+ this.shadow.setDisplayed('block');
+ this.shadow.hide = this.hideAction;
+ this.shadow.hide();
+ }else{
+ this.shadowOffset = 0;
+ }
+ // adding an iframe shim to FF kills the cursor on the PC, but is needed on the Mac
+ // where it (luckily) does not kill the cursor
+ if(!YAHOO.ext.util.Browser.isGecko || YAHOO.ext.util.Browser.isMac){
+ if(this.shim){
+ this.shim = this.el.createShim();
+ this.shim.hide = this.hideAction;
+ this.shim.hide();
+ }
+ }else{
+ this.shim = false;
+ }
+ if(this.autoTabs){
+ this.initTabs();
+ }
+ this.syncBodyHeight();
+ this.events = {
+ /**
+ * @event keydown
+ * Fires when a key is pressed
+ * @param {YAHOO.ext.BasicDialog} this
+ * @param {YAHOO.ext.EventObject} e
+ */
+ 'keydown' : true,
+ /**
+ * @event move
+ * Fires when this dialog is moved by the user.
+ * @param {YAHOO.ext.BasicDialog} this
+ * @param {Number} x The new page X
+ * @param {Number} y The new page Y
+ */
+ 'move' : true,
+ /**
+ * @event resize
+ * Fires when this dialog is resized by the user.
+ * @param {YAHOO.ext.BasicDialog} this
+ * @param {Number} width The new width
+ * @param {Number} height The new height
+ */
+ 'resize' : true,
+ /**
+ * @event beforehide
+ * Fires before this dialog is hidden.
+ * @param {YAHOO.ext.BasicDialog} this
+ */
+ 'beforehide' : true,
+ /**
+ * @event hide
+ * Fires when this dialog is hidden.
+ * @param {YAHOO.ext.BasicDialog} this
+ */
+ 'hide' : true,
+ /**
+ * @event beforeshow
+ * Fires before this dialog is shown.
+ * @param {YAHOO.ext.BasicDialog} this
+ */
+ 'beforeshow' : true,
+ /**
+ * @event show
+ * Fires when this dialog is shown.
+ * @param {YAHOO.ext.BasicDialog} this
+ */
+ 'show' : true
+ };
+ el.mon('keydown', this.onKeyDown, this, true);
+ el.mon("mousedown", this.toFront, this, true);
+
+ YAHOO.ext.EventManager.onWindowResize(this.adjustViewport, this, true);
+ this.el.hide();
+ YAHOO.ext.DialogManager.register(this);
+};
+
+YAHOO.extendX(YAHOO.ext.BasicDialog, YAHOO.ext.util.Observable, {
+ shadowOffset: 3,
+ minHeight: 80,
+ minWidth: 200,
+ minButtonWidth: 75,
+ defaultButton: null,
+ buttonAlign: 'right',
+ /**
+ * Sets the dialog title.
+ * @param {String} text
+ * @return {YAHOO.ext.BasicDialog} this
+ */
+ setTitle : function(text){
+ this.header.update(text);
+ return this;
+ },
+
+ closeClick : function(){
+ this.hide();
+ },
+
+ /**
+ * Reinitializes the tabs component, clearing out old tabs and finding new ones.
+ * @return {YAHOO.ext.TabPanel} tabs The tabs component
+ */
+ initTabs : function(){
+ var tabs = this.getTabs();
+ while(tabs.getTab(0)){
+ tabs.removeTab(0);
+ }
+ var tabEls = YAHOO.util.Dom.getElementsByClassName('ydlg-tab', this.tabTag || 'div', this.el.dom);
+ if(tabEls.length > 0){
+ for(var i = 0, len = tabEls.length; i < len; i++) {
+ var tabEl = tabEls[i];
+ tabs.addTab(YAHOO.util.Dom.generateId(tabEl), tabEl.title);
+ tabEl.title = '';
+ }
+ tabs.activate(0);
+ }
+ return tabs;
+ },
+
+ beforeResize : function(){
+ this.resizer.minHeight = Math.max(this.minHeight, this.getHeaderFooterHeight(true)+40);
+ },
+
+ onResize : function(){
+ this.refreshSize();
+ this.syncBodyHeight();
+ this.adjustAssets();
+ this.fireEvent('resize', this, this.size.width, this.size.height);
+ },
+
+ onKeyDown : function(e){
+ if(this.isVisible()){
+ this.fireEvent('keydown', this, e);
+ }
+ },
+
+ /**
+ * Resizes the dialog.
+ * @param {Number} width
+ * @param {Number} height
+ * @return {YAHOO.ext.BasicDialog} this
+ */
+ resizeTo : function(width, height){
+ this.el.setSize(width, height);
+ this.size = {width: width, height: height};
+ this.syncBodyHeight();
+ if(this.fixedcenter){
+ this.center();
+ }
+ if(this.isVisible()){
+ this.constrainXY();
+ this.adjustAssets();
+ }
+ this.fireEvent('resize', this, width, height);
+ return this;
+ },
+
+
+ /**
+ * Resizes the dialog to fit the specified content size.
+ * @param {Number} width
+ * @param {Number} height
+ * @return {YAHOO.ext.BasicDialog} this
+ */
+ setContentSize : function(w, h){
+ h += this.getHeaderFooterHeight() + this.body.getMargins('tb');
+ w += this.body.getMargins('lr') + this.bwrap.getMargins('lr') + this.centerBg.getPadding('lr');
+ //if(!this.el.isBorderBox()){
+ h += this.body.getPadding('tb') + this.bwrap.getBorderWidth('tb') + this.body.getBorderWidth('tb') + this.el.getBorderWidth('tb');
+ w += this.body.getPadding('lr') + this.bwrap.getBorderWidth('lr') + this.body.getBorderWidth('lr') + this.bwrap.getPadding('lr') + this.el.getBorderWidth('lr');
+ //}
+ if(this.tabs){
+ h += this.tabs.stripWrap.getHeight() + this.tabs.bodyEl.getMargins('tb') + this.tabs.bodyEl.getPadding('tb');
+ w += this.tabs.bodyEl.getMargins('lr') + this.tabs.bodyEl.getPadding('lr');
+ }
+ this.resizeTo(w, h);
+ return this;
+ },
+
+ /**
+ * Adds a key listener for when this dialog is displayed
+ * @param {Number/Array/Object} key Either the numeric key code, array of key codes or an object with the following options:
+ * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
+ * @param {Function} fn The function to call
+ * @param {Object} scope (optional) The scope of the function
+ * @return {YAHOO.ext.BasicDialog} this
+ */
+ addKeyListener : function(key, fn, scope){
+ var keyCode, shift, ctrl, alt;
+ if(typeof key == 'object' && !(key instanceof Array)){
+ keyCode = key['key'];
+ shift = key['shift'];
+ ctrl = key['ctrl'];
+ alt = key['alt'];
+ }else{
+ keyCode = key;
+ }
+ var handler = function(dlg, e){
+ if((!shift || e.shiftKey) && (!ctrl || e.ctrlKey) && (!alt || e.altKey)){
+ var k = e.getKey();
+ if(keyCode instanceof Array){
+ for(var i = 0, len = keyCode.length; i < len; i++){
+ if(keyCode[i] == k){
+ fn.call(scope || window, dlg, k, e);
+ return;
+ }
+ }
+ }else{
+ if(k == keyCode){
+ fn.call(scope || window, dlg, k, e);
+ }
+ }
+ }
+ };
+ this.on('keydown', handler);
+ return this;
+ },
+
+ /**
+ * Returns the TabPanel component (if autoTabs)
+ * @return {YAHOO.ext.TabPanel}
+ */
+ getTabs : function(){
+ if(!this.tabs){
+ this.el.addClass('ydlg-auto-tabs');
+ this.body.addClass(this.tabPosition == 'bottom' ? 'ytabs-bottom' : 'ytabs-top');
+ this.tabs = new YAHOO.ext.TabPanel(this.body.dom, this.tabPosition == 'bottom');
+ }
+ return this.tabs;
+ },
+
+ /**
+ * Adds a button.
+ * @param {String/Object} config A string becomes the button text, an object is expected to be a valid YAHOO.ext.DomHelper element config
+ * @param {Function} handler The function called when the button is clicked
+ * @param {Object} scope (optional) The scope of the handler function
+ * @return {YAHOO.ext.Button}
+ */
+ addButton : function(config, handler, scope){
+ var dh = YAHOO.ext.DomHelper;
+ if(!this.footer){
+ this.footer = dh.append(this.bwrap.dom, {tag: 'div', cls:'ydlg-ft'}, true);
+ }
+ if(!this.btnContainer){
+ var tb = this.footer.createChild({
+ tag:'div',
+ cls:'ydlg-btns ydlg-btns-'+this.buttonAlign,
+ html:'<table cellspacing="0"><tbody><tr></tr></tbody></table>'
+ });
+ 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.<br>
+ * Example Usage (including a nested layout):
+ * <pre><code> 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();</code></pre>
+ * @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 <strike>and resets display to none</strike>. Use standard beginUpdate/endUpdate on the layout.
+ * @deprecated
+ */
+ endUpdate : function(){
+ this.layout.endUpdate();
+ },
+ /**
+ * Begins an update of the layout <strike>and sets display to block and visibility to hidden</strike>. 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('<a href="#" class="ybtn-focus"><table border="0" cellpadding="0" cellspacing="0" class="ybtn-wrap"><tbody><tr><td class="ybtn-left">&#160;</td><td class="ybtn-center" unselectable="on">{0}</td><td class="ybtn-right">&#160;</td></tr></tbody></table></a>');
+ }
+ 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 = '<iframe id="'+this.id+'_shdw" frameborder="0" class="ypopcal-shadow" src="'+YAHOO.ext.SSL_SECURE_URL+'"></iframe>' +
+ '<div hidefocus="true" class="ypopcal" id="'+this.id+'">' +
+ '<table class="ypopcal-head" border=0 cellpadding=0 cellspacing=0><tbody><tr><td class="ypopcal-arrow"><div class="prev-month">&#160;</div></td><td class="ypopcal-month">&#160;</td><td class="ypopcal-arrow"><div class="next-month">&#160;</div></td></tr></tbody></table>' +
+ '<center><div class="ypopcal-inner">';
+ html += "<table border=0 cellspacing=0 class=\"ypopcal-table\"><thead><tr class=\"ypopcal-daynames\">";
+ var names = this.dayNames;
+ for(var i = 0; i < names.length; i++){
+ html += '<td>' + names[i].substr(0, 1) + '</td>';
+ }
+ html+= "</tr></thead><tbody><tr>";
+ for(var i = 0; i < 42; i++) {
+ if(i % 7 == 0 && i != 0){
+ html += '</tr><tr>';
+ }
+ html += "<td>&nbsp;</td>";
+ }
+ html += "</tr></tbody></table>";
+ html += '</div><button class="ypopcal-today">'+this.todayText+'</button></center></div>';
+ c.innerHTML = html;
+ this.shadow = getEl(c.childNodes[0], true);
+ this.shadow.enableDisplayMode('block');
+ this.element = getEl(c.childNodes[1], true);
+ this.element.enableDisplayMode('block');
+ document.body.appendChild(this.shadow.dom);
+ document.body.appendChild(this.element.dom);
+ document.body.removeChild(c);
+ this.element.on("selectstart", function(){return false;});
+ var tbody = this.element.dom.getElementsByTagName('tbody')[1];
+ this.cells = tbody.getElementsByTagName('td');
+ this.calHead = this.element.getChildrenByClassName('ypopcal-month', 'td')[0];
+ this.element.mon('mousedown', this.handleClick, this, true);
+};
+
+YAHOO.ext.DatePicker.prototype.buildInnerCal = function(dateVal){
+ var days = this.getDaysInMonth(dateVal.getMonth() + 1, dateVal.getFullYear());
+ var firstOfMonth = new Date(dateVal.getFullYear(), dateVal.getMonth(), 1);
+ var startingPos = firstOfMonth.getDay();
+ if(startingPos == 0) startingPos = 7;
+ var pm = this.getPrevMonth(dateVal);
+ var prevStart = this.getDaysInMonth(pm.getMonth()+1, pm.getFullYear())-startingPos;
+ var cells = this.cells;
+ days += startingPos;
+
+ // convert everything to numbers so it's fast
+ var day = 86400000;
+ var date = this.clearTime(new Date(pm.getFullYear(), pm.getMonth(), prevStart));
+ var today = this.clearTime(new Date()).getTime();
+ var sel = this.selectedDate ? this.clearTime(this.selectedDate).getTime() : today + 1; //today +1 will never match anything
+ var min = this.minDate ? this.clearTime(this.minDate).getTime() : Number.NEGATIVE_INFINITY;
+ var max = this.maxDate ? this.clearTime(this.maxDate).getTime() : Number.POSITIVE_INFINITY;
+ var ddMatch = this.disabledDatesRE;
+ var ddText = this.disabledDatesText;
+ var ddays = this.disabledDays;
+ var ddaysText = this.disabledDaysText;
+ var format = this.format;
+
+ var setCellClass = function(cal, cell, d){
+ cell.title = '';
+ var t = d.getTime();
+ if(t == today){
+ cell.className += ' today';
+ cell.title = cal.todayText;
+ }
+ if(t == sel){
+ cell.className += ' selected';
+ }
+ // disabling
+ if(t < min) {
+ cell.className = ' ypopcal-disabled';
+ cell.title = cal.minText;
+ return;
+ }
+ if(t > max) {
+ cell.className = ' ypopcal-disabled';
+ cell.title = cal.maxText;
+ return;
+ }
+ if(ddays){
+ var day = d.getDay();
+ for(var i = 0; i < ddays.length; i++) {
+ if(day === ddays[i]){
+ cell.title = ddaysText;
+ cell.className = ' ypopcal-disabled';
+ return;
+ }
+ }
+ }
+ if(ddMatch && format){
+ var fvalue = d.format(format);
+ if(ddMatch.test(fvalue)){
+ cell.title = ddText.replace('%0', fvalue);
+ cell.className = ' ypopcal-disabled';
+ return;
+ }
+ }
+ };
+
+ var i = 0;
+ for(; i < startingPos; i++) {
+ cells[i].innerHTML = (++prevStart);
+ date.setDate(date.getDate()+1);
+ cells[i].className = 'prevday';
+ setCellClass(this, cells[i], date);
+ }
+ for(; i < days; i++){
+ intDay = i - startingPos + 1;
+ cells[i].innerHTML = (intDay);
+ date.setDate(date.getDate()+1);
+ cells[i].className = 'active';
+ setCellClass(this, cells[i], date);
+ }
+ var extraDays = 0;
+ for(; i < 42; i++) {
+ cells[i].innerHTML = (++extraDays);
+ date.setDate(date.getDate()+1);
+ cells[i].className = 'nextday';
+ setCellClass(this, cells[i], date);
+ }
+};
+
+YAHOO.ext.DatePicker.prototype.todayText = "Today";
+YAHOO.ext.DatePicker.prototype.minDate = null;
+YAHOO.ext.DatePicker.prototype.maxDate = null;
+YAHOO.ext.DatePicker.prototype.minText = "This date is before the minimum date";
+YAHOO.ext.DatePicker.prototype.maxText = "This date is after the maximum date";
+YAHOO.ext.DatePicker.prototype.format = 'm/d/y';
+YAHOO.ext.DatePicker.prototype.disabledDays = null;
+YAHOO.ext.DatePicker.prototype.disabledDaysText = '';
+YAHOO.ext.DatePicker.prototype.disabledDatesRE = null;
+YAHOO.ext.DatePicker.prototype.disabledDatesText = '';
+YAHOO.ext.DatePicker.prototype.constrainToViewport = true;
+
+
+YAHOO.ext.DatePicker.prototype.monthNames = Date.monthNames;
+
+YAHOO.ext.DatePicker.prototype.dayNames = Date.dayNames;
diff --git a/frontend/beta/js/YUI-extensions/widgets/InlineEditor.js b/frontend/beta/js/YUI-extensions/widgets/InlineEditor.js
new file mode 100644
index 0000000..a498faa
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/widgets/InlineEditor.js
@@ -0,0 +1,216 @@
+YAHOO.ext.InlineEditor = function(config, existingEl){
+ YAHOO.ext.util.Config.apply(this, config);
+ var dh = YAHOO.ext.DomHelper;
+ this.wrap = dh.append(this.container || document.body, {
+ tag:'div',
+ cls:'yinline-editor-wrap'
+ }, true);
+
+ this.textSizeEl = dh.append(document.body, {
+ tag: 'div',
+ cls: 'yinline-editor-sizer ' + (this.cls || '')
+ });
+ if(YAHOO.ext.util.Browser.isSafari){ // extra padding for safari's textboxes
+ this.textSizeEl.style.padding = '4px';
+ YAHOO.util.Dom.setStyle(this.textSizeEl, 'padding-right', '10px');
+ }
+
+ if(!YAHOO.ext.util.Browser.isGecko){ // no one else needs FireFox cursor fix
+ this.wrap.setStyle('overflow', 'hidden');
+ }
+
+ if(existingEl){
+ this.el = getEl(existingEl);
+ }
+ if(!this.el){
+ this.id = this.id || YAHOO.util.Dom.generateId();
+ if(!this.multiline){
+ this.el = this.wrap.createChild({
+ tag: 'input',
+ name: this.name || this.id,
+ id: this.id,
+ type: this.type || 'text',
+ autocomplete: 'off',
+ value: this.value || '',
+ cls: 'yinline-editor ' + (this.cls || ''),
+ maxlength: this.maxLength || ''
+ });
+ }else{
+ this.el = this.wrap.createChild({
+ tag: 'textarea',
+ name: this.name || this.id,
+ id: this.id,
+ html: this.value || '',
+ cls: 'yinline-editor yinline-editor-multiline ' + (this.cls || ''),
+ wrap: 'none'
+ });
+ }
+ }else{
+ this.wrap.dom.appendChild(this.el.dom);
+ }
+ this.el.addKeyMap([{
+ key: [10, 13],
+ fn: this.onEnter,
+ scope: this
+ },{
+ key: 27,
+ fn: this.onEsc,
+ scope: this
+ }]);
+ this.el.mon('keyup', this.onKeyUp, this, true);
+ this.el.on('blur', this.onBlur, this, true);
+ this.el.swallowEvent('keydown');
+ this.events = {
+ 'startedit' : true,
+ 'beforecomplete' : true,
+ 'complete' : true
+ };
+ this.editing = false;
+ this.autoSizeTask = new YAHOO.ext.util.DelayedTask(this.autoSize, this);
+};
+
+YAHOO.extendX(YAHOO.ext.InlineEditor, YAHOO.ext.util.Observable, {
+ onEnter : function(k, e){
+ if(this.multiline && (e.ctrlKey || e.shiftKey)){
+ return;
+ }else{
+ this.completeEdit();
+ e.stopEvent();
+ }
+ },
+
+ onEsc : function(){
+ if(this.ignoreNoChange){
+ this.revert(true);
+ }else{
+ this.revert(false);
+ this.completeEdit();
+ }
+ },
+
+ onBlur : function(){
+ if(this.editing && this.completeOnBlur !== false){
+ this.completeEdit();
+ }
+ },
+
+ startEdit : function(el, value){
+ this.boundEl = YAHOO.util.Dom.get(el);
+ if(this.hideEl !== false){
+ this.boundEl.style.visibility = 'hidden';
+ }
+ var v = value || this.boundEl.innerHTML;
+ this.startValue = v;
+ this.setValue(v);
+ this.moveTo(YAHOO.util.Dom.getXY(this.boundEl));
+ this.editing = true;
+ if(YAHOO.ext.QuickTips){
+ YAHOO.ext.QuickTips.disable();
+ }
+ this.show.defer(10, this);
+ },
+
+ onKeyUp : function(e){
+ var k = e.getKey();
+ if(this.editing && (k < 33 || k > 40) && k != 27){
+ this.autoSizeTask.delay(50);
+ }
+ },
+
+ completeEdit : function(){
+ var v = this.getValue();
+ if(this.revertBlank !== false && v.length < 1){
+ v = this.startValue;
+ this.revert();
+ }
+ if(v == this.startValue && this.ignoreNoChange){
+ this.hide();
+ }
+ if(this.fireEvent('beforecomplete', this, v, this.startValue) !== false){
+ if(this.updateEl !== false && this.boundEl){
+ this.boundEl.innerHTML = v;
+ }
+ this.hide();
+ this.fireEvent('complete', this, v, this.startValue);
+ }
+ },
+
+ revert : function(hide){
+ this.setValue(this.startValue);
+ if(hide){
+ this.hide();
+ }
+ },
+
+ show : function(){
+ this.autoSize();
+ this.wrap.show();
+ this.el.focus();
+ if(this.selectOnEdit !== false){
+ this.el.dom.select();
+ }
+ },
+
+ hide : function(){
+ this.editing = false;
+ this.wrap.hide();
+ this.wrap.setLeftTop(-10000,-10000);
+ this.el.blur();
+ if(this.hideEl !== false){
+ this.boundEl.style.visibility = 'visible';
+ }
+ if(YAHOO.ext.QuickTips){
+ YAHOO.ext.QuickTips.enable();
+ }
+ },
+
+ setValue : function(v){
+ this.el.dom.value = v;
+ },
+
+ getValue : function(){
+ return this.el.dom.value;
+ },
+
+ autoSize : function(){
+ var el = this.el;
+ var wrap = this.wrap;
+ var v = el.dom.value;
+ var ts = this.textSizeEl;
+ if(v.length < 1){
+ ts.innerHTML = "&#160;&#160;";
+ }else{
+ v = v.replace(/[<> ]/g, '&#160;');
+ if(this.multiline){
+ v = v.replace(/\n/g, '<br />&#160;');
+ }
+ 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:'<span class="ext-mb-text"></span><br /><input type="text" class="ext-mb-input"><textarea class="ext-mb-textarea"></textarea><div class="ext-mb-progress-wrap"><div class="ext-mb-progress"><div class="ext-mb-progress-bar">&#160;</div></div></div>'
+ });
+ msgEl = bodyEl.dom.firstChild;
+ textboxEl = getEl(bodyEl.dom.childNodes[2]);
+ textboxEl.enableDisplayMode();
+ textboxEl.addKeyListener([10,13], function(){
+ if(dlg.isVisible() && opt && opt.buttons){
+ if(opt.buttons.ok){
+ handleButton('ok');
+ }else if(opt.buttons.yes){
+ handleButton('yes');
+ }
+ }
+ });
+ textareaEl = getEl(bodyEl.dom.childNodes[3]);
+ textareaEl.enableDisplayMode();
+ progressEl = getEl(bodyEl.dom.childNodes[4]);
+ progressEl.enableDisplayMode();
+ pp = getEl(progressEl.dom.firstChild.firstChild);
+ }
+ return dlg;
+ },
+
+ updateText : function(text){
+ if(!dlg.isVisible() && !opt.width){
+ dlg.resizeTo(this.maxWidth, 100); // resize first so content is never clipped from previous shows
+ }
+ msgEl.innerHTML = text;
+ var w = Math.max(Math.min(opt.width || msgEl.offsetWidth, this.maxWidth),
+ Math.max(opt.minWidth || this.minWidth, bwidth));
+ if(opt.prompt){
+ activeTextEl.setWidth(w);
+ }
+ dlg.setContentSize(w, bodyEl.getHeight());
+ },
+
+ updateProgress : function(value, text){
+ if(text){
+ this.updateText(text);
+ }
+ pp.setWidth(value*progressEl.dom.firstChild.offsetWidth);
+ },
+
+ isVisible : function(){
+ return dlg && dlg.isVisible();
+ },
+
+ hide : function(){
+ if(this.isVisible()){
+ dlg.hide();
+ }
+ },
+
+ show : function(options){
+ var d = this.getDialog();
+ opt = options;
+ d.setTitle(opt.title || '&#160;');
+ d.close.setDisplayed(opt.closable !== false);
+ activeTextEl = textboxEl;
+ opt.prompt = opt.prompt || (opt.multiline ? true : false)
+ if(opt.prompt){
+ if(opt.multiline){
+ textboxEl.hide();
+ textareaEl.show();
+ textareaEl.setHeight(typeof opt.multiline == 'number' ?
+ opt.multiline : this.defaultTextHeight);
+ activeTextEl = textareaEl;
+ }else{
+ textboxEl.show();
+ textareaEl.hide();
+ }
+ }else{
+ textboxEl.hide();
+ textareaEl.hide();
+ }
+ progressEl.setDisplayed(opt.progress === true);
+ this.updateProgress(0);
+ activeTextEl.dom.value = opt.value || '';
+ if(opt.prompt){
+ dlg.setDefaultButton(activeTextEl);
+ }else{
+ var bs = opt.buttons;
+ var db = null;
+ if(bs && bs.ok){
+ db = buttons['ok'];
+ }else if(bs && bs.yes){
+ db = buttons['yes'];
+ }
+ dlg.setDefaultButton(db);
+ }
+ bwidth = updateButtons(opt.buttons);
+ this.updateText(opt.msg);
+ d.modal = opt.modal !== false;
+ d.mask = opt.modal !== false ? mask : false;
+ d.animateTarget = null;
+ d.show(options.animEl);
+ },
+
+ progress : function(title, msg){
+ this.show({
+ title : title,
+ msg : msg,
+ buttons: false,
+ progress:true,
+ closable:false
+ });
+ },
+
+ alert : function(title, msg, fn, scope){
+ this.show({
+ title : title,
+ msg : msg,
+ buttons: this.OK,
+ fn: fn,
+ scope : scope
+ });
+ },
+
+ confirm : function(title, msg, fn, scope){
+ this.show({
+ title : title,
+ msg : msg,
+ buttons: this.YESNO,
+ fn: fn,
+ scope : scope
+ });
+ },
+
+ prompt : function(title, msg, fn, scope, multiline){
+ this.show({
+ title : title,
+ msg : msg,
+ buttons: this.OKCANCEL,
+ fn: fn,
+ minWidth:250,
+ scope : scope,
+ prompt:true,
+ multiline: multiline
+ });
+ },
+
+ OK : {ok:true},
+ YESNO : {yes:true, no:true},
+ OKCANCEL : {ok:true, cancel:true},
+ YESNOCANCEL : {yes:true, no:true, cancel:true},
+
+ defaultTextHeight:75,
+ maxWidth : 500,
+ minWidth : 100,
+ buttonText : {
+ ok : 'OK',
+ cancel : 'Cancel',
+ yes : 'Yes',
+ no : 'No'
+ }
+ };
+}();
+
+YAHOO.ext.Msg = YAHOO.ext.MessageBox;
diff --git a/frontend/beta/js/YUI-extensions/widgets/QuickTips.js b/frontend/beta/js/YUI-extensions/widgets/QuickTips.js
new file mode 100644
index 0000000..6658574
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/widgets/QuickTips.js
@@ -0,0 +1,311 @@
+/**
+ * @class YAHOO.ext.QuickTips
+ * @singleton
+ */
+YAHOO.ext.QuickTips = function(){
+ var el, tipBody, tipTitle, tm, cfg, close, tagEls = {}, reader, esc, anim, removeCls = null;
+ var ce, bd, xy;
+ var visible = false, disabled = true, inited = false;
+ var showProc = hideProc = dismissProc = 1, locks = [];
+ var E = YAHOO.util.Event, dd;
+
+ var onOver = function(e){
+ if(disabled){
+ return;
+ }
+ var t = E.getTarget(e);
+ if(!t){
+ return;
+ }
+ if(ce && t == ce.el){
+ clearTimeout(hideProc);
+ return;
+ }
+ if(t && tagEls[t.id]){
+ tagEls[t.id].el = t;
+ showProc = show.defer(tm.showDelay, tm, [tagEls[t.id]]);
+ return;
+ }
+ var ttp = reader.getAttribute(t, cfg.attribute);
+ if(!ttp && tm.interceptTitles && t.title){
+ ttp = t.title;
+ t.title = '';
+ if(reader.useNS){
+ t.setAttributeNS('y', 'qtip', ttp);
+ }else{
+ t.setAttribute('qtip', ttp);
+ }
+ }
+ if(ttp){
+ xy = E.getXY(e);
+ xy[0] += 12; xy[1] += 20;
+ showProc = show.defer(tm.showDelay, tm, [{
+ el: t,
+ text: ttp,
+ width: reader.getAttribute(t, cfg.width),
+ autoHide: reader.getAttribute(t, cfg.hide) != 'user',
+ title: reader.getAttribute(t, cfg.title),
+ cls: reader.getAttribute(t, cfg.cls)
+ }]);
+ }
+ };
+
+ var onOut = function(e){
+ clearTimeout(showProc);
+ var t = E.getTarget(e);
+ if(t && ce && ce.el == t && (tm.autoHide && ce.autoHide !== false)){
+ hideProc = setTimeout(hide, tm.hideDelay);
+ }
+ };
+
+ var onMove = function(e){
+ if(disabled){
+ return;
+ }
+ xy = E.getXY(e);
+ xy[0] += 12; xy[1] += 20;
+ if(tm.trackMouse && ce){
+ el.setXY(xy);
+ }
+ };
+
+ var onDown = function(e){
+ clearTimeout(showProc);
+ clearTimeout(hideProc);
+ if(!e.within(el)){
+ if(tm.hideOnClick && ce && ce.autoHide !== false){
+ hide();
+ tm.disable();
+ }
+ }
+ };
+
+ var onUp = function(e){
+ tm.enable();
+ }
+
+ var show = function(o){
+ if(disabled){
+ return;
+ }
+ clearTimeout(dismissProc);
+ stopAnim();
+ ce = o;
+ if(removeCls){ // in case manually hidden
+ el.removeClass(removeCls);
+ removeCls = null;
+ }
+ if(ce.cls){
+ el.addClass(ce.cls);
+ removeCls = ce.cls;
+ }
+ if(ce.title){
+ tipTitleText.update(ce.title);
+ tipTitle.show();
+ }else{
+ tipTitle.hide();
+ }
+ tipBody.update(o.text);
+ if(!ce.width){
+ if(tipBody.dom.style.width){
+ tipBody.dom.style.width = '';
+ }
+ if(tipBody.dom.offsetWidth > tm.maxWidth){
+ tipBody.setWidth(tm.maxWidth);
+ }
+ }else{
+ tipBody.setWidth(ce.width);
+ }
+ if(!ce.autoHide){
+ close.setDisplayed(true);
+ if(dd){
+ dd.unlock();
+ }
+ }else{
+ close.setDisplayed(false);
+ if(dd){
+ dd.lock();
+ }
+ }
+ if(xy){
+ el.setXY(xy);
+ }
+ if(tm.animate){
+ anim.attributes = {opacity:{to:1}};
+ el.setOpacity(.1);
+ el.setStyle('visibility', 'visible');
+ anim.animateX(afterShow);
+ }else{
+ afterShow();
+ }
+ };
+
+ var afterShow = function(){
+ if(ce){
+ el.show();
+ esc.enable();
+ if(tm.autoDismiss && ce.autoHide !== false){
+ dismissProc = setTimeout(hide, tm.autoDismissDelay);
+ }
+ }
+ }
+
+ var hide = function(noanim){
+ clearTimeout(dismissProc);
+ clearTimeout(hideProc);
+ ce = null;
+ if(el.isVisible()){
+ esc.disable();
+ stopAnim();
+ if(noanim !== true && tm.animate){
+ anim.attributes = {opacity:{to:.1}};
+ el.beforeAction();
+ anim.animateX(afterHide);
+ }else{
+ afterHide();
+ }
+ }
+ };
+
+ var afterHide = function(){
+ el.hide();
+ if(removeCls){
+ el.removeClass(removeCls);
+ removeCls = null;
+ }
+ }
+
+ var stopAnim = function(){
+ if(anim && anim.isAnimated()){
+ anim.stop();
+ }
+ }
+
+ return {
+ init : function(){
+ if(YAHOO.ext.util.Browser.isIE && !YAHOO.ext.util.Browser.isIE7){
+ return;
+ }
+ tm = YAHOO.ext.QuickTips;
+ cfg = tm.tagConfig;
+ reader = new YAHOO.ext.CustomTagReader(cfg.namespace);
+ if(!inited){
+ el = new YAHOO.ext.Layer({cls:'ytip', shadow:true, useDisplay: false});
+ el.update('<div class="ytip-hd-left"><div class="ytip-hd-right"><div class="ytip-hd"></div></div></div>');
+ tipTitle = getEl(el.dom.firstChild);
+ tipTitleText = getEl(el.dom.firstChild.firstChild.firstChild);
+ tipTitle.enableDisplayMode('block');
+ tipBody = el.createChild({tag:'div', cls:'ytip-bd'});
+ close = el.createChild({tag:'div', cls:'ytip-close'});
+ close.on('click', hide);
+ d = getEl(document);
+ d.mon('mousedown', onDown);
+ d.on('mouseup', onUp);
+ d.on('mouseover', onOver);
+ d.on('mouseout', onOut);
+ d.on('mousemove', onMove);
+ esc = d.addKeyListener(27, hide);
+ esc.disable();
+ if(tm.animate){
+ anim = new YAHOO.util.Anim(el.dom, {}, .1);
+ }
+ if(YAHOO.util.DD){
+ dd = el.initDD('default', null, {
+ onDrag : function(){
+ el.sync();
+ }
+ });
+ dd.setHandleElId(tipTitleText.id);
+ dd.lock();
+ }
+ inited = true;
+ }
+ this.scan(document.body);
+ this.enable();
+ },
+
+ tips : function(config){
+ var cs = config instanceof Array ? config : arguments;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ var c = cs[i];
+ var target = c.target;
+ if(target){
+ if(target instanceof Array){
+ for(var j = 0, jlen = target.length; j < jlen; j++){
+ tagEls[target[j]] = c;
+ }
+ }else{
+ tagEls[target] = c;
+ }
+ }
+ }
+ },
+
+ enable : function(){
+ if(inited){
+ locks.pop();
+ if(locks.length < 1){
+ disabled = false;
+ }
+ }
+ },
+
+ disable : function(){
+ disabled = true;
+ clearTimeout(showProc);
+ clearTimeout(hideProc);
+ clearTimeout(dismissProc);
+ if(ce){
+ hide(true);
+ }
+ locks.push(1);
+ },
+
+ scan : function(toScan){
+ toScan = toScan.dom ? toScan.dom : YAHOO.util.Dom.get(toScan);
+ var found = [];
+ reader.eachElement(cfg.tag, toScan, function(el){
+ var t = reader.getAttribute(el, cfg.target);
+ if(t){
+ found.push({
+ target: t.indexOf(',') != -1 ? t.split(',') : t,
+ text: el.innerHTML,
+ autoHide: reader.getAttribute(el, cfg.hide) != 'user',
+ width: reader.getAttribute(el, cfg.width),
+ title: reader.getAttribute(el, cfg.title),
+ cls: reader.getAttribute(el, cfg.cls)
+ });
+ }
+ el.parentNode.removeChild(el);
+ });
+ this.tips(found);
+ },
+
+ tagConfig : {
+ namespace : 'y',
+ tag : 'qtip',
+ attribute : 'qtip',
+ width : 'width',
+ target : 'target',
+ title : 'qtitle',
+ hide : 'hide',
+ cls : 'qclass'
+ },
+
+ maxWidth : 300,
+ interceptTitles : true,
+ trackMouse : false,
+ hideOnClick : true,
+ showDelay : 500,
+ hideDelay : 200,
+ autoHide : true,
+ autoDismiss : true,
+ autoDismissDelay : 5000,
+ /**
+ * True to turn on fade animation. Defaults to true
+ * except in IE7 (ClearType/scrollbar flicker issues in IE7 with filters).
+ * @type Boolean
+ */
+ animate : YAHOO.util.Anim && !YAHOO.ext.util.Browser.isIE7
+ }
+}();
diff --git a/frontend/beta/js/YUI-extensions/widgets/Resizable.js b/frontend/beta/js/YUI-extensions/widgets/Resizable.js
new file mode 100644
index 0000000..6944683
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/widgets/Resizable.js
@@ -0,0 +1,586 @@
+/**
+ * @class YAHOO.ext.Resizable
+ * @extends YAHOO.ext.util.Observable
+ * <p>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), <b>or</b> set wrap:true in your config and
+ * the element will be wrapped for you automatically.</p><br/>
+ * Here's a Resizable with every possible config option and it's default value:
+<pre><code>
+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);
+</code></pre>
+* <p>
+ * To hide a particular handle, set it's display to none in CSS, or through script:<br>
+ * resizer.east.setDisplayed(false);
+ * </p>
+ * @constructor
+ * Create a new resizable component
+ * @param {String/HTMLElement/YAHOO.ext.Element} el The id or element to resize
+ * @param {Object} config configuration options
+ */
+YAHOO.ext.Resizable = function(el, config){
+ this.el = getEl(el);
+
+ if(config && config.wrap){
+ config.resizeChild = this.el;
+ this.el = this.el.wrap(typeof config.wrap == 'object' ? config.wrap : null);
+ this.el.id = this.el.dom.id = config.resizeChild.id + '-rzwrap';
+ this.el.setStyle('overflow', 'hidden');
+ this.el.setPositioning(config.resizeChild.getPositioning());
+ config.resizeChild.clearPositioning();
+ if(!config.width || !config.height){
+ var csize = config.resizeChild.getSize();
+ //csize.width -= config.adjustments[0];
+ //csize.height -= config.adjustments[1];
+ this.el.setSize(csize.width, csize.height);
+ }
+ if(config.pinned && !config.adjustments){
+ config.adjustments = 'auto';
+ }
+ }
+
+ this.proxy = this.el.createProxy({tag: 'div', cls: 'yresizable-proxy', id: this.el.id + '-rzproxy'})
+ this.proxy.unselectable();
+
+ // the overlay traps mouse events while dragging and fixes iframe issue
+ this.overlay = this.el.createProxy({tag: 'div', cls: 'yresizable-overlay', html: '&#160;'});
+ this.overlay.unselectable();
+ this.overlay.enableDisplayMode('block');
+ this.overlay.mon('mousemove', this.onMouseMove, this, true);
+ this.overlay.mon('mouseup', this.onMouseUp, this, true);
+
+ YAHOO.ext.util.Config.apply(this, config, {
+ /** True to resizeSize the first child or id/element to resize @type YAHOO.ext.Element */
+ resizeChild : false,
+ /** String "auto" or an array [width, height] with values to be <b>added</b> to the resize operation's new size. @type Array/String */
+ adjustments : [0, 0],
+ /** The minimum width for the element @type Number */
+ minWidth : 5,
+ /** The minimum height for the element @type Number */
+ minHeight : 5,
+ /** The maximum width for the element @type Number */
+ maxWidth : 10000,
+ /** The maximum height for the element @type Number */
+ maxHeight : 10000,
+ /** false to disable resizing @type Boolean */
+ enabled : true,
+ /** True to animate the resize (not compatible with dynamic sizing) @type Boolean */
+ animate : false,
+ /** Animation duration @type Float */
+ duration : .35,
+ /** True to resize the element while dragging instead of using a proxy @type Boolean */
+ dynamic : false,
+ // these 3 are only available at config time
+ /** String consisting of the resize handles to display. Valid handles are
+ * n (north), s (south) e (east), w (west), ne (northeast), nw (northwest), se (southeast), sw (southwest)
+ * and all (which applies them all). If this is blank it defaults to "e,s,se". Handles can be delimited using
+ * a space, comma or semi-colon. This is only applied at config time. @type String*/
+ handles : false,
+ multiDirectional : false,
+ /** true to disable mouse tracking. This is only applied at config time. @type Boolean*/
+ disableTrackOver : false,
+ /** Animation easing @type YAHOO.util.Easing */
+ easing : YAHOO.util.Easing ? YAHOO.util.Easing.easeOutStrong : null,
+ /** The increment to snap the width resize in pixels (dynamic must be true) @type Number */
+ widthIncrement : 0,
+ /** The increment to snap the height resize in pixels (dynamic must be true) @type Number */
+ heightIncrement : 0,
+ /** true to pin the resize handles. This is only applied at config time. @type Boolean*/
+ pinned : false,
+ /** The initial width for the element @type Number */
+ width : null,
+ /** The initial height for the element @type Number */
+ height : null,
+ /** true to preserve the initial size ratio. @type Boolean*/
+ preserveRatio : false,
+ /** true for transparent handles. This is only applied at config time. @type Boolean*/
+ transparent: false,
+ /** The minimum allowed page X for the element (only used for west resizing, defaults to 0) @type Number */
+ minX: 0,
+ /** The minimum allowed page Y for the element (only used for north resizing, defaults to 0) @type Number */
+ minY: 0,
+ /** convenience to initialize drag drop. @type Boolean*/
+ draggable: false
+ });
+
+ if(this.pinned){
+ this.disableTrackOver = true;
+ this.el.addClass('yresizable-pinned');
+ }
+ // if the element isn't positioned, make it relative
+ var position = this.el.getStyle('position');
+ if(position != 'absolute' && position != 'fixed'){
+ this.el.setStyle('position', 'relative');
+ }
+ if(!this.handles){ // no handles passed, must be legacy style
+ this.handles = 's,e,se';
+ if(this.multiDirectional){
+ this.handles += ',n,w';
+ }
+ }
+ if(this.handles == 'all'){
+ this.handles = 'n s e w ne nw se sw';
+ }
+ var hs = this.handles.split(/\s*?[,;]\s*?| /);
+ var ps = YAHOO.ext.Resizable.positions;
+ for(var i = 0, len = hs.length; i < len; i++){
+ if(hs[i] && ps[hs[i]]){
+ var pos = ps[hs[i]];
+ this[pos] = new YAHOO.ext.Resizable.Handle(this, pos, this.disableTrackOver, this.transparent);
+ }
+ }
+ // legacy
+ this.corner = this.southeast;
+
+ this.activeHandle = null;
+
+ if(this.resizeChild){
+ if(typeof this.resizeChild == 'boolean'){
+ this.resizeChild = YAHOO.ext.Element.get(this.el.dom.firstChild, true);
+ }else{
+ this.resizeChild = YAHOO.ext.Element.get(this.resizeChild, true);
+ }
+ }
+
+ if(this.adjustments == 'auto'){
+ var rc = this.resizeChild;
+ var hw = this.west, he = this.east, hn = this.north, hs = this.south;
+ if(rc && (hw || hn)){
+ rc.setRelativePositioned();
+ rc.setLeft(hw ? hw.el.getWidth() : 0);
+ rc.setTop(hn ? hn.el.getHeight() : 0);
+ }
+ this.adjustments = [
+ (he ? -he.el.getWidth() : 0) + (hw ? -hw.el.getWidth() : 0),
+ (hn ? -hn.el.getHeight() : 0) + (hs ? -hs.el.getHeight() : 0) -1
+ ];
+ }
+
+ if(this.draggable){
+ this.dd = this.dynamic ?
+ this.el.initDD(null) : this.el.initDDProxy(null, {dragElId: this.proxy.id});
+ this.dd.setHandleElId(this.resizeChild ? this.resizeChild.id : this.el.id);
+ }
+
+ // public events
+ this.events = {
+ /**
+ * @event beforeresize
+ * Fired before resize is allowed. Set enabled to false to cancel resize.
+ * @param {YAHOO.ext.Resizable} this
+ * @param {YAHOO.ext.EventObject} e The mousedown event
+ */
+ 'beforeresize' : new YAHOO.util.CustomEvent(),
+ /**
+ * @event resize
+ * Fired after a resize.
+ * @param {YAHOO.ext.Resizable} this
+ * @param {Number} width The new width
+ * @param {Number} height The new height
+ * @param {YAHOO.ext.EventObject} e The mouseup event
+ */
+ 'resize' : new YAHOO.util.CustomEvent()
+ };
+
+ if(this.width !== null && this.height !== null){
+ this.resizeTo(this.width, this.height);
+ }else{
+ this.updateChildSize();
+ }
+};
+
+YAHOO.extendX(YAHOO.ext.Resizable, YAHOO.ext.util.Observable, {
+ /**
+ * Perform a manual resize
+ * @param {Number} width
+ * @param {Number} height
+ */
+ resizeTo : function(width, height){
+ this.el.setSize(width, height);
+ this.updateChildSize();
+ this.fireEvent('resize', this, width, height, null);
+ },
+
+ startSizing : function(e){
+ this.fireEvent('beforeresize', this, e);
+ if(this.enabled){ // 2nd enabled check in case disabled before beforeresize handler
+ this.resizing = true;
+ this.startBox = this.el.getBox();
+ this.startPoint = e.getXY();
+ this.offsets = [(this.startBox.x + this.startBox.width) - this.startPoint[0],
+ (this.startBox.y + this.startBox.height) - this.startPoint[1]];
+ this.proxy.setBox(this.startBox);
+
+ this.overlay.setSize(YAHOO.util.Dom.getDocumentWidth(), YAHOO.util.Dom.getDocumentHeight());
+ this.overlay.show();
+
+ if(!this.dynamic){
+ this.proxy.show();
+ }
+ }
+ },
+
+ onMouseDown : function(handle, e){
+ if(this.enabled){
+ e.stopEvent();
+ this.activeHandle = handle;
+ this.overlay.setStyle('cursor', handle.el.getStyle('cursor'));
+ this.startSizing(e);
+ }
+ },
+
+ onMouseUp : function(e){
+ var size = this.resizeElement();
+ this.resizing = false;
+ this.handleOut();
+ this.overlay.hide();
+ this.fireEvent('resize', this, size.width, size.height, e);
+ },
+
+ updateChildSize : function(){
+ if(this.resizeChild){
+ var el = this.el;
+ var child = this.resizeChild;
+ var adj = this.adjustments;
+ if(el.dom.offsetWidth){
+ var b = el.getSize(true);
+ child.setSize(b.width+adj[0], b.height+adj[1]);
+ }
+ // Second call here for IE
+ // The first call enables instant resizing and
+ // the second call corrects scroll bars if they
+ // exist
+ if(YAHOO.ext.util.Browser.isIE){
+ setTimeout(function(){
+ if(el.dom.offsetWidth){
+ var b = el.getSize(true);
+ child.setSize(b.width+adj[0], b.height+adj[1]);
+ }
+ }, 10);
+ }
+ }
+ },
+
+ snap : function(value, inc, min){
+ if(!inc || !value) return value;
+ var newValue = value;
+ var m = value % inc;
+ if(m > 0){
+ if(m > (inc/2)){
+ newValue = value + (inc-m);
+ }else{
+ newValue = value - m;
+ }
+ }
+ return Math.max(min, newValue);
+ },
+
+ resizeElement : function(){
+ var box = this.proxy.getBox();
+ //box.width = this.snap(box.width, this.widthIncrement);
+ //box.height = this.snap(box.height, this.heightIncrement);
+ //if(this.multiDirectional){
+ this.el.setBox(box, false, this.animate, this.duration, null, this.easing);
+ //}else{
+ // this.el.setSize(box.width, box.height, this.animate, this.duration, null, this.easing);
+ //}
+ this.updateChildSize();
+ this.proxy.hide();
+ return box;
+ },
+
+ constrain : function(v, diff, m, mx){
+ if(v - diff < m){
+ diff = v - m;
+ }else if(v - diff > mx){
+ diff = mx - v;
+ }
+ return diff;
+ },
+
+ onMouseMove : function(e){
+ if(this.enabled){
+ try{// try catch so if something goes wrong the user doesn't get hung
+
+ //var curXY = this.startPoint;
+ var curSize = this.curSize || this.startBox;
+ var x = this.startBox.x, y = this.startBox.y;
+ var ox = x, oy = y;
+ var w = curSize.width, h = curSize.height;
+ var ow = w, oh = h;
+ var mw = this.minWidth, mh = this.minHeight;
+ var mxw = this.maxWidth, mxh = this.maxHeight;
+ var wi = this.widthIncrement;
+ var hi = this.heightIncrement;
+
+ var eventXY = e.getXY();
+ var diffX = -(this.startPoint[0] - Math.max(this.minX, eventXY[0]));
+ var diffY = -(this.startPoint[1] - Math.max(this.minY, eventXY[1]));
+
+ var pos = this.activeHandle.position;
+
+ switch(pos){
+ case 'east':
+ w += diffX;
+ w = Math.min(Math.max(mw, w), mxw);
+ break;
+ case 'south':
+ h += diffY;
+ h = Math.min(Math.max(mh, h), mxh);
+ break;
+ case 'southeast':
+ w += diffX;
+ h += diffY;
+ w = Math.min(Math.max(mw, w), mxw);
+ h = Math.min(Math.max(mh, h), mxh);
+ break;
+ case 'north':
+ diffY = this.constrain(h, diffY, mh, mxh);
+ y += diffY;
+ h -= diffY;
+ break;
+ case 'west':
+ diffX = this.constrain(w, diffX, mw, mxw);
+ x += diffX;
+ w -= diffX;
+ break;
+ case 'northeast':
+ w += diffX;
+ w = Math.min(Math.max(mw, w), mxw);
+ diffY = this.constrain(h, diffY, mh, mxh);
+ y += diffY;
+ h -= diffY;
+ break;
+ case 'northwest':
+ diffX = this.constrain(w, diffX, mw, mxw);
+ diffY = this.constrain(h, diffY, mh, mxh);
+ y += diffY;
+ h -= diffY;
+ x += diffX;
+ w -= diffX;
+ break;
+ case 'southwest':
+ diffX = this.constrain(w, diffX, mw, mxw);
+ h += diffY;
+ h = Math.min(Math.max(mh, h), mxh);
+ x += diffX;
+ w -= diffX;
+ break;
+ }
+
+ var sw = this.snap(w, wi, mw);
+ var sh = this.snap(h, hi, mh);
+ if(sw != w || sh != h){
+ switch(pos){
+ case 'northeast':
+ y -= sh - h;
+ break;
+ case 'north':
+ y -= sh - h;
+ break;
+ case 'southwest':
+ x -= sw - w;
+ break;
+ case 'west':
+ x -= sw - w;
+ break;
+ case 'northwest':
+ x -= sw - w;
+ y -= sh - h;
+ break;
+ }
+ w = sw;
+ h = sh;
+ }
+
+ if(this.preserveRatio){
+ switch(pos){
+ case 'southeast':
+ case 'east':
+ h = oh * (w/ow);
+ h = Math.min(Math.max(mh, h), mxh);
+ w = ow * (h/oh);
+ break;
+ case 'south':
+ w = ow * (h/oh);
+ w = Math.min(Math.max(mw, w), mxw);
+ h = oh * (w/ow);
+ break;
+ case 'northeast':
+ w = ow * (h/oh);
+ w = Math.min(Math.max(mw, w), mxw);
+ h = oh * (w/ow);
+ break;
+ case 'north':
+ var tw = w;
+ w = ow * (h/oh);
+ w = Math.min(Math.max(mw, w), mxw);
+ h = oh * (w/ow);
+ x += (tw - w) / 2;
+ break;
+ case 'southwest':
+ h = oh * (w/ow);
+ h = Math.min(Math.max(mh, h), mxh);
+ var tw = w;
+ w = ow * (h/oh);
+ x += tw - w;
+ break;
+ case 'west':
+ var th = h;
+ h = oh * (w/ow);
+ h = Math.min(Math.max(mh, h), mxh);
+ y += (th - h) / 2;
+ var tw = w;
+ w = ow * (h/oh);
+ x += tw - w;
+ break;
+ case 'northwest':
+ var tw = w;
+ var th = h;
+ h = oh * (w/ow);
+ h = Math.min(Math.max(mh, h), mxh);
+ w = ow * (h/oh);
+ y += th - h;
+ x += tw - w;
+ break;
+
+ }
+ }
+ this.proxy.setBounds(x, y, w, h);
+ if(this.dynamic){
+ this.resizeElement();
+ }
+ }catch(e){}
+ }
+ },
+
+ handleOver : function(){
+ if(this.enabled){
+ this.el.addClass('yresizable-over');
+ }
+ },
+
+ handleOut : function(){
+ if(!this.resizing){
+ this.el.removeClass('yresizable-over');
+ }
+ },
+
+ /**
+ * Returns the element this component is bound to.
+ * @return {YAHOO.ext.Element}
+ */
+ getEl : function(){
+ return this.el;
+ },
+
+ /**
+ * Returns the resizeChild element (or null).
+ * @return {YAHOO.ext.Element}
+ */
+ getResizeChild : function(){
+ return this.resizeChild;
+ },
+
+ /**
+ * Destroys this resizable. If the element was wrapped and
+ * removeEl is not true then the wrap remains.
+ * @param {Boolean} removeEl (optional) true to remove the element from the DOM
+ */
+ destroy : function(removeEl){
+ this.proxy.remove();
+ this.overlay.removeAllListeners();
+ this.overlay.remove();
+ var ps = YAHOO.ext.Resizable.positions;
+ for(var k in ps){
+ if(typeof ps[k] != 'function' && this[ps[k]]){
+ var h = this[ps[k]];
+ h.el.removeAllListeners();
+ h.el.remove();
+ }
+ }
+ if(removeEl){
+ this.el.update('');
+ this.el.remove();
+ }
+ }
+});
+
+// hash to map config positions to true positions
+YAHOO.ext.Resizable.positions = {
+ n: 'north', s: 'south', e: 'east', w: 'west', se: 'southeast', sw: 'southwest', nw: 'northwest', ne: 'northeast'
+};
+
+
+YAHOO.ext.Resizable.Handle = function(rz, pos, disableTrackOver, transparent){
+ if(!this.tpl){
+ // only initialize the template if resizable is used
+ var tpl = YAHOO.ext.DomHelper.createTemplate(
+ {tag: 'div', cls: 'yresizable-handle yresizable-handle-{0}', html: '&#160;'}
+ );
+ tpl.compile();
+ YAHOO.ext.Resizable.Handle.prototype.tpl = tpl;
+ }
+ this.position = pos;
+ this.rz = rz;
+ this.el = this.tpl.append(rz.el.dom, [this.position], true);
+ this.el.unselectable();
+ if(transparent){
+ this.el.setOpacity(0);
+ }
+ this.el.mon('mousedown', this.onMouseDown, this, true);
+ if(!disableTrackOver){
+ this.el.mon('mouseover', this.onMouseOver, this, true);
+ this.el.mon('mouseout', this.onMouseOut, this, true);
+ }
+};
+
+YAHOO.ext.Resizable.Handle.prototype = {
+ afterResize : function(rz){
+ // do nothing
+ },
+
+ onMouseDown : function(e){
+ this.rz.onMouseDown(this, e);
+ },
+
+ onMouseOver : function(e){
+ this.rz.handleOver(this, e);
+ },
+
+ onMouseOut : function(e){
+ this.rz.handleOut(this, e);
+ }
+};
+
+
+
diff --git a/frontend/beta/js/YUI-extensions/widgets/SplitBar.js b/frontend/beta/js/YUI-extensions/widgets/SplitBar.js
new file mode 100644
index 0000000..855d138
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/widgets/SplitBar.js
@@ -0,0 +1,468 @@
+/*
+ * splitbar.js, version .7
+ * Copyright(c) 2006, Jack Slocum.
+ * Code licensed under the BSD License
+ */
+if(YAHOO.util.DragDropMgr){
+ YAHOO.util.DragDropMgr.clickTimeThresh = 350;
+}
+/**
+ * @class YAHOO.ext.SplitBar
+ * @extends YAHOO.ext.util.Observable
+ * Creates draggable splitter bar functionality from two elements.
+ * <br><br>
+ * Usage:
+ * <pre><code>
+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);
+</code></pre>
+ * @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.
+ * <br><br>
+ * Usage:
+ * <pre><code>
+ <font color="#008000">// basic tabs 1, built from existing content</font>
+ var tabs = new YAHOO.ext.TabPanel('tabs1');
+ tabs.addTab('script', "View Script");
+ tabs.addTab('markup', "View Markup");
+ tabs.activate('script');
+
+ <font color="#008000">// more advanced tabs, built from javascript</font>
+ var jtabs = new YAHOO.ext.TabPanel('jtabs');
+ jtabs.addTab('jtabs-1', "Normal Tab", "My content was added during construction.");
+
+ <font color="#008000">// set up the UpdateManager</font>
+ var tab2 = jtabs.addTab('jtabs-2', "Ajax Tab 1");
+ var updater = tab2.getUpdateManager();
+ updater.setDefaultUrl('ajax1.htm');
+ tab2.onActivate.subscribe(updater.refresh, updater, true);
+
+ <font color="#008000">// Use setUrl for Ajax loading</font>
+ var tab3 = jtabs.addTab('jtabs-3', "Ajax Tab 2");
+ tab3.setUrl('ajax2.htm', null, true);
+
+ <font color="#008000">// Disabled tab</font>
+ var tab4 = jtabs.addTab('tabs1-5', "Disabled Tab", "Can't see me cause I'm disabled");
+ tab4.disable();
+
+ jtabs.activate('jtabs-1');
+}
+ * </code></pre>
+ * @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 <b>or create</b>
+ * @param {String} text The text for the tab
+ * @param {<i>String</i>} content (optional) Content to put in the TabPanelItem body
+ * @param {<i>Boolean</i>} 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. <b>It cannot be the active tab, if it is this call is ignored.</b>.
+ * @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 <b>does not</b> 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 <b>does</b> 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 {<i>String/Object</i>} 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 {<i>Boolean</i>} 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 = '<div class="ytab-strip-wrap"><table class="ytab-strip" cellspacing="0" cellpadding="0" border="0"><tbody><tr></tr></tbody></table></div>';
+ return strip.firstChild.firstChild.firstChild.firstChild;
+};
+/** @private */
+YAHOO.ext.TabPanel.prototype.createBody = function(container){
+ var body = document.createElement('div');
+ YAHOO.util.Dom.generateId(body, 'tab-body');
+ YAHOO.util.Dom.addClass(body, 'yui-ext-tabbody');
+ container.appendChild(body);
+ return body;
+};
+/** @private */
+YAHOO.ext.TabPanel.prototype.createItemBody = function(bodyEl, id){
+ var body = YAHOO.util.Dom.get(id);
+ if(!body){
+ body = document.createElement('div');
+ body.id = id;
+ }
+ YAHOO.util.Dom.addClass(body, 'yui-ext-tabitembody');
+ bodyEl.insertBefore(body, bodyEl.firstChild);
+ return body;
+};
+/** @private */
+YAHOO.ext.TabPanel.prototype.createStripElements = function(stripEl, text, closable){
+ var td = document.createElement('td');
+ stripEl.appendChild(td);
+ if(closable){
+ td.className = "ytab-closable";
+ if(!this.closeTpl){
+ this.closeTpl = new YAHOO.ext.Template(
+ '<a href="#" class="ytab-right"><span class="ytab-left"><em class="ytab-inner">' +
+ '<span unselectable="on" title="{text}" class="ytab-text">{text}</span>' +
+ '<div unselectable="on" class="close-icon">&#160;</div></em></span></a>'
+ );
+ }
+ var el = this.closeTpl.overwrite(td, {'text': text});
+ var close = el.getElementsByTagName('div')[0];
+ var inner = el.getElementsByTagName('em')[0];
+ return {'el': el, 'close': close, 'inner': inner};
+ } else {
+ if(!this.tabTpl){
+ this.tabTpl = new YAHOO.ext.Template(
+ '<a href="#" class="ytab-right"><span class="ytab-left"><em class="ytab-inner">' +
+ '<span unselectable="on" title="{text}" class="ytab-text">{text}</span></em></span></a>'
+ );
+ }
+ var el = this.tabTpl.overwrite(td, {'text': text});
+ var inner = el.getElementsByTagName('em')[0];
+ return {'el': el, 'inner': inner};
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/widgets/TaskPanel.js b/frontend/beta/js/YUI-extensions/widgets/TaskPanel.js
new file mode 100644
index 0000000..e69de29
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/widgets/TaskPanel.js
diff --git a/frontend/beta/js/YUI-extensions/widgets/TemplateView.js b/frontend/beta/js/YUI-extensions/widgets/TemplateView.js
new file mode 100644
index 0000000..2100205
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/widgets/TemplateView.js
@@ -0,0 +1,766 @@
+/**
+ * @class YAHOO.ext.View
+ * @extends YAHOO.ext.util.Observable
+ * Create a "View" for an element based on a data model or UpdateManager and the supplied DomHelper template.
+ * This class also supports single and multi selection modes. <br>
+ * Create a data model bound view:
+<pre><code>
+var dataModel = new YAHOO.ext.grid.XMLDataModel(...);
+var view = new YAHOO.ext.View('my-element',
+ '&lt;div id="{0}"&gt;{2} - {1}&lt;/div&gt;', // 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');
+</code></pre>
+For an example of creating a JSON/UpdateManager view, see {@link YAHOO.ext.JsonView}.
+ * <br><br>
+ * <b>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.</b>
+ * @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:
+<pre><code>
+var view = new YAHOO.ext.JsonView('my-element',
+ '&lt;div id="{id}"&gt;{foo} - {bar}&lt;/div&gt;', // 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(
+ '&lt;div class="entry"&gt;' +
+ '&lt;a class="entry-title" href="{link}"&gt;{title}&lt;/a&gt;' +
+ '&lt;h4&gt;{date} by {author} | {comments} Comments&lt;/h4&gt;{description}' +
+ '&lt;/div&gt;&lt;hr /&gt;'
+);
+
+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...'
+});
+</code></pre>
+ * @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:
+<pre><code>
+view.load({
+ url: 'your-url.php',<br/>
+ params: {param1: 'foo', param2: 'bar'}, // or a URL encoded string<br/>
+ callback: yourFunction,<br/>
+ scope: yourObject, //(optional scope) <br/>
+ discardUrl: false, <br/>
+ nocache: false,<br/>
+ text: 'Loading...',<br/>
+ timeout: 30,<br/>
+ scripts: false<br/>
+});
+</code></pre>
+ * 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 {<i>String/Object</i>} params (optional) The parameters to pass as either a url encoded string "param1=1&amp;param2=2" or an object {param1: 1, param2: 2}
+ * @param {<i>Function</i>} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess)
+ * @param {<i>Boolean</i>} 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:
+ * <ul>
+ * <li>className - The CSS class for the button. Use this to attach a background image for an icon.</li>
+ * <li>text - The button's text</li>
+ * <li>tooltip - The buttons tooltip text</li>
+ * <li>click - function to call when the button is clicked</li>
+ * <li>mouseover - function to call when the mouse moves over the button</li>
+ * <li>mouseout - function to call when the mouse moves off the button</li>
+ * <li>scope - The scope of the above event handlers</li>
+ * <li></li>
+ * <li></li>
+ * @constructor
+ * @param {Object} config
+ */
+YAHOO.ext.ToolbarButton = function(config){
+ YAHOO.ext.util.Config.apply(this, config);
+};
+
+YAHOO.ext.ToolbarButton.prototype = {
+ /** @private */
+ init : function(appendTo){
+ var element = document.createElement('span');
+ element.className = 'ytb-button';
+ if(this.id){
+ element.id = this.id;
+ }
+ this.setDisabled(this.disabled === true);
+ var inner = document.createElement('span');
+ inner.className = 'ytb-button-inner ' + (this.className || this.cls);
+ inner.unselectable = 'on';
+ if(this.tooltip){
+ element.setAttribute('title', this.tooltip);
+ }
+ if(this.style){
+ YAHOO.ext.DomHelper.applyStyles(inner, this.style);
+ }
+ element.appendChild(inner);
+ appendTo.appendChild(element);
+ this.el = getEl(element, true);
+ this.el.unselectable();
+ inner.innerHTML = (this.text ? this.text : '&#160;');
+ this.inner = inner;
+ this.el.mon('click', this.onClick, this, true);
+ this.el.mon('mouseover', this.onMouseOver, this, true);
+ this.el.mon('mouseout', this.onMouseOut, this, true);
+ },
+
+ /**
+ * Sets this buttons click handler
+ * @param {Function} click The function to call when the button is clicked
+ * @param {Object} scope (optional) Scope for the function passed above
+ */
+ setHandler : function(click, scope){
+ this.click = click;
+ this.scope = scope;
+ },
+
+ /**
+ * Set this buttons text
+ * @param {String} text
+ */
+ setText : function(text){
+ this.inner.innerHTML = text;
+ },
+
+ /**
+ * Set this buttons tooltip text
+ * @param {String} text
+ */
+ setTooltip : function(text){
+ this.el.dom.title = text;
+ },
+
+ /**
+ * Show this button
+ */
+ show: function(){
+ this.el.dom.parentNode.style.display = '';
+ },
+
+ /**
+ * Hide this button
+ */
+ hide: function(){
+ this.el.dom.parentNode.style.display = 'none';
+ },
+
+ /**
+ * Disable this button
+ */
+ disable : function(){
+ this.disabled = true;
+ if(this.el){
+ this.el.addClass('ytb-button-disabled');
+ }
+ },
+
+ /**
+ * Enable this button
+ */
+ enable : function(){
+ this.disabled = false;
+ if(this.el){
+ this.el.removeClass('ytb-button-disabled');
+ }
+ },
+
+ /**
+ * Returns true if this button is disabled.
+ * @return {Boolean}
+ */
+ isDisabled : function(){
+ return this.disabled === true;
+ },
+
+ setDisabled : function(disabled){
+ if(disabled){
+ this.disable();
+ }else{
+ this.enable();
+ }
+ },
+
+ /** @private */
+ onClick : function(){
+ if(!this.disabled && this.click){
+ this.click.call(this.scope || window, this);
+ }
+ },
+
+ /** @private */
+ onMouseOver : function(){
+ if(!this.disabled){
+ this.el.addClass('ytb-button-over');
+ if(this.mouseover){
+ this.mouseover.call(this.scope || window, this);
+ }
+ }
+ },
+
+ /** @private */
+ onMouseOut : function(){
+ this.el.removeClass('ytb-button-over');
+ if(!this.disabled){
+ if(this.mouseout){
+ this.mouseout.call(this.scope || window, this);
+ }
+ }
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/yutil.js b/frontend/beta/js/YUI-extensions/yutil.js
new file mode 100644
index 0000000..a815397
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/yutil.js
@@ -0,0 +1,637 @@
+YAHOO.namespace('ext', 'ext.util', 'ext.grid', 'ext.dd', 'ext.tree', 'ext.data', 'ext.form');
+if(typeof Ext == 'undefined'){
+ Ext = YAHOO.ext;
+}
+YAHOO.ext.Strict = (document.compatMode == 'CSS1Compat');
+YAHOO.ext.SSL_SECURE_URL = 'javascript:false';
+YAHOO.ext.BLANK_IMAGE_URL = 'http:/'+'/www.yui-ext.com/blog/images/s.gif';
+
+// for old browsers
+window.undefined = undefined;
+/**
+ * @class Function
+ * These functions are available on every Function object (any javascript function).
+ */
+ //
+ /**
+ * Creates a callback that passes arguments[0], arguments[1], arguments[2], ...
+ * Call directly on any function. Example: <code>myFunction.createCallback(myarg, myarg2)</code>
+ * 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: <code>this.myFunction.createDelegate(this)</code>
+ * Will create a function that is automatically scoped to this.
+ * @param {Object} obj (optional) The object for which the scope is set
+ * @param {<i>Array</i>} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
+ * @param {<i>Boolean/Number</i>} 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 {<i>Array</i>} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
+ * @param {<i>Boolean/Number</i>} 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 {<i>Object</i>} 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 {<i>Object</i>} 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.<br>
+ * Example:<br><br><pre><code>
+ * if(beforeUpdateEvent.fireDirect(myArg, myArg2) !== false){
+ * // do update
+ * }</code></pre>
+ */
+YAHOO.util.CustomEvent.prototype.fireDirect = function(){
+ var len=this.subscribers.length;
+ for (var i=0; i<len; ++i) {
+ var s = this.subscribers[i];
+ if(s){
+ var scope = (s.override) ? s.obj : this.scope;
+ if(s.fn.apply(scope, arguments) === false){
+ return false;
+ }
+ }
+ }
+ return true;
+};
+
+/**
+ * @class YAHOO-
+ * Additional functionality for the global YAHOO object.
+ * @singleton
+ */
+/**
+ * Prints all arguments to a resizable, movable, scrolling region without
+ * the need to include separate js or css. Double click it to hide it.
+ * @param {Mixed} arg1
+ * @param {Mixed} arg2
+ * @param {Mixed} etc
+ * @static
+ */
+YAHOO.print = function(arg1, arg2, etc){
+ if(!YAHOO.ext._console){
+ var cs = YAHOO.ext.DomHelper.insertBefore(document.body.firstChild,
+ {tag: 'div',style:'width:250px;height:350px;overflow:auto;border:3px solid #c3daf9;' +
+ 'background:white;position:absolute;right:5px;top:5px;' +
+ 'font:normal 8pt arial,verdana,helvetica;z-index:50000;padding:5px;'}, true);
+ if(YAHOO.ext.Resizable){
+ new YAHOO.ext.Resizable(cs, {
+ transparent:true,
+ handles: 'all',
+ pinned:true,
+ adjustments: [0,0],
+ wrap:true,
+ draggable:(YAHOO.util.DD ? true : false)
+ });
+ }
+ cs.on('dblclick', cs.hide);
+ YAHOO.ext._console = cs;
+ }
+ var m = '';
+ for(var i = 0, len = arguments.length; i < len; i++) {
+ m += (i == 0 ? '' : ', ') + arguments[i];
+ }
+ m += '<hr noshade style="color:#eeeeee;" size="1">'
+ var d = YAHOO.ext._console.dom;
+ d.innerHTML = m + d.innerHTML;
+ d.scrollTop = 0;
+ YAHOO.ext._console.show();
+};
+
+/**
+ * Applies the passed C#/DomHelper style format (e.g. "The variable {0} is equal to {1}") before calling {@link YAHOO#print}
+ * @param {String} format
+ * @param {Mixed} arg1
+ * @param {Mixed} arg2
+ * @param {Mixed} etc
+ * @static
+ */
+YAHOO.printf = function(format, arg1, arg2, etc){
+ var args = Array.prototype.slice.call(arguments, 1);
+ YAHOO.print(format.replace(
+ /\{\{[^{}]*\}\}|\{(\d+)(,\s*([\w.]+))?\}/g,
+ function(m, a1, a2, a3) {
+ if (m.chatAt == '{') {
+ return m.slice(1, -1);
+ }
+ var rpl = args[a1];
+ if (a3) {
+ var f = eval(a3);
+ rpl = f(rpl);
+ }
+ return rpl ? rpl : '';
+ }));
+}
+
+YAHOO._timers = {};
+/**
+ * If a timer with specified name doesn't exist it is started. If one exists and reset is not true
+ * it prints the ellapsed time since the start using YAHOO.printf.
+ * @param {String} name
+ * @param {Boolean} reset true to reset an existing timer
+ */
+YAHOO.timer = function(name, reset){
+ var t = new Date().getTime();
+ if(YAHOO._timers[name] && !reset){
+ YAHOO.printf("{0} : {1} ms", name, t-YAHOO._timers[name]);
+ }else{
+ YAHOO._timers[name] = t;
+ }
+}
+/**
+ * Extends one class with another class and optionally overrides members with the passed literal. This class
+ * also adds the function "override()" to the class that can be used to override
+ * members on an instance.
+ * @param {Object} subclass The class inheriting the functionality
+ * @param {Object} superclass The class being extended
+ * @param {Object} overrides (optional) A literal with members
+ * @static
+ */
+YAHOO.extendX = function(subclass, superclass, overrides){
+ YAHOO.extend(subclass, superclass);
+ subclass.override = function(o){
+ YAHOO.override(subclass, o);
+ };
+ if(!subclass.prototype.override){
+ subclass.prototype.override = function(o){
+ for(var method in o){
+ this[method] = o[method];
+ }
+ };
+ }
+ if(overrides){
+ subclass.override(overrides);
+ }
+};
+
+/**
+ * Creates namespaces but does not assume YAHOO is the root.
+ * @param {String} namespace1
+ * @param {String} namespace2
+ * @param {String} etc
+ * @static
+ */
+YAHOO.namespaceX = function(){
+ var a = arguments, len = a.length, i;
+ YAHOO.namespace.apply(YAHOO, a);
+ for(i = 0; i < len; i++){
+ var p = a[i].split('.')[0];
+ if(p != 'YAHOO' && YAHOO[p]){
+ eval(p + ' = YAHOO.' + p);
+ delete YAHOO[p];
+ }
+ }
+};
+
+YAHOO.override = function(origclass, overrides){
+ if(overrides){
+ var p = origclass.prototype;
+ for(var method in overrides){
+ p[method] = overrides[method];
+ }
+ }
+};
+
+/**
+ * @class YAHOO.ext.util.DelayedTask
+ * Provides a convenient method of performing setTimeout where a new
+ * timeout cancels the old timeout. An example would be performing validation on a keypress.
+ * You can use this class to buffer
+ * the keypress events for a certain number of milliseconds, and perform only if they stop
+ * for that amount of time.
+ * @constructor The parameters to this constructor serve as defaults and are not required.
+ * @param {<i>Function</i>} fn (optional) The default function to timeout
+ * @param {<i>Object</i>} scope (optional) The default scope of that timeout
+ * @param {<i>Array</i>} args (optional) The default Array of arguments
+ */
+YAHOO.ext.util.DelayedTask = function(fn, scope, args){
+ var timeoutId = null;
+
+ /**
+ * Cancels any pending timeout and queues a new one
+ * @param {Number} delay The milliseconds to delay
+ * @param {Function} newFn (optional) Overrides function passed to constructor
+ * @param {Object} newScope (optional) Overrides scope passed to constructor
+ * @param {Array} newArgs (optional) Overrides args passed to constructor
+ */
+ this.delay = function(delay, newFn, newScope, newArgs){
+ if(timeoutId){
+ clearTimeout(timeoutId);
+ }
+ fn = newFn || fn;
+ scope = newScope || scope;
+ args = newArgs || args;
+ timeoutId = setTimeout(fn.createDelegate(scope, args), delay);
+ };
+
+ /**
+ * Cancel the last queued timeout
+ */
+ this.cancel = function(){
+ if(timeoutId){
+ clearTimeout(timeoutId);
+ timeoutId = null;
+ }
+ };
+};
+
+/**
+ * @class YAHOO.ext.util.Observable
+ * Abstract base class that provides a common interface for publishing events. Subclasses are expected to
+ * to have a property "events" with all the events defined.<br>
+ * For example:
+ * <pre><code>
+ 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);
+</code></pre>
+ */
+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 {<i>Object</i>} scope (optional) The scope (this object) for the handler
+ * @param {<i>boolean</i>} 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 {<i>Object</i>} scope (optional) The scope (this object) for the handler
+ * @param {<i>Number</i>} 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 {<i>Object</i>} scope (optional) The scope (this object) for the handler
+ * @param {<i>Number</i>} 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 {<i>Object</i>} 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 {<i>Object</i>} scope (optional) The scope (this object) for the handler
+ * @param {<i>boolean</i>} 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
+ * <b>before</b> 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 <b>all</b> 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.<br /><br />
+ *
+ * Usage:
+ <pre><code>
+ var dd = new YAHOO.util.DDProxy("dragDiv1", "proxytest",
+ { dragElId: "existingProxyDiv" });
+ dd.startDrag = function(){
+ this.constrainTo('parent-id');
+ };
+ </code></pre>
+ * Or you can initalize it using the {@link YAHOO.ext.Element} object:
+ <pre><code>
+ getEl('dragDiv1').initDDProxy('proxytest', {dragElId: "existingProxyDiv"}, {
+ startDrag : function(){
+ this.constrainTo('parent-id');
+ }
+ });
+ </code></pre>
+ */
+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
+ );
+ }
+}