/**
 * @namespace
 * This namespace holds common functions, classes, constants, and utilities used on the GridMob site.
 * 
 */
gm.util = {  
	/**
	 * Returns true if a function false otherwise
	 * @param {Object} it
	 * @return {boolean}
	 */
	isFunction : function(it) {
		if (!isSafari || !(typeof it == "function" && it == "[object NodeList]")) {
			return typeof it == "function" || it instanceof Function;
		} else {	
			return false;
		}
	},

	/**
	 * Returns true if it is a JavaScript object (or an Array, a Function or null)
	 * @param {Object} it
	 * @return {boolean}
	 */
	isObject : function(it){
		return it !== undefined && (it === null || typeof it == "object" || this.isArray(it) || this.isFunction(it));
	},

	/**
	 * Checks if the passed in element is an array.
	 * @param {Object} el
	 * @return {boolean}
	 */
	isArray : function(el) {
		return el && el instanceof Array || typeof el == "array";
	},

	/**
	 * Checks if the passed in element has array properties array.  Looser description of an array than checking the typeof.
	 * @param {Object} el
	 * @return {boolean}
	 */
	isArrayLike : function(el) {
		return (el!=null && typeof(el)=="object" && typeof(el.length)=="number" && (el.length==0 || typeof((el[0])) != "undefined"));	
	},

	/**
	 * Returns true if string
	 * @param {Object} el 
	 * @return {boolean}
	 */
	isString : function(el) {
		return typeof el == "string" || el instanceof String; // Boolean
	},

	/**
	 * Checks if the passed in element is an html element
	 * 
	 * @method isHTMLElement
	 * @param  {Object}       ele
	 * @param  {String|Array} tagname  (optional) the valid tagname/s for the element to be
	 * @return {boolean}               true if an html element matching the tagname/s, false otherwise
	 */
	isHTMLElement : function(el, tagname) {
		// if the element is not an 
		if (el == null || typeof el != "object" || el.nodeName == null) {
			return false;
		}

		// if no tagname we are not comparing to anything, so return true
		if (!tagname) {return true;}

		// if the tagname is a string compare it to the elements nodename
		if (typeof tagname == "string" && tagname.toLowerCase() ==  el.nodeName.toLowerCase()) {
			return true;
		}
			
		// if we have an array of elements we want to compare to do a recursive call with the array contents
		if (this.isArray(tagname)) {
			for (var i = 0; i < tagname.length; i++) {
				// recursive call with the element to determine the contents of the array
				if (this.isHTMLElement(el,tagname[i])) {
					return true;
				}
			}
		}

		return false;
	},
    
    /**
     * Unescapes a value containing HTML markup.
     *   
     * @param {String} value  escaped value
     * @return the unescaped value
     */
    unescapeHTML : function(value) {
        return value.replace(/&([^;]+);/g, function(match, match1) { 
            switch (match1) {
                case 'lt':
                    return '<';
                case 'gt':
                    return '>';
                case 'amp':
                    return '&';
                case 'quot':
                    return '"';
                default:
                    // Assume char code.
                    if (match1.charAt(0) == '#') { match1 = match1.substring(1, match1.length); }
                    return String.fromCharCode(match1);
            }
        });
    },
    
	/**
	 * 
	 * @param {Object} value
	 */
    isEmptyString : function(value) {
        return (!(value) || (value.length == 0));
    },

	/**
	 * Function will trim spaces off a string
	 * @param {String} s  the string we want to trim
	 * @return {String}
	 */
    trim : function(s) {
		return s.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
	},
	
	/**
	 * 
	 * @param {Object} value
	 */
    capitalize : function(value) {
        return value.replace(/\w\S*/g, function(word) { return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase(); });
    },
	
	
	/**
	 * Function assists in setting of transparent pngs.  accounts for ie6 mainly
	 * @param {String|HTMLElement} img   the handle to the image
	 * @param {String} src               the src of the image
	 * @param {String} mode              (optional) the mode of the image "scale" or "noscale" defaults to "noscale"
	 */
	setImgToPng : function(img,src,mode) {
		img = $(img);
		mode = mode || "noscale";
        if (isIE && isIE < 7) {
			img.src = "/cdn/beta/images/px.gif";
			img.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src + "', sizingMethod='" + mode + "')";
        } else {
			img.src = src;
        }
	},

	/**
	 * Locates the first index of the provided value in the passed array. If the value is not found, -1 is returned.
	 * For details on this method, see: http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:indexOf
	 * @param {Array}   array
	 * @param {Object}  value
	 * @param {Integer} fromIndex  (optional) index to start searching from
	 * @param {Boolean} findLast   (optional) will return the last occurance if true is passed in
	 * @retur {int} the index of the entry in the array, or -1 if not found in the array
	 */
	indexOf : function(array, value,fromIndex,findLast){
		var i = 0, step = 1, end = array.length;
		if(findLast){
			i = end - 1;
			step = end = -1;
		}
		for(i = fromIndex || i; i != end; i += step){
			if(array[i] == value){ return i; }
		}
		return -1;
	},
	
    /**
     * Opens a window according to the given parameters.
     * 
     * @param {Object} fPage    window url
     * @param {Object} fName    window name
     * @param {Object} fWidth   window width
     * @param {Object} fHeight  window height
     * @param {Object} fScroll  whether or not to show a scroll bar
     */
    newWindow : function(fPage, fName, fWidth, fHeight, fScroll) {   
        fScroll = "yes";
		var winl = 10, wint = 10;
        var winprops = 'height='+fHeight+',width='+fWidth+',top='+wint+',left='+winl+',scrollbars='+fScroll+',directories=no,resizable=yes';
        win = window.open(fPage, fName, winprops); 
        if (parseInt(navigator.appVersion) >= 4) {
            win.focus();
        }  
        return win;
	},

	/**
	 * Dojo extends the functionality of nodes to have an addEventListener to IE.  Since we don't use some of 
	 * dojo's codebase, we have to have custom code to attach events ... that is called from their stuff 
     * @param {HTMLElement} el      the element to bind the handler to
     * @param {string}      eType   the type of event handler
     * @param {function}    fn      the callback to invoke
	 */
	addEventListener : function(el,eType,fn) {
	    if (window.addEventListener) {
            el.addEventListener(eType, fn,false);
	    } else if (window.attachEvent) {
            el.attachEvent("on" + eType, fn);
	    }
	},
	
	/**
	 * Dojo extends the functionality of nodes to have an addEventListener to IE.  Since we only use some of 
	 * dojo's codebase, we have to have custom code to attach events ... that is called from their stuff 
	 * @param {Object} el
	 * @param {Object} eType
	 * @param {Object} fn
	 */
	removeEventListener : function(el,eType,fn) {
        if (window.removeEventListener) {
            el.removeEventListener(eType, fn,false);
        } else if (window.detachEvent) {
            el.detachEvent("on" + eType, fn);
        }
	},
	
	/**
	 * Will set the style of the passed in element
	 * @param {String|HTMLNode} el       the element we are styling
	 * @param {String}          property the style property name
	 * @param {String|int}      val      the property value
	 */
	setStyle :  function(el,property,val) {
		el = $(el);
	    if (isIE) {
            switch (property) {
                case 'opacity':
                    if (this.isString(el.style.filter)) { // in case not appended
                        el.style.filter = 'alpha(opacity=' + val * 100 + ')';

                        if (!el.currentStyle || !el.currentStyle.hasLayout) {
                            el.style.zoom = 1; // when no layout or cant tell
                        }
                    }
                    break;
                case 'float':
                    property = 'styleFloat';
                default:
                    el.style[property] = val;
            }
	    } else {
            if (property == 'float') {
                property = 'cssFloat';
            }
            el.style[property] = val;
	    }
	},

	/**
	 * Returns whether or not the specified classes are a portion of the class list currently applied to the node. 
	 * @param  {DomNode|String} node
	 * @param  {String}         classStr
	 * @return {Boolean}
	 */
	hasClass : function(node,classStr){
		return ((" " + $(node).className + " ").indexOf(" " + this.trim(classStr) + " ") >= 0);  
	},
	
	/**
	 * Adds the specified classes to the end of the class list on the passed node.
	 * @param  {DomNode|String} node     the dom node or it's identifier that we want to add the class to
	 * @param  {String}         classStr the class that will be added
	 */
	addClass : function(node,classStr){
		node = $(node);
		var cls = node.className;
		if((" "+cls+" ").indexOf(" " + this.trim(classStr) + " ") < 0){
			node.className = cls + (cls ? ' ' : '') + classStr;
		}
	},
	
	/**
	 * Removes the specified classes from node.
	 * @param  {DomNode|String} node     the dom node or it's identifier that we want to add the class to
	 * @param  {String}         classStr the class that will be removed
	 */
	removeClass : function(node,classStr){
		node = $(node);
		var t = this.trim((" " + node.className + " ").replace(" " + classStr + " ", " "));
		if(node.className != t){ node.className = t; }
	},

	/**
	 * Adds a class to node if not present, or removes if present. Pass a boolean condition if you want 
	 * to explicitly add or remove.
	 * @param  {DomNode|String} node      the dom node or it's identifier
	 * @param  {String}         classStr  the class we will be toggling
	 * @param  {Boolean}        condition (optional) If passed, true means to add the class, false means to remove.
	 */
	toggleClass : function(/*DomNode|String*/node, /*String*/classStr, /*Boolean?*/condition){
		if(condition === undefined){
			condition = !this.hasClass(node, classStr);
		}
		this[condition ? "addClass" : "removeClass"](node, classStr);
	},

	/**
	 * Function will check if the provided xy point is outside of the bounds of the provided element.
	 * @method pointInBounds
	 * @param  {Array}   pXY     {x,y} position of point
	 * @param  {Object}  el      element that we are checking the region for
	 * @param  {int}     margin  the allowable margin that we will still return that the xy is inside the bounds
	 * @return {boolean}         true if the point is within the element bounds, false if outside of the element bounds
	 */
	pointInBounds : function (p,el,margin) {
		margin = margin || 0;
		var elc = this.coords(el,true);
		return (elc.x - margin < p.x && p.x < elc.x + elc.w + margin &&  elc.y - margin < p.y && p.y < elc.y + elc.h + margin);
	},
	
	
	/**
	 * Function gets the xy of the mouse from the passed in event
	 * @param {Object} ev
	 */
	getXYFromEvent : function (ev) {
		// get where the mousdown event occurred
		if (isIE) {
			// grab the x-y pos.s if browser is IE
			return {x: window.event.clientX + this.getDocumentScrollLeft(),y:window.event.clientY + this.getDocumentScrollTop()};

		} else {
			// grab the x-y pos.s if browser is other
			return {x: ev.pageX,y:ev.pageY};
		}
	},

	/**
	 * This function return the bounding box of an element relative to the page.
	 *
	 * @method coords
	 * @param  {object}   el   The element we want the bounding box for
	 * @return {object}         The array of points 
	 */
	coords : function (el) {
		var xy = this.getXY(el);
		return (!xy) ? false : {
			l : xy[0],
			t : xy[1],
			x : xy[0],
			y : xy[1],
			w : el.offsetWidth,
			h : el.offsetHeight
		};
	},

	/**
	 * Function will return the xy position of the passed in element
	 * @param {HTMLElement} el 
	 * @return {Array}         an array that holds the [x,y] position of the element
	 */
	getXY : function(el) {
		var pd,box,rootNode,pos,parentNode,accountForBody,regTest = null;
        if (isIE) {
            box = el.getBoundingClientRect();
            rootNode = el.ownerDocument;
            return [box.left + this.getDocumentScrollLeft(rootNode), box.top + this.getDocumentScrollTop(rootNode)];
        } else {
            pos = [el.offsetLeft, el.offsetTop];
            parentNode = el.offsetParent;

            // safari: subtract body offsets if el is abs (or any offsetParent), unless body is offsetParent
            accountForBody = (isSafari && el.style && el.style.position == "absolute" && el.offsetParent == el.ownerDocument.body);

			// loop through absolute positioned parents and get their offsets to add to the root xy
            if (parentNode != el) {
                while (parentNode) {
                    pos[0] += parentNode.offsetLeft;
                    pos[1] += parentNode.offsetTop;
                    if (!accountForBody && isSafari && el.style && el.style.position == "absolute") { 
                        accountForBody = true;
                    }
                    parentNode = parentNode.offsetParent;
                }
            }

			//safari doubles in this case
            if (accountForBody) { 
                pos[0] -= el.ownerDocument.body.offsetLeft;
                pos[1] -= el.ownerDocument.body.offsetTop;
            } 
            parentNode = el.parentNode;
			pd = "";
			regTest = /^(?:inline|table-row)$/i;
            // account for any scrolled ancestors
            while (parentNode.tagName && parentNode.tagName != "body" && parentNode.tagName != "html") {
                if (parentNode.scrollTop || parentNode.scrollLeft) {
                    // work around opera inline/table scrollLeft/Top bug (false reports offset as scroll)
					pd = (parentNode.style && parentNode.style.display) ? parentNode.style.display : "";
                    if (regTest.test(pd) && (!isOpera || (parentNode.style && parentNode.style.overflow !== 'visible'))) { 
                        pos[0] -= parentNode.scrollLeft;
                        pos[1] -= parentNode.scrollTop;
                    }
                }
                parentNode = parentNode.parentNode; 
            }
            return pos;
        }
	},

    /**
     * Returns the left scroll value of the document 
     * @param {HTMLDocument} document (optional) The document to get the scroll value of
     * @return {Int}  The amount that the document is scrolled to the left
     */
    getDocumentScrollLeft: function(doc) {
        doc = doc || document;
        return Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft);
    }, 

    /**
     * Returns the top scroll value of the document 
     * @param {HTMLDocument} document (optional) The document to get the scroll value of
     * @return {Int}  The amount that the document is scrolled to the top
     */
    getDocumentScrollTop: function(doc) {
        doc = doc || document;
        return Math.max(doc.documentElement.scrollTop, doc.body.scrollTop);
    },

	/**
	 * This function will tell you if the element in question will overlap the edges of the screen when 
	 * displayed at the coordinates provided.
	 *
	 * @method getPageOverlap
	 * @param  {DomNode}  el        The element we want the bounding box
	 * @param  {Object}   p         (optional) The x/y coordinates we will show the element
	 * @return {object}             Static object containing the amount the object overlaps
	 *                              the edges of the document.
	 */
	getPageOverlap : function (el,p) {
	    var v = this.getDocumentDimensions();
		var elc = this.coords(el, true);
	
	    // if position is not provided use the position of the element provided
	    if (p.x === null || p.y === null) {
	        p.x = elc.x;
	        p.y = elc.y;
	    }
	
	    // check if the element overlaps the edges
	    overlap = {
	        overTop : (p.y < v.t) ? v.t - p.y : 0,
	        overBottom :(p.y + elc.h > v.vh + v.t) ? (p.y + elc.h) - (v.vh + v.t) : 0,
	        overLeft : (p.x < v.l) ? v.l - p.x : 0,
	        overRight :(p.x + elc.w > v.vw + v.l) ? (p.x + elc.w) - (v.vw + v.l) : 0
	    };
	
	    return overlap;
	},
	    
	/**
	 * Get's all the different dimensions for the document
	 * @return {Object} object containing the values of the viewport width and height (obj.vw,obj.vh), the 
	 *                  pages scroll amount (obj.l,obj.t), and the pages width and height (obj.w,obj.h)
	 */
	getDocumentDimensions : function() {
	    var d = {};
		var dde = document.documentElement;
		var b = document.body;
	
	    // viewport
	    d.vw = (dde && dde.clientWidth)  ? dde.clientWidth  : window.innerWidth || self.innerWidth || b.clientWidth; 
	    d.vh = (dde && dde.clientHeight) ? dde.clientHeight : window.innerHeight || self.innerHeight || b.clientHeight; 
	
	    // scroll
	    d.l = (dde && dde.scrollLeft) ? dde.scrollLeft : window.pageXOffset || self.pageXOffset || b.scrollLeft; 
	    d.t = (dde && dde.scrollTop)  ? dde.scrollTop  : window.pageYOffset || self.pageYOffset || b.scrollTop; 
	
	    // page
	    d.w = (dde && dde.scrollWidth)  ? dde.scrollWidth  : (b.scrollWidth > b.offsetWidth) ? b.scrollWidth : b.offsetWidth; 
	    d.h = (dde && dde.scrollHeight) ? dde.scrollHeight : (b.scrollHeight > b.offsetHeight) ? b.scrollHeight : b.offsetHeight;
	    return d;
	},

	/**
	 * IE and FF Mac have issues with some window elements ignoring zIndexes.  This method appends a dom 
	 * node specific for those situations.  Specifically using an iframe to prevent IE5+ select boxes from 
	 * floating and using a transparent scrollable div to prevent Firefox2 Mac from having scrollbars float 
	 * to the top.
	 * 
	 * @param {Object} syncEl the dom node that we want to sync our dom hack to
	 */
	addBrowserHackDomNode : function(syncEl) {
		// init vars
		var domEl,listener = null;
	
		// get the dom node based on the browser
		domEl = this.getBrowserHackDomNode(syncEl);
		document.body.appendChild(domEl);
	
		// for ff mac listen to window events and reset overlays
		if (isFF && isMac) {
			listener = this.addFFMacListeners(domEl);
		}
		return {domEl:domEl,listener:listener};
	},
	
	
	/**
	 * Removes hack nodes that have been added
	 * @param {Object} syncEl the dom node that we want to sync our dom hack to
	 */
	removeBrowserHackDomNode : function(hackObject) {
		if (hackObject.domEl) {
			try {
				document.body.removeChild(hackObject.domEl);
			} catch (e) {}
		}
	
		// for ff mac remove listener on window events
		if (isFF && iMac && hackObject.listener) {
			this.removeFFMacListeners(hackObject.listener);
		}
	},
	
	/**
	 * Finds elements with a passed in className
	 * @param {String} className
	 * @param {String|HTMLElement} baseEl (optional) the element to look inside for the class.  if not present 
	 *                                    will default to the document body.
	 */
	getElementsByClassName : function(classname,baseEl) {
		if(!baseEl) {
			baseEl = document.getElementsByTagName("body")[0];
		} else {
			baseEl = $(baseEl);
		}
		var a = [];
		var re = new RegExp('\\b' + classname + '\\b');
		var els = baseEl.getElementsByTagName("*");
		for (var i = 0; i < els.length; i++) {
			if (re.test(els[i].className)) {
				a.push(els[i]);
			}
		}
		return a;
	},
	
	/**
	 * IE and FF Mac have issues with some window elements ignoring zIndexes.  This method returns a dom 
	 * node specific for those situations.  Specifically using an iframe to prevent IE5+ select boxes from 
	 * floating and using a transparent scrollable div to prevent Firefox2 Mac from having scrollbars float 
	 * to the top.
	 * 
	 * @param {Object}  syncEl  the dom node that we want to sync our dom hack to
	 */
	getBrowserHackDomNode : function(syncEl) {
	    var coords = this.coords(syncEl,true), hack = null;
	    // create an overflow div for firefox mac
	    if (isFF && isMac) {
	        hack = document.createElement("div");
	        hack.style.overflow = "auto";
	    }
	
	    // create an iframe for ie 6 and lower
	    if (isIE && isIE < 7) {
	        hack = document.createElement("iframe");
	        hack.src = "javascript:false;";
	        hack.style.filter = "alpha(opacity=0)";
	        hack.frameBorder = 0;
	    }
	
	    if (!hack) {
	        return;
	    }
	
	    // sync with passed in el
	    hack.style.width = coords.w + "px";
	    hack.style.height = coords.h + "px";
	    hack.style.position = "absolute";
	    hack.style.left = coords.x + "px";
	    hack.style.top = coords.y + "px";
	    hack.style.border = "none";
	    hack.style.padding = "0";
	    hack.style.margin = "0";
	    hack.style.zIndex = parseInt(syncEl.style.zIndex,10) - 2;
	    
	    return hack;            
	},

	/**
	 * Sets Focus listeners for the window to prevent scrollbars from showing through
	 * @param {DomNode} el
	 */
	addFFMacListeners : function(el) {
	    var listenerId = dojo.connect(window,'focus',function(){
	        try {
	            document.body.removeChild(el);
	            document.body.appendChild(el);
	        } catch (e) {}
	    });
	    return listenerId;
	},
	
	
	/**
	 * Removes Focus listeners for the window for the given el
	 * @param {String} listenerId  the id of the listener that we want to remove
	 */
	removeFFMacListeners : function(listenerId) {
	    dojo.disconnect(listenerId);
	},
	
	
	/**
	 * 
	 * @param {HTMLElement} el                the html element we are processing
	 * @param {String}      defaultText       the text that we are going to check
	 * @param {String}      defaultTextClass  (optional) the classname for the default text classs
	 */
	setInputDefault : function(el,defaultText,defaultTextClass) {
		defaultTextClass = defaultTextClass || "defaultText";
		if (el.value.replace(/^\s*/,'').replace(/\s*$/,'') == '') {
			el.value=defaultText;
			this.addClass(el,defaultTextClass);		
		}
	},
	
	/**
	 * 
	 * @param {HTMLElement} el                the html element we are processing
	 * @param {String}      defaultText       the text that we are going to check
	 * @param {String}      defaultTextClass  (optional) the classname for the default text classs
	 */
	clearInputDefault : function(el,defaultText,defaultTextClass) {
		defaultTextClass = defaultTextClass || "defaultText";
		if(el.value == defaultText){
			el.value='';
		}
		this.removeClass(el,defaultTextClass);
	},
	
	/**
	 * returns a list of url parameters and their values
	 */
	getURLParameters : function() {
		var loc = location.search.substring(1, location.search.length);
		var params = loc.split("&");
		var arr = [];

		for (i=0; i<params.length;i++) {
			arr[params[i].substring(0,params[i].indexOf('='))] = params[i].substring(params[i].indexOf('=')+1);
		}

		return arr;
	},
	
	/**
	 * Returns the value of the parameter if it exists
	 * @param  {String} param
	 * @return {String|Null}   the string value if it exists, null otherwise
	 * 
	 */
	getURLParameter : function(param) {
		return this.getURLParameters()[param] || null;
	}
};

/**
 * @namespace
 * Holds basic functions for classes 
 */
gm.util.Class = {
	/**
	 * Create a simple class.
	 * @method create
	 * @param {Object|Function} def   A object literal containing properties and methods to be applied
	 *                                to the class prototype. The 'initialize' method implements the
	 *                                constructor. A function is also acceptable as the definition,
	 *                                in which case the function becomes the initialize method with
	 *                                an empty prototype.
	 * @return {Function} The new class.
	 */
	create: function(def) {
		// Create base class.
		var props = typeof def == 'function' ? def.prototype : def || {};
		var cls = function() {
			// Apply instance variables (two levels, skipping functions).
			var iprops = arguments.callee.prototype;
			for (var prop in iprops) {
				if (typeof iprops[prop] == 'object' && !(iprops[prop] instanceof Array) && iprops[prop] != null) {
					var subprops = iprops[prop];
					this[prop] = {};
					for (var sprop in subprops) {
						this[prop][sprop] = subprops[sprop];
					}
				} else if (typeof iprops[prop] != 'function') {
					this[prop] = iprops[prop];
				}
			}
			// Initialize.
			this.initialize.apply(this, arguments);
		};
		// Apply prototype.
		for (var prop in props) {
			cls.prototype[prop] = props[prop];
		}
		// Apply initialization.
		if (!cls.prototype.initialize) {
			cls.prototype.initialize = typeof def == 'function' ? def : function() {};
		}
		return cls;
	},
	
	/**
	 * Extends a class/object with additional properties and methods.
	 * @method extend
	 * @param {Object|Function} dest        The destination class to extend. No existing properties or methods 
	 *                                      will be overwritten, all conflicts are skipped.
	 * @param {Array|Object|Function} src   A source object/function to copy properties and methods from. 
	 *                                      This can also be an array of sources.
	 * @param {boolean} overwriteConflicts  (optional) defaults to true.  false will prevent it from overwriting
	 *                                      base functionality.
	 * @return {Object|Function} The updated class or object.
	 */
	extend: function(dest, src, overwriteConflicts) {
		// Mix in source classes/objects.
		dest = typeof dest == 'function' ? dest.prototype : dest || {};
		if (!(src instanceof Array)) {
			src = [src];
		}
		for (var i = 0; i < src.length; i++) {
			var props = typeof src[i] == 'function' ? src[i].prototype : src[i] || {};
			for (var prop in props) {
				if (!dest[prop] || !!overwriteConflicts) {
					dest[prop] = props[prop];
				}
			}
		}
		return dest;
	},
	
	/**
	 * Inherits a new class from a parent class.
	 * @method inherit
	 * @param {Function} parent       The parent class to derive from. The constructor/initialize method 
	 *                                will automatically be called upon initializing the new class.
	 * @param {Object|Function} def   A object literal containing properties and methods to be applied
	 *                                to the class prototype. The 'initialize' method implements the
	 *                                constructor. A function is also acceptable as the definition,
	 *                                in which case the function becomes the 'initialize' method with
	 *                                an empty prototype.
	 * @return {Function} The new class.
	 */
	inherit: function(parent, def) {
		// Create destination class.
		var cls = gm.util.Class.create(def);
		gm.util.Class.extend(cls, parent);
		// Inherit parent class.
		var init = cls.prototype.initialize;
		var base = parent.prototype.initialize || parent;
		cls.prototype.initialize = function() {
			init.apply(this, arguments);
			base.apply(this, arguments);
		};
		// Set a reference to the base class.
		cls.prototype.base = parent;
		return cls;
	}
};


/**
 * @namespace 
 * This namespace contains common functions for exeucting ajax requests.
 * 
 * @static
 */
gm.util.AJAX = {

    /**
     * Synchronously requests the given url and returns the response.
     * 
     * @param {String} url     url
     * @param {Object} config  parameters used to control the request
     * @return the response
     */
    syncRequest : function(url, config) {
        // TODO: error handling
        if (!config) {
			config = { };
		}
        config.async = false;
        var req = this._createRequest(url, config);
        
        var xhr = req.xhr;
        xhr.send(null);
        
        var response = (xhr.responseXML) ? xhr.responseXML : (xhr.status == 200) ? xhr.responseText : '';
        if (req.timeout) { clearTimeout(req.timeout); }
        return response;   
    },
    
    /**
     * Asynchronously requests the given url and executes the given callback passing the response. 
     * 
     * @param {String} url         url
     * @param {Function} callback  callback that handles the request
     * @param {Object} config      parameters used to control the request
     * @return the response
     */
    asyncRequest : function(url, callback, config) {
        // TODO: error handling
        if (!config) {
			config = { };
		}
        config.async = true;
        var req = this._createRequest(url, config);

        var xhr = req.xhr;
        
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4) {
                var response = (xhr.status == 200) ? xhr.responseText : '';
                if (req.timeout) { clearTimeout(req.timeout); }
                callback(response);
            }
        };
        
        xhr.send(null);
    },
    
    /**
     * Sets up the request.
     * 
     * @param {Object} url      url
     * @param {Object} config  parameters used to control the request
     */
    _createRequest : function(url, config) {
    	// Create the request in a browser-independent manner.
        var xhr;
        
        if (window.XMLHttpRequest) {
            xhr = new XMLHttpRequest();
        } else if (window.ActiveXObject) {
	       	try {
	        	xhr = new ActiveXObject("Msxml2.XMLHTTP");
	      	} catch(e) {
	        	try {
	          		xhr = new ActiveXObject("Microsoft.XMLHTTP");
	        	} catch(e) {
	          		xhr = false;
	        	}
			}


        }

		if (!xhr) {
			return false;
		}
        
        xhr.open((config.method ? config.method.toUpperCase() : 'POST'), url, config.async);
        
        // Add a timeout handler if requested.
        var timeout;
        
        if (config.timeout) {
            timeout = setTimeout(function() { gm.util.AJAX._handleTimeout(xhr);}, config.timeout);
        }
        
        var req = {
            xhr : xhr,
            timeout : timeout
        };
        
        return req;
    },
    
    /**
     * Handles a request timeout.  
     */
    _handleTimeout : function(xhr) {
    	xhr.abort();
    }
};


/**
 * @namespace
 * This namespace holds utilities related to managing and building functionality in widgets
 */
gm.util.widget = {
	
	
	/**
	 * Will replace the provided key in the 
	 * @param {Object} txt
	 * @param {Object} key
	 * @param {Object} value
	 * @return {String}
	 */
	replace : function(txt,key,value) {
		// in the text passed in replace the keys with the string value passed in
		value = (value != null ? value : '').toString();
		return txt.replace(new RegExp("\\$\\{" + key + "\\}",'g'),value);
	},

	/**
	 * This function provides a way to provide our HTML a way to define event attachment inline without having
	 * to worry about the memory leaks associated with providing event attachment through onclick=""
	 * and similar html coding.  Since inline events typically have poor garbage collection and handle closures 
	 * poorly, we will use dojo to handle the event attachment of custom attributes.
	 * 
	 * The pattern we use here is to have a custom attribute in the html called "attachevent".  The attributes
	 * in attach event will provide keys to map to a function or set of functions as defined by the definitions
	 * file passed in.
	 * 
	 * This will effectively allow multistep event attachment with a small markup footprint, and circumvent the
	 * need for eval and other memory intensive procedures.
	 * 
	 * @example
	 * The syntax for the attribute is a set of comma delimited keys
	 * 
	 * <div attachevent="key1,key2">...</div>
	 * 
	 * The definitions object that is passed into the object will look like this:
	 * 
	 * object = {
	 *     key1 : {event:'click',fn:function() {gm.foo();}},
	 * 	   key2 : {event:'mousedown',fn:foo,context:gm}
	 * }
	 * 
	 * In the above example both key1 and key2 are examples of ways to call the same function.
	 * 
	 * @param {String|HTMLElement} baseEl  the handle to the element to start looking for events from
	 * @param {Object} mapObj              the object that maps the attachevent keys to functions
	 */
	attachEvents : function(baseEl,mapObj) {
		if(!baseEl) {
			return;
		} else if (gm.util.isString(baseEl)) {
			baseEl = $(baseEl);
		}

		var i,j,els,arr,st,def;

		// rotate through all nodes in el and attach events as needed
		els = baseEl.getElementsByTagName("*");

		// loop through all the elements in
		for (i = 0; i < els.length; i++) {
			st = els[i].getAttribute("gmattachevent");
			st = st;
			if (st) {
				st = gm.util.trim(st);
				arr = (st.indexOf(",") >= 0) ? st.split[","] : [st];
				for (j = 0; j < arr.length;j++) {
					def = mapObj[arr[j]];
					if (!def) {
						alert("No mapping for gmattachevent : " + arr[j]);
					}
					def.context = def.context || null;
					dojo.connect(els[i],def.event,def.context,def.fn);
				}
				els[i].removeAttribute("attachevent");
			}
		}
	},

	/**
	 * Format a template string with a list of replacement strings
	 * 
	 * @param {String} s        The string to run replacements on
	 * @param {Object} replace  An object literal of hashes to be replaced along with their values
	 * @return {String}         The resulting string
	 */
	template : function(s,replace) {
		for (var i in replace) {
			s = this.replace(s,i,replace[i]);
		}
		return s;
	},

	/**
	 * Function will search for a dom node with id "scriptToEval" and eval it.  Generic pattern to prevent us
	 * from having to make another request for json data associated with html.
	 */
	evalScript : function() {
		// eval the script in the scriptToEval div
		if ($('scriptToEval')) {
			// eval the passed in script and remove the node from the tree
			var s = $('scriptToEval');
			eval(s.innerHTML);
			s.parentNode.removeChild(s);
		}
	}
};
