From ef68436ac04da078ffdcacd7e1f785473a303d45 Mon Sep 17 00:00:00 2001 From: Giulio Cesare Solaroli Date: Sun, 02 Oct 2011 23:56:18 +0000 Subject: First version of the newly restructured repository --- (limited to 'frontend/beta/js/YUI-extensions/widgets') 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: + *

+  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
+  });
+  
+ * @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: '
 
' + }); + 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:'
' + }); + this.btnContainer = tb.dom.firstChild.firstChild.firstChild; + } + var bconfig = { + handler: handler, + scope: scope, + minWidth: this.minButtonWidth + }; + if(typeof config == 'string'){ + bconfig.text = config; + }else{ + bconfig.dhconfig = config; + } + var btn = new YAHOO.ext.Button( + this.btnContainer.appendChild(document.createElement('td')), + bconfig + ); + this.syncBodyHeight(); + if(!this.buttons){ + this.buttons = []; + } + this.buttons.push(btn); + return btn; + }, + + /** + * Sets the default button to be focused when the dialog is displayed + * @param {YAHOO.ext.BasicDialog.Button} btn The button object returned by addButton + * @return {YAHOO.ext.BasicDialog} this + */ + setDefaultButton : function(btn){ + this.defaultButton = btn; + return this; + }, + + getHeaderFooterHeight : function(safe){ + var height = 0; + if(this.header){ + height += this.header.getHeight(); + } + if(this.footer){ + var fm = this.footer.getMargins(); + height += (this.footer.getHeight()+fm.top+fm.bottom); + } + height += this.bwrap.getPadding('tb')+this.bwrap.getBorderWidth('tb'); + height += this.centerBg.getPadding('tb'); + return height; + }, + + syncBodyHeight : function(){ + var height = this.size.height - this.getHeaderFooterHeight(false); + this.body.setHeight(height-this.body.getMargins('tb')); + if(this.tabs){ + this.tabs.syncHeight(); + } + var hh = this.header.getHeight(); + var h = this.size.height-hh; + this.centerBg.setHeight(h); + this.bwrap.setLeftTop(this.centerBg.getPadding('l'), hh+this.centerBg.getPadding('t')); + this.bwrap.setHeight(h-this.centerBg.getPadding('tb')); + this.bwrap.setWidth(this.el.getWidth(true)-this.centerBg.getPadding('lr')); + this.body.setWidth(this.bwrap.getWidth(true)); + }, + + /** + * Restores the previous state of the dialog if YAHOO.ext.state is configured + * @return {YAHOO.ext.BasicDialog} this + */ + restoreState : function(){ + var box = YAHOO.ext.state.Manager.get(this.stateId || (this.el.id + '-state')); + if(box && box.width){ + this.xy = [box.x, box.y]; + this.resizeTo(box.width, box.height); + } + return this; + }, + + beforeShow : function(){ + if(this.fixedcenter) { + this.xy = this.el.getCenterXY(true); + } + if(this.modal){ + YAHOO.util.Dom.addClass(document.body, 'masked'); + this.mask.setSize(YAHOO.util.Dom.getDocumentWidth(), YAHOO.util.Dom.getDocumentHeight()); + this.mask.show(); + } + this.constrainXY(); + }, + + animShow : function(){ + var b = getEl(this.animateTarget, true).getBox(); + this.proxy.setSize(b.width, b.height); + this.proxy.setLocation(b.x, b.y); + this.proxy.show(); + this.proxy.setBounds(this.xy[0], this.xy[1], this.size.width, this.size.height, + true, .35, this.showEl.createDelegate(this)); + }, + + /** + * Shows the dialog. + * @param {String/HTMLElement/YAHOO.ext.Element} animateTarget (optional) Reset the animation target + * @return {YAHOO.ext.BasicDialog} this + */ + show : function(animateTarget){ + if (this.fireEvent('beforeshow', this) === false){ + return; + } + if(this.syncHeightBeforeShow){ + this.syncBodyHeight(); + } + this.animateTarget = animateTarget || this.animateTarget; + if(!this.el.isVisible()){ + this.beforeShow(); + if(this.animateTarget){ + this.animShow(); + }else{ + this.showEl(); + } + } + return this; + }, + + showEl : function(){ + this.proxy.hide(); + this.el.setXY(this.xy); + this.el.show(); + this.adjustAssets(true); + this.toFront(); + this.focus(); + this.fireEvent('show', this); + }, + + focus : function(){ + if(this.defaultButton){ + this.defaultButton.focus(); + }else{ + this.focusEl.focus(); + } + }, + + constrainXY : function(){ + if(this.constraintoviewport !== false){ + if(!this.viewSize){ + if(this.container){ + var s = this.container.getSize(); + this.viewSize = [s.width, s.height]; + }else{ + this.viewSize = [YAHOO.util.Dom.getViewportWidth(), + YAHOO.util.Dom.getViewportHeight()]; + } + } + var x = this.xy[0], y = this.xy[1]; + var w = this.size.width, h = this.size.height; + var vw = this.viewSize[0], vh = this.viewSize[1]; + // only move it if it needs it + var moved = false; + // first validate right/bottom + if(x + w > vw){ + x = vw - w; + moved = true; + } + if(y + h > vh){ + y = vh - h; + moved = true; + } + // then make sure top/left isn't negative + if(x < 0){ + x = 0; + moved = true; + } + if(y < 0){ + y = 0; + moved = true; + } + if(moved){ + // cache xy + this.xy = [x, y]; + if(this.isVisible()){ + this.el.setLocation(x, y); + this.adjustAssets(); + } + } + } + }, + + onDrag : function(){ + if(!this.proxyDrag){ + this.xy = this.el.getXY(); + this.adjustAssets(); + } + }, + + adjustAssets : function(doShow){ + var x = this.xy[0], y = this.xy[1]; + var w = this.size.width, h = this.size.height; + if(doShow === true){ + if(this.shadow){ + this.shadow.show(); + } + if(this.shim){ + this.shim.show(); + } + } + if(this.shadow && this.shadow.isVisible()){ + this.shadow.setBounds(x + this.shadowOffset, y + this.shadowOffset, w, h); + } + if(this.shim && this.shim.isVisible()){ + this.shim.setBounds(x, y, w, h); + } + }, + + + adjustViewport : function(w, h){ + if(!w || !h){ + w = YAHOO.util.Dom.getViewportWidth(); + h = YAHOO.util.Dom.getViewportHeight(); + } + // cache the size + this.viewSize = [w, h]; + if(this.modal && this.mask.isVisible()){ + this.mask.setSize(w, h); // first make sure the mask isn't causing overflow + this.mask.setSize(YAHOO.util.Dom.getDocumentWidth(), YAHOO.util.Dom.getDocumentHeight()); + } + if(this.isVisible()){ + this.constrainXY(); + } + }, + + /** + * Destroys this dialog + * @param {Boolean} removeEl (optional) true to remove the element from the DOM + */ + destroy : function(removeEl){ + YAHOO.ext.EventManager.removeResizeListener(this.adjustViewport, this); + if(this.tabs){ + this.tabs.destroy(removeEl); + } + if(this.shim){ + this.shim.remove(); + } + if(this.shadow){ + this.shadow.remove(); + } + if(this.proxy){ + this.proxy.remove(); + } + if(this.resizer){ + this.resizer.destroy(); + } + if(this.close){ + this.close.removeAllListeners(); + this.close.remove(); + } + if(this.mask){ + this.mask.remove(); + } + if(this.dd){ + this.dd.unreg(); + } + if(this.buttons){ + for(var i = 0, len = this.buttons.length; i < len; i++){ + this.buttons[i].destroy(); + } + } + this.el.removeAllListeners(); + if(removeEl === true){ + this.el.update(''); + this.el.remove(); + } + YAHOO.ext.DialogManager.unregister(this); + }, + + startMove : function(){ + if(this.proxyDrag){ + this.proxy.show(); + } + if(this.constraintoviewport !== false){ + this.dd.constrainTo(document.body, {right: this.shadowOffset, bottom: this.shadowOffset}); + } + }, + + endMove : function(){ + if(!this.proxyDrag){ + YAHOO.util.DD.prototype.endDrag.apply(this.dd, arguments); + }else{ + YAHOO.util.DDProxy.prototype.endDrag.apply(this.dd, arguments); + this.proxy.hide(); + } + this.refreshSize(); + this.adjustAssets(); + this.fireEvent('move', this, this.xy[0], this.xy[1]) + }, + + /** + * Brings this dialog to the front of any other visible dialogs + * @return {YAHOO.ext.BasicDialog} this + */ + toFront : function(){ + YAHOO.ext.DialogManager.bringToFront(this); + return this; + }, + + /** + * Sends this dialog to the back (under) of any other visible dialogs + * @return {YAHOO.ext.BasicDialog} this + */ + toBack : function(){ + YAHOO.ext.DialogManager.sendToBack(this); + return this; + }, + + /** + * Centers this dialog + * @return {YAHOO.ext.BasicDialog} this + */ + center : function(){ + var xy = this.el.getCenterXY(true); + this.moveTo(xy[0], xy[1]); + return this; + }, + + /** + * Moves the dialog to the specified point + * @param {Number} x + * @param {Number} y + * @return {YAHOO.ext.BasicDialog} this + */ + moveTo : function(x, y){ + this.xy = [x,y]; + if(this.isVisible()){ + this.el.setXY(this.xy); + this.adjustAssets(); + } + return this; + }, + + /** + * Returns true if the dialog is visible + * @return {Boolean} + */ + isVisible : function(){ + return this.el.isVisible(); + }, + + animHide : function(callback){ + var b = getEl(this.animateTarget, true).getBox(); + this.proxy.show(); + this.proxy.setBounds(this.xy[0], this.xy[1], this.size.width, this.size.height); + this.el.hide(); + this.proxy.setBounds(b.x, b.y, b.width, b.height, true, .35, + this.hideEl.createDelegate(this, [callback])); + }, + + /** + * Hides the dialog. + * @param {Function} callback (optional) Function to call when the dialog is hidden + * @return {YAHOO.ext.BasicDialog} this + */ + hide : function(callback){ + if (this.fireEvent('beforehide', this) === false) + return; + + if(this.shadow){ + this.shadow.hide(); + } + if(this.shim) { + this.shim.hide(); + } + if(this.animateTarget){ + this.animHide(callback); + }else{ + this.el.hide(); + this.hideEl(callback); + } + return this; + }, + + hideEl : function(callback){ + this.proxy.hide(); + if(this.modal){ + this.mask.hide(); + YAHOO.util.Dom.removeClass(document.body, 'masked'); + } + this.fireEvent('hide', this); + if(typeof callback == 'function'){ + callback(); + } + }, + + hideAction : function(){ + this.setLeft('-10000px'); + this.setTop('-10000px'); + this.setStyle('visibility', 'hidden'); + }, + + refreshSize : function(){ + this.size = this.el.getSize(); + this.xy = this.el.getXY(); + YAHOO.ext.state.Manager.set(this.stateId || this.el.id + '-state', this.el.getBox()); + }, + + setZIndex : function(index){ + if(this.modal){ + this.mask.setStyle('z-index', index); + } + if(this.shim){ + this.shim.setStyle('z-index', ++index); + } + if(this.shadow){ + this.shadow.setStyle('z-index', ++index); + } + this.el.setStyle('z-index', ++index); + if(this.proxy){ + this.proxy.setStyle('z-index', ++index); + } + if(this.resizer){ + this.resizer.proxy.setStyle('z-index', ++index); + } + + this.lastZIndex = index; + }, + + /** + * Returns the element for this dialog + * @return {YAHOO.ext.Element} + */ + getEl : function(){ + return this.el; + } +}); + +/** + * @class YAHOO.ext.DialogManager + * Provides global access to BasicDialogs that have been created and + * support for z-indexing (layering) multiple open dialogs. + */ +YAHOO.ext.DialogManager = function(){ + var list = {}; + var accessList = []; + var front = null; + + var sortDialogs = function(d1, d2){ + return (!d1._lastAccess || d1._lastAccess < d2._lastAccess) ? -1 : 1; + }; + + var orderDialogs = function(){ + accessList.sort(sortDialogs); + var seed = YAHOO.ext.DialogManager.zseed; + for(var i = 0, len = accessList.length; i < len; i++){ + if(accessList[i]){ + accessList[i].setZIndex(seed + (i*10)); + } + } + }; + + return { + /** + * The starting z-index for BasicDialogs - defaults to 10000 + * @type Number + */ + zseed : 10000, + + + register : function(dlg){ + list[dlg.id] = dlg; + accessList.push(dlg); + }, + + unregister : function(dlg){ + delete list[dlg.id]; + if(!accessList.indexOf){ + for(var i = 0, len = accessList.length; i < len; i++){ + accessList.splice(i, 1); + return; + } + }else{ + var i = accessList.indexOf(dlg); + if(i != -1){ + accessList.splice(i, 1); + } + } + }, + + /** + * Gets a registered dialog by id + * @param {String/Object} id The id of the dialog or a dialog + * @return {YAHOO.ext.BasicDialog} + */ + get : function(id){ + return typeof id == 'object' ? id : list[id]; + }, + + /** + * Brings the specified dialog to the front + * @param {String/Object} dlg The id of the dialog or a dialog + * @return {YAHOO.ext.BasicDialog} + */ + bringToFront : function(dlg){ + dlg = this.get(dlg); + if(dlg != front){ + front = dlg; + dlg._lastAccess = new Date().getTime(); + orderDialogs(); + } + return dlg; + }, + + /** + * Sends the specified dialog to the back + * @param {String/Object} dlg The id of the dialog or a dialog + * @return {YAHOO.ext.BasicDialog} + */ + sendToBack : function(dlg){ + dlg = this.get(dlg); + dlg._lastAccess = -(new Date().getTime()); + orderDialogs(); + return dlg; + } + }; +}(); + +/** + * @class YAHOO.ext.LayoutDialog + * @extends YAHOO.ext.BasicDialog + * Dialog which provides adjustments for working with a layout in a Dialog. + * Add your neccessary layout config options to the dialogs config.
+ * Example Usage (including a nested layout): + *
    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();
+ * @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 and resets display to none. Use standard beginUpdate/endUpdate on the layout. + * @deprecated + */ + endUpdate : function(){ + this.layout.endUpdate(); + }, + /** + * Begins an update of the layout and sets display to block and visibility to hidden. Use standard beginUpdate/endUpdate on the layout. + * @deprecated + */ + beginUpdate : function(){ + this.layout.beginUpdate(); + }, + /** + * Get the BorderLayout for this dialog + * @return {YAHOO.ext.BorderLayout} + */ + getLayout : function(){ + return this.layout; + }, + syncBodyHeight : function(){ + YAHOO.ext.LayoutDialog.superclass.syncBodyHeight.call(this); + if(this.layout)this.layout.layout(); + } +}); diff --git a/frontend/beta/js/YUI-extensions/widgets/Button.js b/frontend/beta/js/YUI-extensions/widgets/Button.js new file mode 100644 index 0000000..5bf3dc3 --- a/dev/null +++ b/frontend/beta/js/YUI-extensions/widgets/Button.js @@ -0,0 +1,185 @@ +/** + * @class YAHOO.ext.Button + * @extends YAHOO.ext.util.Observable + * Simple Button class + * @cfg {String} text The button text + * @cfg {Function} handler A function called when the button is clicked (can be used instead of click event) + * @cfg {Object} scope The scope of the handler + * @cfg {Number} minWidth The minimum width for this button (used to give a set of buttons a common width) + * @constructor + * Create a new button + * @param {String/HTMLElement/Element} renderTo The element to append the button to + * @param {Object} config The config object + */ +YAHOO.ext.Button = function(renderTo, config){ + YAHOO.ext.util.Config.apply(this, config); + this.events = { + /** + * @event click + * Fires when this button is clicked + * @param {Button} this + * @param {EventObject} e The click event + */ + 'click' : true + }; + if(renderTo){ + this.render(renderTo); + } +}; + +YAHOO.extendX(YAHOO.ext.Button, YAHOO.ext.util.Observable, { + render : function(renderTo){ + var btn; + if(!this.dhconfig){ + if(!YAHOO.ext.Button.buttonTemplate){ + // hideous table template + YAHOO.ext.Button.buttonTemplate = new YAHOO.ext.DomHelper.Template('
 {0} 
'); + } + 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 = '' + + '
' + + '
 
 
 
' + + '
'; + html += ""; + var names = this.dayNames; + for(var i = 0; i < names.length; i++){ + html += ''; + } + html+= ""; + for(var i = 0; i < 42; i++) { + if(i % 7 == 0 && i != 0){ + html += ''; + } + html += ""; + } + html += "
' + names[i].substr(0, 1) + '
 
"; + html += '
'; + 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 = "  "; + }else{ + v = v.replace(/[<> ]/g, ' '); + if(this.multiline){ + v = v.replace(/\n/g, '
 '); + } + 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:'
 
' + }); + 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 || ' '); + 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('
'); + 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 + *

Applies drag handles to an element to make it resizable. The drag handles are inserted into the element + * and positioned absolute. Some elements, such as a textarea or image, don't support this. To overcome that, you can wrap + * the textarea in a div and set "resizeChild" to true (or the id of the textarea), or set wrap:true in your config and + * the element will be wrapped for you automatically.


+ * Here's a Resizable with every possible config option and it's default value: +

+var resizer = new YAHOO.ext.Resizable('element-id', {
+    resizeChild : false,
+    adjustments : [0, 0],
+    minWidth : 5,
+    minHeight : 5,
+    maxWidth : 10000,
+    maxHeight : 10000,
+    enabled : true,
+    wrap: false, // true to wrap the element
+    width: null, // initial size
+    height: null, // initial size
+    animate : false,
+    duration : .35,
+    dynamic : false,
+    handles : false,
+    multiDirectional : false,
+    disableTrackOver : false,
+    easing : YAHOO.util.Easing ? YAHOO.util.Easing.easeOutStrong : null,
+    widthIncrement : 0,
+    heightIncrement : 0,
+    pinned : false,
+    width : null,
+    height : null,
+    preserveRatio : false,
+    transparent: false,
+    minX: 0,
+    minY: 0,
+    draggable: false
+});
+resizer.on('resize', myHandler);
+
+*

+ * To hide a particular handle, set it's display to none in CSS, or through script:
+ * resizer.east.setDisplayed(false); + *

+ * @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: ' '}); + 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 added 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: ' '} + ); + 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. + *

+ * Usage: + *

+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);
+
+ * @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. + *

+ * Usage: + *

+    // basic tabs 1, built from existing content
+    var tabs = new YAHOO.ext.TabPanel('tabs1');
+    tabs.addTab('script', "View Script");
+    tabs.addTab('markup', "View Markup");
+    tabs.activate('script');
+    
+    // more advanced tabs, built from javascript
+    var jtabs = new YAHOO.ext.TabPanel('jtabs');
+    jtabs.addTab('jtabs-1', "Normal Tab", "My content was added during construction.");
+    
+    // set up the UpdateManager
+    var tab2 = jtabs.addTab('jtabs-2', "Ajax Tab 1");
+    var updater = tab2.getUpdateManager();
+    updater.setDefaultUrl('ajax1.htm');
+    tab2.onActivate.subscribe(updater.refresh, updater, true);
+    
+    // Use setUrl for Ajax loading
+    var tab3 = jtabs.addTab('jtabs-3', "Ajax Tab 2");
+    tab3.setUrl('ajax2.htm', null, true);
+    
+    // Disabled tab
+    var tab4 = jtabs.addTab('tabs1-5', "Disabled Tab", "Can't see me cause I'm disabled");
+    tab4.disable();
+    
+    jtabs.activate('jtabs-1');
+}
+ * 
+ * @requires YAHOO.ext.Element + * @requires YAHOO.ext.UpdateManager + * @requires YAHOO.util.Dom + * @requires YAHOO.util.Event + * @requires YAHOO.util.CustomEvent + * @requires YAHOO.util.Connect (optional) + * @constructor + * Create new TabPanel. + * @param {String/HTMLElement/Element} container The id, DOM element or YAHOO.ext.Element container where this TabPanel is to be rendered. + * @param {Boolean} config Config object to set any properties for this TabPanel or true to render the tabs on the bottom. + */ +YAHOO.ext.TabPanel = function(container, config){ + /** + * The container element for this TabPanel. + * @type YAHOO.ext.Element + */ + this.el = getEl(container, true); + /** The position of the tabs. Can be 'top' or 'bottom' @type String */ + this.tabPosition = 'top'; + this.currentTabWidth = 0; + /** The minimum width of a tab (ignored if resizeTabs is not true). @type Number */ + this.minTabWidth = 40; + /** The maximum width of a tab (ignored if resizeTabs is not true). @type Number */ + this.maxTabWidth = 250; + /** The preferred (default) width of a tab (ignored if resizeTabs is not true). @type Number */ + this.preferredTabWidth = 175; + /** Set this to true to enable dynamic tab resizing. @type Boolean */ + this.resizeTabs = false; + /** Set this to true to turn on window resizing monitoring (ignored if resizeTabs is not true). @type Boolean */ + this.monitorResize = true; + + if(config){ + if(typeof config == 'boolean'){ + this.tabPosition = config ? 'bottom' : 'top'; + }else{ + YAHOO.ext.util.Config.apply(this, config); + } + } + if(this.tabPosition == 'bottom'){ + this.bodyEl = getEl(this.createBody(this.el.dom)); + this.el.addClass('ytabs-bottom'); + } + this.stripWrap = getEl(this.createStrip(this.el.dom), true); + this.stripEl = getEl(this.createStripList(this.stripWrap.dom), true); + this.stripBody = getEl(this.stripWrap.dom.firstChild.firstChild, true); + if(YAHOO.ext.util.Browser.isIE){ + YAHOO.util.Dom.setStyle(this.stripWrap.dom.firstChild, 'overflow-x', 'hidden'); + } + if(this.tabPosition != 'bottom'){ + /** The body element that contains TabPaneItem bodies. + * @type YAHOO.ext.Element + */ + this.bodyEl = getEl(this.createBody(this.el.dom)); + this.el.addClass('ytabs-top'); + } + this.items = []; + + this.bodyEl.setStyle('position', 'relative'); + + // add indexOf to array if it isn't present + if(!this.items.indexOf){ + this.items.indexOf = function(o){ + for(var i = 0, len = this.length; i < len; i++){ + if(this[i] == o) return i; + } + return -1; + } + } + this.active = null; + this.onTabChange = new YAHOO.util.CustomEvent('TabItem.onTabChange'); + this.activateDelegate = this.activate.createDelegate(this); + + this.events = { + /** + * @event tabchange + * Fires when the active tab changes + * @param {YAHOO.ext.TabPanel} this + * @param {YAHOO.ext.TabPanelItem} activePanel The new active tab + */ + 'tabchange': this.onTabChange, + /** + * @event beforetabchange + * Fires before the active tab changes, set cancel to true on the "e" parameter to cancel the change + * @param {YAHOO.ext.TabPanel} this + * @param {Object} e Set cancel to true on this object to cancel the tab change + * @param {YAHOO.ext.TabPanelItem} tab The tab being changed to + */ + 'beforetabchange' : new YAHOO.util.CustomEvent('beforechange') + }; + + YAHOO.ext.EventManager.onWindowResize(this.onResize, this, true); + this.cpad = this.el.getPadding('lr'); + this.hiddenCount = 0; +} + +YAHOO.ext.TabPanel.prototype = { + fireEvent : YAHOO.ext.util.Observable.prototype.fireEvent, + on : YAHOO.ext.util.Observable.prototype.on, + addListener : YAHOO.ext.util.Observable.prototype.addListener, + delayedListener : YAHOO.ext.util.Observable.prototype.delayedListener, + removeListener : YAHOO.ext.util.Observable.prototype.removeListener, + purgeListeners : YAHOO.ext.util.Observable.prototype.purgeListeners, + /** + * Creates a new TabPanelItem by looking for an existing element with the provided id - if it's not found it creates one. + * @param {String} id The id of the div to use or create + * @param {String} text The text for the tab + * @param {String} content (optional) Content to put in the TabPanelItem body + * @param {Boolean} closable (optional) True to create a close icon on the tab + * @return {YAHOO.ext.TabPanelItem} The created TabPanelItem + */ + addTab : function(id, text, content, closable){ + var item = new YAHOO.ext.TabPanelItem(this, id, text, closable); + this.addTabItem(item); + if(content){ + item.setContent(content); + } + return item; + }, + + /** + * Returns the TabPanelItem with the specified id/index + * @param {String/Number} id The id or index of the TabPanelItem to fetch. + * @return {YAHOO.ext.TabPanelItem} + */ + getTab : function(id){ + return this.items[id]; + }, + + /** + * Hides the TabPanelItem with the specified id/index + * @param {String/Number} id The id or index of the TabPanelItem to hide. + */ + hideTab : function(id){ + var t = this.items[id]; + if(!t.isHidden()){ + t.setHidden(true); + this.hiddenCount++; + this.autoSizeTabs(); + } + }, + + /** + * "Unhides" the TabPanelItem with the specified id/index + * @param {String/Number} id The id or index of the TabPanelItem to unhide. + */ + unhideTab : function(id){ + var t = this.items[id]; + if(t.isHidden()){ + t.setHidden(false); + this.hiddenCount--; + this.autoSizeTabs(); + } + }, + + /** + * Add an existing TabPanelItem. + * @param {YAHOO.ext.TabPanelItem} item The TabPanelItem to add + */ + addTabItem : function(item){ + this.items[item.id] = item; + this.items.push(item); + if(this.resizeTabs){ + item.setWidth(this.currentTabWidth || this.preferredTabWidth) + this.autoSizeTabs(); + }else{ + item.autoSize(); + } + }, + + /** + * Remove a TabPanelItem. + * @param {String/Number} id The id or index of the TabPanelItem to remove. + */ + removeTab : function(id){ + var items = this.items; + var tab = items[id]; + if(!tab) return; + var index = items.indexOf(tab); + if(this.active == tab && items.length > 1){ + var newTab = this.getNextAvailable(index); + if(newTab)newTab.activate(); + } + this.stripEl.dom.removeChild(tab.pnode.dom); + if(tab.bodyEl.dom.parentNode == this.bodyEl.dom){ // if it was moved already prevent error + this.bodyEl.dom.removeChild(tab.bodyEl.dom); + } + items.splice(index, 1); + delete this.items[tab.id]; + tab.fireEvent('close', tab); + tab.purgeListeners(); + this.autoSizeTabs(); + }, + + getNextAvailable : function(start){ + var items = this.items; + var index = start; + // look for a next tab that will slide over to + // replace the one being removed + while(index < items.length){ + var item = items[++index]; + if(item && !item.isHidden()){ + return item; + } + } + // if one isn't found select the previous tab (on the left) + var index = start; + while(index >= 0){ + var item = items[--index]; + if(item && !item.isHidden()){ + return item; + } + } + return null; + }, + + /** + * Disable a TabPanelItem. It cannot be the active tab, if it is this call is ignored.. + * @param {String/Number} id The id or index of the TabPanelItem to disable. + */ + disableTab : function(id){ + var tab = this.items[id]; + if(tab && this.active != tab){ + tab.disable(); + } + }, + + /** + * Enable a TabPanelItem that is disabled. + * @param {String/Number} id The id or index of the TabPanelItem to enable. + */ + enableTab : function(id){ + var tab = this.items[id]; + tab.enable(); + }, + + /** + * Activate a TabPanelItem. The currently active will be deactivated. + * @param {String/Number} id The id or index of the TabPanelItem to activate. + */ + activate : function(id){ + var tab = this.items[id]; + if(tab == this.active){ + return tab; + } + var e = {}; + this.fireEvent('beforetabchange', this, e, tab); + if(e.cancel !== true && !tab.disabled){ + if(this.active){ + this.active.hide(); + } + this.active = this.items[id]; + this.active.show(); + this.onTabChange.fireDirect(this, this.active); + } + return tab; + }, + + /** + * Get the active TabPanelItem + * @return {YAHOO.ext.TabPanelItem} The active TabPanelItem or null if none are active. + */ + getActiveTab : function(){ + return this.active; + }, + + /** + * Updates the tab body element to fit the height of the container element + * for overflow scrolling + * @param {Number} targetHeight (optional) Override the starting height from the elements height + */ + syncHeight : function(targetHeight){ + var height = (targetHeight || this.el.getHeight())-this.el.getBorderWidth('tb')-this.el.getPadding('tb'); + var bm = this.bodyEl.getMargins(); + var newHeight = height-(this.stripWrap.getHeight()||0)-(bm.top+bm.bottom); + this.bodyEl.setHeight(newHeight); + return newHeight; + }, + + onResize : function(){ + if(this.monitorResize){ + this.autoSizeTabs(); + } + }, + + /** + * Disables tab resizing while tabs are being added (if resizeTabs is false this does nothing) + */ + beginUpdate : function(){ + this.updating = true; + }, + + /** + * Stops an update and resizes the tabs (if resizeTabs is false this does nothing) + */ + endUpdate : function(){ + this.updating = false; + this.autoSizeTabs(); + }, + + /** + * Manual call to resize the tabs (if resizeTabs is false this does nothing) + */ + autoSizeTabs : function(){ + var count = this.items.length; + var vcount = count - this.hiddenCount; + if(!this.resizeTabs || count < 1 || vcount < 1 || this.updating) return; + var w = Math.max(this.el.getWidth() - this.cpad, 10); + var availWidth = Math.floor(w / vcount); + var b = this.stripBody; + if(b.getWidth() > w){ + var tabs = this.items; + this.setTabWidth(Math.max(availWidth, this.minTabWidth)); + if(availWidth < this.minTabWidth){ + /*if(!this.sleft){ // incomplete scrolling code + this.createScrollButtons(); + } + this.showScroll(); + this.stripClip.setWidth(w - (this.sleft.getWidth()+this.sright.getWidth()));*/ + } + }else{ + if(this.currentTabWidth < this.preferredTabWidth){ + this.setTabWidth(Math.min(availWidth, this.preferredTabWidth)); + } + } + }, + + /** + * Returns the number of tabs + * @return {Number} + */ + getCount : function(){ + return this.items.length; + }, + + /** + * Resizes all the tabs to the passed width + * @param {Number} The new width + */ + setTabWidth : function(width){ + this.currentTabWidth = width; + for(var i = 0, len = this.items.length; i < len; i++) { + if(!this.items[i].isHidden())this.items[i].setWidth(width); + } + }, + + /** + * Destroys this TabPanel + * @param {Boolean} removeEl (optional) True to remove the element from the DOM as well + */ + destroy : function(removeEl){ + YAHOO.ext.EventManager.removeResizeListener(this.onResize, this); + for(var i = 0, len = this.items.length; i < len; i++){ + this.items[i].purgeListeners(); + } + if(removeEl === true){ + this.el.update(''); + this.el.remove(); + } + } +}; + +/** +* @class YAHOO.ext.TabPanelItem +* @extends YAHOO.ext.util.Observable +*/ +YAHOO.ext.TabPanelItem = function(tabPanel, id, text, closable){ + /** + * The TabPanel this TabPanelItem belongs to + * @type YAHOO.ext.TabPanel + */ + this.tabPanel = tabPanel; + /** + * The id for this TabPanelItem + * @type String + */ + this.id = id; + /** @private */ + this.disabled = false; + /** @private */ + this.text = text; + /** @private */ + this.loaded = false; + this.closable = closable; + + /** + * The body element for this TabPanelItem + * @type YAHOO.ext.Element + */ + this.bodyEl = getEl(tabPanel.createItemBody(tabPanel.bodyEl.dom, id)); + this.bodyEl.setVisibilityMode(YAHOO.ext.Element.VISIBILITY); + this.bodyEl.setStyle('display', 'block'); + this.bodyEl.setStyle('zoom', '1'); + this.hideAction(); + + var els = tabPanel.createStripElements(tabPanel.stripEl.dom, text, closable); + /** @private */ + this.el = getEl(els.el, true); + this.inner = getEl(els.inner, true); + this.textEl = getEl(this.el.dom.firstChild.firstChild.firstChild, true); + this.pnode = getEl(els.el.parentNode, true); + this.el.mon('click', this.onTabClick, this, true); + /** @private */ + if(closable){ + var c = getEl(els.close, true); + c.dom.title = this.closeText; + c.addClassOnOver('close-over'); + c.mon('click', this.closeClick, this, true); + } + + // these two are now private and deprecated + this.onActivate = new YAHOO.util.CustomEvent('TabItem.onActivate'); + this.onDeactivate = new YAHOO.util.CustomEvent('TabItem.onDeactivate'); + + this.events = { + /** + * @event activate + * Fires when this tab becomes the active tab + * @param {YAHOO.ext.TabPanel} tabPanel + * @param {YAHOO.ext.TabPanelItem} this + */ + 'activate': this.onActivate, + /** + * @event beforeclose + * Fires before this tab is closed. To cancal the close, set cancel to true on e. (e.cancel = true) + * @param {YAHOO.ext.TabPanelItem} this + * @param {Object} e Set cancel to true on this object to cancel the close. + */ + 'beforeclose': new YAHOO.util.CustomEvent('beforeclose'), + /** + * @event close + * Fires when this tab is closed + * @param {YAHOO.ext.TabPanelItem} this + */ + 'close': new YAHOO.util.CustomEvent('close'), + /** + * @event deactivate + * Fires when this tab is no longer the active tab + * @param {YAHOO.ext.TabPanel} tabPanel + * @param {YAHOO.ext.TabPanelItem} this + */ + 'deactivate' : this.onDeactivate + }; + this.hidden = false; +}; + +YAHOO.ext.TabPanelItem.prototype = { + fireEvent : YAHOO.ext.util.Observable.prototype.fireEvent, + on : YAHOO.ext.util.Observable.prototype.on, + addListener : YAHOO.ext.util.Observable.prototype.addListener, + delayedListener : YAHOO.ext.util.Observable.prototype.delayedListener, + removeListener : YAHOO.ext.util.Observable.prototype.removeListener, + purgeListeners : function(){ + YAHOO.ext.util.Observable.prototype.purgeListeners.call(this); + this.el.removeAllListeners(); + }, + /** + * Show this TabPanelItem - this does not deactivate the currently active TabPanelItem. + */ + show : function(){ + this.pnode.addClass('on'); + this.showAction(); + if(YAHOO.ext.util.Browser.isOpera){ + this.tabPanel.stripWrap.repaint(); + } + this.onActivate.fireDirect(this.tabPanel, this); + }, + + /** + * Returns true if this tab is the active tab + * @return {Boolean} + */ + isActive : function(){ + return this.tabPanel.getActiveTab() == this; + }, + + /** + * Hide this TabPanelItem - if you don't activate another TabPanelItem this could look odd. + */ + hide : function(){ + this.pnode.removeClass('on'); + this.hideAction(); + this.onDeactivate.fireDirect(this.tabPanel, this); + }, + + hideAction : function(){ + this.bodyEl.setStyle('position', 'absolute'); + this.bodyEl.setLeft('-20000px'); + this.bodyEl.setTop('-20000px'); + this.bodyEl.hide(); + }, + + showAction : function(){ + this.bodyEl.setStyle('position', 'relative'); + this.bodyEl.setTop(''); + this.bodyEl.setLeft(''); + this.bodyEl.show(); + this.tabPanel.el.repaint.defer(1); + }, + + /** + * Set the tooltip (title attribute) for the tab + * @param {String} tooltip + */ + setTooltip : function(text){ + this.textEl.dom.title = text; + }, + + onTabClick : function(e){ + e.preventDefault(); + this.tabPanel.activate(this.id); + }, + + getWidth : function(){ + return this.inner.getWidth(); + }, + + setWidth : function(width){ + var iwidth = width - this.pnode.getPadding("lr"); + this.inner.setWidth(iwidth); + this.textEl.setWidth(iwidth-this.inner.getPadding('lr')); + this.pnode.setWidth(width); + }, + + setHidden : function(hidden){ + this.hidden = hidden; + this.pnode.setStyle('display', hidden ? 'none' : ''); + }, + + /** + * Returns true if this tab is "hidden" + * @return {Boolean} + */ + isHidden : function(){ + return this.hidden; + }, + + /** + * Returns the text for this tab + * @return {String} + */ + getText : function(){ + return this.text; + }, + + autoSize : function(){ + this.el.beginMeasure(); + this.textEl.setWidth(1); + this.setWidth(this.textEl.dom.scrollWidth+this.pnode.getPadding("lr")+this.inner.getPadding('lr')); + this.el.endMeasure(); + }, + + /** + * Sets the text for the tab (Note: this also sets the tooltip) + * @param {String} text + */ + setText : function(text){ + this.text = text; + this.textEl.update(text); + this.textEl.dom.title = text; + if(!this.tabPanel.resizeTabs){ + this.autoSize(); + } + }, + /** + * Activate this TabPanelItem - this does deactivate the currently active TabPanelItem. + */ + activate : function(){ + this.tabPanel.activate(this.id); + }, + + /** + * Disable this TabPanelItem - this call is ignore if this is the active TabPanelItem. + */ + disable : function(){ + if(this.tabPanel.active != this){ + this.disabled = true; + this.pnode.addClass('disabled'); + } + }, + + /** + * Enable this TabPanelItem if it was previously disabled. + */ + enable : function(){ + this.disabled = false; + this.pnode.removeClass('disabled'); + }, + + /** + * Set the content for this TabPanelItem. + * @param {String} content The content + * @param {Boolean} loadScripts true to look for and load scripts + */ + setContent : function(content, loadScripts){ + this.bodyEl.update(content, loadScripts); + }, + + /** + * Get the {@link YAHOO.ext.UpdateManager} for the body of this TabPanelItem. Enables you to perform Ajax updates. + * @return {YAHOO.ext.UpdateManager} The UpdateManager + */ + getUpdateManager : function(){ + return this.bodyEl.getUpdateManager(); + }, + + /** + * Set a URL to be used to load the content for this TabPanelItem. + * @param {String/Function} url The url to load the content from or a function to call to get the url + * @param {String/Object} params (optional) The string params for the update call or an object of the params. See {@link YAHOO.ext.UpdateManager#update} for more details. (Defaults to null) + * @param {Boolean} loadOnce (optional) Whether to only load the content once. If this is false it makes the Ajax call every time this TabPanelItem is activated. (Defaults to false) + * @return {YAHOO.ext.UpdateManager} The UpdateManager + */ + setUrl : function(url, params, loadOnce){ + if(this.refreshDelegate){ + this.onActivate.unsubscribe(this.refreshDelegate); + } + this.refreshDelegate = this._handleRefresh.createDelegate(this, [url, params, loadOnce]); + this.onActivate.subscribe(this.refreshDelegate); + return this.bodyEl.getUpdateManager(); + }, + + /** @private */ + _handleRefresh : function(url, params, loadOnce){ + if(!loadOnce || !this.loaded){ + var updater = this.bodyEl.getUpdateManager(); + updater.update(url, params, this._setLoaded.createDelegate(this)); + } + }, + + /** + * Force a content refresh from the URL specified in the setUrl() method. + * Will fail silently if the setUrl method has not been called. + * This does not activate the panel, just updates its content. + */ + refresh : function(){ + if(this.refreshDelegate){ + this.loaded = false; + this.refreshDelegate(); + } + }, + + /** @private */ + _setLoaded : function(){ + this.loaded = true; + }, + + /** @private */ + closeClick : function(e){ + var e = {}; + this.fireEvent('beforeclose', this, e); + if(e.cancel !== true){ + this.tabPanel.removeTab(this.id); + } + }, + /** + * The text displayed in the tooltip for the close icon. + * @type String + */ + closeText : 'Close this tab' +}; + +/** @private */ +YAHOO.ext.TabPanel.prototype.createStrip = function(container){ + var strip = document.createElement('div'); + strip.className = 'ytab-wrap'; + container.appendChild(strip); + return strip; +}; +/** @private */ +YAHOO.ext.TabPanel.prototype.createStripList = function(strip){ + // div wrapper for retard IE + strip.innerHTML = '
'; + 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( + '' + + '{text}' + + '
 
' + ); + } + 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( + '' + + '{text}' + ); + } + 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.
+ * Create a data model bound view: +

+var dataModel = new YAHOO.ext.grid.XMLDataModel(...);
+var view = new YAHOO.ext.View('my-element', 
+           '<div id="{0}">{2} - {1}</div>', // auto create template
+           dataModel, { 
+              singleSelect: true, 
+              selectedClass: 'ydataview-selected'
+           });
+
+// listen for node click?
+view.on('click', function(vw, index, node, e){
+    alert('Node "' + node.id + '" at index: ' + index + ' was clicked.');
+});
+
+// load XML data
+dataModel.load('foobar.xml');
+
+For an example of creating a JSON/UpdateManager view, see {@link YAHOO.ext.JsonView}. + *

+ * Note: The root of your template must be a single node. Table/row implementations may work but are not supported due to + * IE's limited insertion support with tables and Opera's faulty event bubbling. + * @constructor + * Create a new View + * @param {String/HTMLElement/Element} container The container element where the view is to be rendered. + * @param {String/DomHelper.Template} tpl The rendering template or a string to create a template with + * @param {DataModel} dataModel The bound data model + * @param {Object} config The config object +*/ +YAHOO.ext.View = function(container, tpl, dataModel, config){ + this.el = getEl(container, true); + this.nodes = this.el.dom.childNodes; + if(typeof tpl == 'string'){ + tpl = new YAHOO.ext.Template(tpl); + } + tpl.compile(); + /** + * The template used by this View + * @type {YAHOO.ext.DomHelper.Template} + */ + this.tpl = tpl; + this.setDataModel(dataModel); + var CE = YAHOO.util.CustomEvent; + /** @private */ + this.events = { + /** + * @event beforeclick + * Fires before a click is processed. Returns false to cancel the default action. + * @param {YAHOO.ext.View} this + * @param {Number} index The index of the target node + * @param {HTMLElement} node The target node + * @param {YAHOO.ext.EventObject} e The raw event object + */ + 'beforeclick' : true, + /** + * @event click + * Fires when a template node is clicked. + * @param {YAHOO.ext.View} this + * @param {Number} index The index of the target node + * @param {HTMLElement} node The target node + * @param {YAHOO.ext.EventObject} e The raw event object + */ + 'click' : true, + /** + * @event dblclick + * Fires when a template node is double clicked. + * @param {YAHOO.ext.View} this + * @param {Number} index The index of the target node + * @param {HTMLElement} node The target node + * @param {YAHOO.ext.EventObject} e The raw event object + */ + 'dblclick' : true, + /** + * @event contextmenu + * Fires when a template node is right clicked. + * @param {YAHOO.ext.View} this + * @param {Number} index The index of the target node + * @param {HTMLElement} node The target node + * @param {YAHOO.ext.EventObject} e The raw event object + */ + 'contextmenu' : true, + /** + * @event selectionchange + * Fires when the selected nodes change. + * @param {YAHOO.ext.View} this + * @param {Array} selections Array of the selected nodes + */ + 'selectionchange' : true, + + /** + * @event beforeselect + * Fires before a selection is made. If any handlers return false, the selection is cancelled. + * @param {YAHOO.ext.View} this + * @param {HTMLElement} node The node to be selected + * @param {Array} selections Array of currently selected nodes + */ + 'beforeselect' : true + }; + this.el.mon("click", this.onClick, this, true); + this.el.mon("dblclick", this.onDblClick, this, true); + this.el.mon("contextmenu", this.onContextMenu, this, true); + + /** + * The css class to add to selected nodes + * @type {YAHOO.ext.DomHelper.Template} + */ + this.selectedClass = 'ydataview-selected'; + + this.emptyText = ''; + + this.selections = []; + + this.lastSelection = null; + + /** + * The root property in the loaded json object that contains the data + * @type {String} + */ + this.jsonRoot = null; + YAHOO.ext.util.Config.apply(this, config); + if(this.renderUpdates || this.jsonRoot){ + var um = this.el.getUpdateManager(); + um.setRenderer(this); + } +}; + +YAHOO.extendX(YAHOO.ext.View, YAHOO.ext.util.Observable, { + /** + * Returns the element this view is bound to. + * @return {YAHOO.ext.Element} + */ + getEl : function(){ + return this.el; + }, + + render : function(el, response){ + this.clearSelections(); + this.el.update(''); + var o; + try{ + o = YAHOO.ext.util.JSON.decode(response.responseText); + if(this.jsonRoot){ + o = eval('o.' + this.jsonRoot); + } + }catch(e){} + /** + * The current json data or null + */ + this.jsonData = o; + this.beforeRender(); + this.refresh(); + }, + + beforeRender : function(){ + + }, + + /** + * Refreshes the view. + */ + refresh : function(){ + this.clearSelections(); + this.el.update(''); + this.html = []; + if(this.renderUpdates || this.jsonRoot){ + var o = this.jsonData; + if(o){ + for(var i = 0, len = o.length; i < len; i++) { + this.renderEach(o[i]); + } + } + }else{ + this.dataModel.each(this.renderEach, this); + } + var strHtml; + if(this.html.length > 0){ + strHtml = this.html.join(''); + }else{ + strHtml = this.emptyText; + } + this.el.update(strHtml); + this.html = null; + this.nodes = this.el.dom.childNodes; + this.updateIndexes(0); + }, + + /** + * Function to override to reformat the data that is sent to + * the template for each node. + * @param {Array/Object} data The raw data (array of colData for a data model bound view or + * a JSON object for an UpdateManager bound view). + * @param {Number} index The index of the data within the data model + */ + prepareData : function(data, index){ + return data; + }, + + renderEach : function(data){ + this.html[this.html.length] = this.tpl.applyTemplate(this.prepareData(data)); + }, + + /** + * Refresh an individual node. + * @param {Number} index + */ + refreshNode : function(index){ + this.refreshNodes(index, index); + }, + + refreshNodes : function(dm, startIndex, endIndex){ + this.clearSelections(); + var dm = this.dataModel; + var ns = this.nodes; + for(var i = startIndex; i <= endIndex; i++){ + var d = this.prepareData(dm.getRow(i), i); + if(i < ns.length-1){ + var old = ns[i]; + this.tpl.insertBefore(old, d); + this.el.dom.removeChild(old); + }else{ + this.tpl.append(this.el.dom, d); + } + } + this.updateIndexes(startIndex, endIndex); + }, + + deleteNodes : function(dm, startIndex, endIndex){ + this.clearSelections(); + if(startIndex == 0 && endIndex >= this.nodes.length-1){ + this.el.update(''); + }else{ + var el = this.el.dom; + for(var i = startIndex; i <= endIndex; i++){ + el.removeChild(this.nodes[startIndex]); + } + this.updateIndexes(startIndex); + } + }, + + insertNodes : function(dm, startIndex, endIndex){ + if(this.nodes.length == 0){ + this.refresh(); + }else{ + this.clearSelections(); + var t = this.tpl; + var before = this.nodes[startIndex]; + var dm = this.dataModel; + if(before){ + for(var i = startIndex; i <= endIndex; i++){ + t.insertBefore(before, this.prepareData(dm.getRow(i), i)); + } + }else{ + var el = this.el.dom; + for(var i = startIndex; i <= endIndex; i++){ + t.append(el, this.prepareData(dm.getRow(i), i)); + } + } + this.updateIndexes(startIndex); + } + }, + + updateIndexes : function(dm, startIndex, endIndex){ + var ns = this.nodes; + startIndex = startIndex || 0; + endIndex = endIndex || ns.length-1; + for(var i = startIndex; i <= endIndex; i++){ + ns[i].nodeIndex = i; + } + }, + + /** + * Changes the data model this view uses and refresh the view. + * @param {DataModel} dataModel + */ + setDataModel : function(dm){ + if(!dm) return; + this.unplugDataModel(this.dataModel); + this.dataModel = dm; + dm.on('cellupdated', this.refreshNode, this, true); + dm.on('datachanged', this.refresh, this, true); + dm.on('rowsdeleted', this.deleteNodes, this, true); + dm.on('rowsinserted', this.insertNodes, this, true); + dm.on('rowsupdated', this.refreshNodes, this, true); + dm.on('rowssorted', this.refresh, this, true); + this.refresh(); + }, + + /** + * Unplug the data model and stop updates. + * @param {DataModel} dataModel + */ + unplugDataModel : function(dm){ + if(!dm) return; + dm.removeListener('cellupdated', this.refreshNode, this); + dm.removeListener('datachanged', this.refresh, this); + dm.removeListener('rowsdeleted', this.deleteNodes, this); + dm.removeListener('rowsinserted', this.insertNodes, this); + dm.removeListener('rowsupdated', this.refreshNodes, this); + dm.removeListener('rowssorted', this.refresh, this); + this.dataModel = null; + }, + + /** + * Returns the template node the passed child belongs to or null if it doesn't belong to one. + * @param {HTMLElement} node + * @return {HTMLElement} The template node + */ + findItemFromChild : function(node){ + var el = this.el.dom; + if(!node || node.parentNode == el){ + return node; + } + var p = node.parentNode; + while(p && p != el){ + if(p.parentNode == el){ + return p; + } + p = p.parentNode; + } + return null; + }, + + /** @ignore */ + onClick : function(e){ + var item = this.findItemFromChild(e.getTarget()); + if(item){ + var index = this.indexOf(item); + if(this.onItemClick(item, index, e) !== false){ + this.fireEvent('click', this, index, item, e); + } + }else{ + this.clearSelections(); + } + }, + + /** @ignore */ + onContextMenu : function(e){ + var item = this.findItemFromChild(e.getTarget()); + if(item){ + this.fireEvent('contextmenu', this, this.indexOf(item), item, e); + } + }, + + /** @ignore */ + onDblClick : function(e){ + var item = this.findItemFromChild(e.getTarget()); + if(item){ + this.fireEvent('dblclick', this, this.indexOf(item), item, e); + } + }, + + onItemClick : function(item, index, e){ + if(this.fireEvent('beforeclick', this, index, item, e) !== false){ + if(this.multiSelect || this.singleSelect){ + if(this.multiSelect && e.shiftKey && this.lastSelection){ + this.select(this.getNodes(this.indexOf(this.lastSelection), index), false); + }else{ + this.select(item, this.multiSelect && e.ctrlKey); + this.lastSelection = item; + } + e.preventDefault(); + } + return true; + }else{ + return false; + } + }, + + /** + * Get the number of selected nodes. + * @return {Number} + */ + getSelectionCount : function(){ + return this.selections.length; + }, + + /** + * Get the currently selected nodes. + * @return {Array} An array of HTMLElements + */ + getSelectedNodes : function(){ + return this.selections; + }, + + /** + * Get the indexes of the selected nodes. + * @return {Array} + */ + getSelectedIndexes : function(){ + var indexes = []; + for(var i = 0, len = this.selections.length; i < len; i++) { + indexes.push(this.selections[i].nodeIndex); + } + return indexes; + }, + + /** + * Clear all selections + * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange event + */ + clearSelections : function(suppressEvent){ + if(this.multiSelect || this.singleSelect){ + YAHOO.util.Dom.removeClass(this.selections, this.selectedClass); + this.selections = []; + if(!suppressEvent){ + this.fireEvent('selectionchange', this, this.selections); + } + } + }, + + /** + * Returns true if the passed node is selected + * @param {HTMLElement/Number} node The node or node index + * @return {Boolean} + */ + isSelected : function(node){ + node = this.getNode(node); + var s = this.selections; + if(s.length < 1){ + return false; + } + if(s.indexOf){ + return s.indexOf(node) !== -1; + }else{ + for(var i = 0, len = s.length; i < len; i++){ + if (s[i] == node){ + return true; + } + } + return false; + } + }, + + /** + * Selects nodes. + * @param {Array/HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node, id of a template node or an array of any of those to select + * @param {Boolean} keepExisting (optional) true to keep existing selections + * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange vent + */ + select : function(nodeInfo, keepExisting, suppressEvent){ + if(!keepExisting){ + this.clearSelections(true); + } + if(nodeInfo instanceof Array){ + for(var i = 0, len = nodeInfo.length; i < len; i++) { + this.select(nodeInfo[i], true, true); + } + }else{ + var node = this.getNode(nodeInfo); + if(node && !this.isSelected(node)){ + if(this.fireEvent('beforeselect', this, node, this.selections) !== false){ + YAHOO.util.Dom.addClass(node, this.selectedClass); + this.selections.push(node); + } + } + } + if(!suppressEvent){ + this.fireEvent('selectionchange', this, this.selections); + } + }, + + /** + * Gets a template node. + * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node + * @return {HTMLElement} The node or null if it wasn't found + */ + getNode : function(nodeInfo){ + if(typeof nodeInfo == 'object'){ + return nodeInfo; + }else if(typeof nodeInfo == 'string'){ + return document.getElementById(nodeInfo); + }else if(typeof nodeInfo == 'number'){ + return this.nodes[nodeInfo]; + } + return null; + }, + + /** + * Gets a range template nodes. + * @param {Number} startIndex + * @param {Number} endIndex + * @return {Array} An array of nodes + */ + getNodes : function(start, end){ + var ns = this.nodes; + start = start || 0; + end = typeof end == 'undefined' ? ns.length-1 : end; + var nodes = []; + if(start <= end){ + for(var i = start; i <= end; i++) { + nodes.push(ns[i]); + } + }else{ + for(var i = start; i >= end; i--) { + nodes.push(ns[i]); + } + } + return nodes; + }, + + /** + * Finds the index of the passed node + * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node + * @return {Number} The index of the node or -1 + */ + indexOf : function(node){ + node = this.getNode(node); + if(typeof node.nodeIndex == 'number'){ + return node.nodeIndex; + } + var ns = this.nodes; + for(var i = 0, len = ns.length; i < len; i++) { + if(ns[i] == node){ + return i; + } + } + return -1; + } +}); + +/** + * @class YAHOO.ext.JsonView + * @extends YAHOO.ext.View + * Shortcut class to create a JSON + UpdateManager template view. Usage: +

+var view = new YAHOO.ext.JsonView('my-element', 
+           '<div id="{id}">{foo} - {bar}</div>', // auto create template
+           { multiSelect: true, jsonRoot: 'data' });
+
+// listen for node click?
+view.on('click', function(vw, index, node, e){
+    alert('Node "' + node.id + '" at index: ' + index + ' was clicked.');
+});
+
+// direct load of JSON data
+view.load('foobar.php');
+
+
+// Example from my blog list
+var tpl = new YAHOO.ext.Template(
+    '<div class="entry">' + 
+       '<a class="entry-title" href="{link}">{title}</a>' +
+       '<h4>{date} by {author} | {comments} Comments</h4>{description}' +
+    '</div><hr />'
+);
+
+var moreView = new YAHOO.ext.JsonView('entry-list', tpl, {
+   jsonRoot: 'posts'
+});
+moreView.on('beforerender', this.sortEntries, this, true);
+moreView.load({
+    url:'/blog/get-posts.php', 
+    params: 'allposts=true',
+    text:'Loading Blog Entries...'
+});
+
+ * @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: +

+view.load({
+    url: 'your-url.php',
+ params: {param1: 'foo', param2: 'bar'}, // or a URL encoded string
+ callback: yourFunction,
+ scope: yourObject, //(optional scope)
+ discardUrl: false,
+ nocache: false,
+ text: 'Loading...',
+ timeout: 30,
+ scripts: false
+}); +
+ * The only required property is url. The optional properties nocache, text and scripts + * are shorthand for disableCaching, indicatorText and loadScripts and are used to set their associated property on this UpdateManager instance. + * @param {String/Object} params (optional) The parameters to pass as either a url encoded string "param1=1&param2=2" or an object {param1: 1, param2: 2} + * @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess) + * @param {Boolean} discardUrl (optional) By default when you execute an update the defaultUrl is changed to the last used url. If true, it will not store the url. + */ + load : function(){ + var um = this.el.getUpdateManager(); + um.update.apply(um, arguments); + }, + + /** + * Get the number of records in the current JSON dataset + * @return {Number} + */ + getCount : function(){ + return this.jsonData ? this.jsonData.length : 0; + }, + + /** + * Returns the JSON object for the specified node(s) + * @param {HTMLElement/Array} node The node or an array of nodes + * @return {Object/Array} If you pass in an array, you get an array back, otherwise + * you get the JSON object for the node + */ + getNodeData : function(node){ + if(node instanceof Array){ + var data = []; + for(var i = 0, len = node.length; i < len; i++){ + data.push(this.getNodeData(node[i])); + } + return data; + } + return this.jsonData[this.indexOf(node)] || null; + }, + + beforeRender : function(){ + this.snapshot = this.jsonData; + if(this.sortInfo){ + this.sort.apply(this, this.sortInfo); + } + this.fireEvent('beforerender', this, this.jsonData); + }, + + onLoad : function(el, o){ + this.fireEvent('load', this, this.jsonData, o); + }, + + onLoadException : function(el, o){ + this.fireEvent('loadexception', this, o); + }, + + /** + * Filter the data by a specific property. + * @param {String} property A property on your JSON objects + * @param {String/RegExp} value Either string that the property values + * should start with or a RegExp to test against the property + */ + filter : function(property, value){ + if(this.jsonData){ + var data = []; + var ss = this.snapshot; + if(typeof value == 'string'){ + var vlen = value.length; + if(vlen == 0){ + this.clearFilter(); + return; + } + value = value.toLowerCase(); + for(var i = 0, len = ss.length; i < len; i++){ + var o = ss[i]; + if(o[property].substr(0, vlen).toLowerCase() == value){ + data.push(o); + } + } + }else if(value.exec){ // regex? + for(var i = 0, len = ss.length; i < len; i++){ + var o = ss[i]; + if(value.test(o[property])){ + data.push(o); + } + } + }else{ + return; + } + this.jsonData = data; + this.refresh(); + } + }, + + /** + * Filter by a function. The passed function will be called with each + * object in the current dataset. If the function returns true, the value is kept + * otherwise it is filtered. + * @param {Function} fn + * @param {Object} scope (optional) The scope of the function (defaults to this JsonView) + */ + filterBy : function(fn, scope){ + if(this.jsonData){ + var data = []; + var ss = this.snapshot; + for(var i = 0, len = ss.length; i < len; i++){ + var o = ss[i]; + if(fn.call(scope|| this, o)){ + data.push(o); + } + } + this.jsonData = data; + this.refresh(); + } + }, + + /** + * Clears the current filter. + */ + clearFilter : function(){ + if(this.snapshot && this.jsonData != this.snapshot){ + this.jsonData = this.snapshot; + this.refresh(); + } + }, + + + /** + * Sorts the data for this view and refreshes it. + * @param {String} property A property on your JSON objects to sort on + * @param {String} direction (optional) desc or asc (defaults to asc) + * @param {Function} sortType (optional) A function to call to convert the data to a sortable value. + */ + sort : function(property, dir, sortType){ + this.sortInfo = Array.prototype.slice.call(arguments, 0); + if(this.jsonData){ + var p = property; + var dsc = dir && dir.toLowerCase() == 'desc'; + var f = function(o1, o2){ + var v1 = sortType ? sortType(o1[p]) : o1[p]; + var v2 = sortType ? sortType(o2[p]) : o2[p];; + if(v1 < v2){ + return dsc ? +1 : -1; + }else if(v1 > v2){ + return dsc ? -1 : +1; + }else{ + return 0; + } + }; + this.jsonData.sort(f); + this.refresh(); + if(this.jsonData != this.snapshot){ + this.snapshot.sort(f); + } + } + } +}); diff --git a/frontend/beta/js/YUI-extensions/widgets/Toolbar.js b/frontend/beta/js/YUI-extensions/widgets/Toolbar.js new file mode 100644 index 0000000..7c14753 --- a/dev/null +++ b/frontend/beta/js/YUI-extensions/widgets/Toolbar.js @@ -0,0 +1,296 @@ +/** + * @class YAHOO.ext.Toolbar + * Basic Toolbar used by the Grid to create the paging toolbar. This class is reusable but functionality + * is limited. Look for more functionality in a future version. + * @constructor + * @param {String/HTMLElement/Element} container + * @param {Array} buttons (optional) array of button configs or elements to add + */ + YAHOO.ext.Toolbar = function(container, buttons){ + this.el = getEl(container, true); + var div = document.createElement('div'); + div.className = 'ytoolbar'; + var tb = document.createElement('table'); + tb.border = 0; + tb.cellPadding = 0; + tb.cellSpacing = 0; + div.appendChild(tb); + var tbody = document.createElement('tbody'); + tb.appendChild(tbody); + var tr = document.createElement('tr'); + tbody.appendChild(tr); + this.el.dom.appendChild(div); + this.tr = tr; + if(buttons){ + this.add.apply(this, buttons); + } +}; + +YAHOO.ext.Toolbar.prototype = { + /** + * Adds element(s) to the toolbar - this function takes a variable number of + * arguments of mixed type and adds them to the toolbar... + * + * @param {Mixed} arg If arg is a ToolbarButton, it is added. If arg is a string, it is wrapped + * in a ytb-text element and added unless the text is "separator" in which case a separator + * is added. Otherwise, it is assumed the element is an HTMLElement and it is added directly. + */ + add : function(){ + for(var i = 0; i < arguments.length; i++){ + var el = arguments[i]; + var td = document.createElement('td'); + this.tr.appendChild(td); + if(el instanceof YAHOO.ext.ToolbarButton){ + el.init(td); + }else if(el instanceof Array){ + this.addButton(el); + }else if(typeof el == 'string'){ + var span = document.createElement('span'); + if(el == 'separator'){ + span.className = 'ytb-sep'; + }else{ + span.innerHTML = el; + span.className = 'ytb-text'; + } + td.appendChild(span); + }else if(typeof el == 'object' && el.nodeType){ // must be element? + td.appendChild(el); + }else if(typeof el == 'object'){ // must be button config? + this.addButton(el); + } + } + }, + + /** + * Returns the element for this toolbar + * @return {YAHOO.ext.Element} + */ + getEl : function(){ + return this.el; + }, + + /** + * Adds a separator + */ + addSeparator : function(){ + var td = document.createElement('td'); + this.tr.appendChild(td); + var span = document.createElement('span'); + span.className = 'ytb-sep'; + td.appendChild(span); + }, + + /** + * Add a button (or buttons), see {@link YAHOO.ext.ToolbarButton} for more info on the config + * @param {Object/Array} config A button config or array of configs + * @return {YAHOO.ext.ToolbarButton/Array} + */ + addButton : function(config){ + if(config instanceof Array){ + var buttons = []; + for(var i = 0, len = config.length; i < len; i++) { + buttons.push(this.addButton(config[i])); + } + return buttons; + } + var b = config; + if(!(config instanceof YAHOO.ext.ToolbarButton)){ + b = new YAHOO.ext.ToolbarButton(config); + } + this.add(b); + return b; + }, + + /** + * Adds text to the toolbar + * @param {String} text The text to add + * @return {HTMLElement} The span element created which you can use to update the text. + */ + addText : function(text){ + var td = document.createElement('td'); + this.tr.appendChild(td); + var span = document.createElement('span'); + span.className = 'ytb-text'; + span.innerHTML = text; + td.appendChild(span); + return span; + }, + + /** + * Inserts a button (or buttons) at the specified index + * @param {Number} index The index where the buttons are to be inserted + * @param {Object/Array} config A button config or array of configs + * @return {YAHOO.ext.ToolbarButton/Array} + */ + insertButton : function(index, config){ + if(config instanceof Array){ + var buttons = []; + for(var i = 0, len = config.length; i < len; i++) { + buttons.push(this.insertButton(index + i, config[i])); + } + return buttons; + } + var b = new YAHOO.ext.ToolbarButton(config); + var td = document.createElement('td'); + var nextSibling = this.tr.childNodes[index]; + if (nextSibling) + this.tr.insertBefore(td, nextSibling); + else + this.tr.appendChild(td); + b.init(td); + return b; + } +}; + +/** + * @class YAHOO.ext.ToolbarButton + * A toolbar button. The config has the following options: + *