/**
 * @namespace
 * This namespace holds common functions, classes, constants, and utilities used on the MapQuest site.
 * 
 */
m2.util = {  

	/**
	 * Function will atttempt to run a passed in function.  Primarily used to "pseudo-enable" javascript
	 * functions, so that they won't throw errors while the page is loading the script.  Inline javascript
	 * should use this function.
	 * 
	 * @param {Function} fn          the function that we want to call
	 * @param {Array}    paramsArr   the array of params to be passed to the function
	 */
	execute : function(fn,paramsArr) {
		try {
			call.fn(paramsArr);
		} catch (e) {
			setTimeout(100,function() {m2.util.execute(fn,paramsArr);});
		}
	},

	/**
	 * Returns true if a function false otherwise
	 * @param {Object} it
	 * @return {boolean}
	 */
	isFunction : function(it) {
		if (!m2.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 
	 * if typeof is array.
	 * @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 number
     * @param {Object} el
     * @return {boolean}
     */
    isNumber : function(el) {
		return typeof el == "number" || el instanceof Number; // Boolean
    },   

	/**
	 * 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;
	},

	/**
	 * Checks the passed in String to see if it is empty
	 * @param {String} value
	 */
    isEmptyString : function(value) {
        return (!(value) || (value.length == 0));
    },

    
    /**
     * 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);
            }
        });
    },
    
	/**
	 * 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*$/, '');
	},
	
	/**
	 * Will capitalize the first letter of the passed in word
	 * @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 = m2.$(img);
		mode = mode || "noscale"
        if (m2.isIE && m2.isIE < 7) {
			img.src = "/cdn/mqcommon/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 = fScroll || "no";
        var winl = wint = 10,
        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 && !m2.isIE) {
            win.focus();
        }  
        return win;
	},

	/**
	 * Function will play an audio file based on the passed in URL.  The player will not be visible and is hidden
	 * from view.
	 * 
	 * Code taken from MyAol signup.
	 * 
	 * NOTE:  This should probably be moved to a flash audio player with more functionality.  Until then, this
	 * is what you got
	 * 
	 * @param {Object} audioUrl
	 */
	playAudio: function(audioUrl) {
		var embedPlayerCode = '<EMBED SRC="'+audioUrl+'" HIDDEN="true" AUTOSTART="true" />',
		el = document.createElement("div");
		el.style.height = "1px";
		el.style.width = "1px";
		el.style.position = "absolute";
		el.style.left = "-100px";
		el.style.top = "0px";
		el.innerHTML = embedPlayerCode;
		document.body.appendChild(el);
	}, 

	/**
	 * Generic cross-browser add event listener function
     * @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);
	    }
	},
	
	/**
	 * Generic cross-browser remove event listener function
     * @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
	 */
	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 = m2.$(el);
	    if (m2.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 ((" " + m2.$(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 = m2.$(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 = m2.$(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);
	},
	
	/**
	 * Gets the text content of the given node and its children (ala DomNode.textContent).
	 * 
	 * @param {DomNode|String} node
	 */
	getText : function(node) {
		node = m2.$(node);
		// Try the DOM 3 textContent property (yay Firefox).
		if (typeof node.textContent == 'string') { return node.textContent; }
		
		// Try the MS innerText property (boo IE).
		if (typeof node.innerText == 'string') { return node.innerText; }
		
		// Finally, recurse over child nodes (boo Safari).
		function _getText(node) {
			var nodes = node.childNodes,
				nodeCount = nodes.length,
				text = '';
			for (var i = 0; i < nodeCount; ++i){
				node = nodes[i];
				if (node.nodeType == 3) {
					text += node.data;
				} else if (node.nodeType == 1){
					text += _getText(node);
				}
			}
			return text.replace(/\s+/g,' ');
		}

		return _getText(node);
	},

	/**
	 * Function will check if the provided xy point is outside of the bounds of the provided element.
	 * 
	 * Example usage:
	 * > to determine if an event click is in the bounds of an element.
	 * > to determine if you have moused off an 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 (m2.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) {
		el = m2.$(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 (m2.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 = (m2.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 && m2.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 && 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(),
		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 dde = document.documentElement,
		b = document.body;

		return {
		    // viewport
		    vw : (dde && dde.clientWidth)  ? dde.clientWidth  : window.innerWidth || self.innerWidth || b.clientWidth,
		    vh : (dde && dde.clientHeight) ? dde.clientHeight : window.innerHeight || self.innerHeight || b.clientHeight,

		    // scroll
		    l : (dde && dde.scrollLeft) ? dde.scrollLeft : window.pageXOffset || self.pageXOffset || b.scrollLeft,
		    t : (dde && dde.scrollTop)  ? dde.scrollTop  : window.pageYOffset || self.pageYOffset || b.scrollTop,

		    // page
		    w : (dde && dde.scrollWidth)  ? dde.scrollWidth  : (b.scrollWidth > b.offsetWidth) ? b.scrollWidth : b.offsetWidth,
		    h : (dde && dde.scrollHeight) ? dde.scrollHeight : (b.scrollHeight > b.offsetHeight) ? b.scrollHeight : b.offsetHeight
		};
	},

	/**
	 * 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) {
		// get the dom node based on the browser
		var domEl = this.getBrowserHackDomNode(syncEl);
		document.body.appendChild(domEl);

		return {
			domEl:domEl,
			// for ff mac listen to window events and reset overlays
			listener:(m2.isFF && m2.isMac) ? this.addFFMacListeners(domEl) : null
		};
	},

	/**
	 * 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 (m2.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 = m2.$(baseEl);
		}
		
		// Safari and Firefox have native support, use it if available
		if (baseEl.getElementsByClassName) {
		    return baseEl.getElementsByClassName(classname);
		}
		
		// otherwise use the browser agnostic, but slower version
		var cn, a = [];
		var re = new RegExp("(^|\\s)"+classname+"(\\s|$)");
		
		var els = baseEl.getElementsByTagName("*");
		
		for (var i = 0; i < els.length; i++) {
            try {
                cn=els[i].className;
              
                if (cn && re.test(cn)) {
                    a.push(els[i]);
                }
            } catch (e) 
            {
              // IE SUCKS.  Sometimes VML elements lose a sense of themselves and can
              // only be removed.  Even calling the className property will throw an
              // exception.  If this happens, just punt.
              // break;
            }
		}
		
		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 (m2.isFF && m2.isMac) {
	        hack = document.createElement("div");
	        hack.style.overflow = "auto";
	    }
	
	    // create an iframe for ie 6 and lower
	    if (m2.isIE && m2.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 = m2.util.Event.add(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) {
	    m2.util.Event.remove(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);
	},
	
	/**
	 * Selects the specified tab.  Assumes that the tabs are all within one parent and that
	 * setting the class to "s" for the tab will select it.
	 * 
	 * @param {String|HTMLNode} tab            the html tab element or its id
	 * @param {String|HTMLNode} contentPanel   the content panel to show or its id
	 */
	selectTab : function(tab,contentPanel) {
		tab = m2.$(tab);
		contentPanel = m2.$(contentPanel);
		
		if (!tab || !contentPanel) {
			console.log("no tab panel info")
			return;
		}		
		
		var tabNodes = tab.parentNode.childNodes,
		contentNodes = contentPanel.parentNode.childNodes,
		i=0;
		
		// deselect all the tabs
		for(;i < tabNodes.length;i++) {
			this.removeClass(tabNodes[i],"s");
		}
		
		// select our tab
		this.addClass(tab,"s");
		
		// hide all the tab panel content
		for(i=0;i < contentNodes.length;i++) {
			if (this.hasClass(contentNodes[i],("tabPanel"))) {
				contentNodes[i].style.display = "none";
			}
		}
		
		// show our content
		contentPanel.style.display = "block";
	},
	
	/**
	 * Function will convert an object to its json string
	 * 
	 * @param  {Object} obj  the object to convert
	 * @return {String}      the json text
	 */
	toJson : function(obj) {
		return MQA.IO.stringifyJSON(obj);
	},

	/**
	 * Function to convert the json string back into a javascript object
	 * 
	 * @param  {String} jsonText  the json text string
	 * @return {Object}           the object from the eval of the json string
	 */	
	fromJson : function(jsonText) {
		return MQA.IO.parseJSON(jsonText);
	},
	

	/**
	 * Evals JSON in a way that is *more* secure.
	 * 
	 * Borrowed From JQuery
	 * 
	 * @param  {Object} jsonText  the json string
	 * @return {Object}           the object from the eval of the json string
	 * @throws {SyntaxError}
	 */
	fromJsonSecure : function(jsonText) {
		var filtered = jsonText;
		filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@');
		filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
		filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, '');

		if (/^[\],:{}\s]*$/.test(filtered)) {
			return eval("(" + jsonText + ")");
		} else {
			throw new SyntaxError("Error parsing JSON, source is not valid.");
		}
	},

	
	/**
	 * Simple function to see if the toolkit is present.  Will log an error otherwise
	 * 
	 * @return {boolean} true if present, false otherwise
	 */
	hasToolkit : function() {
		if (typeof MQA != "undefined") {
			return true;
		} else {
			console.log("MQA Event Manager not defined");
			return false;
		}
	},
	
	/**
	 * Extend the target object with methods from the source.  The manner
	 * of the extension depends on the format of the key of each method.
	 * Generally, operations are defined by adding a suffix to the name.
	 * The suffix always starts with a dollar sign and then has an operation
	 * type:
	 * 
	 * - $Before: The method is invoked before the method on the base object.
	 *     If the method already has other "Before" modifiers, then this one
	 *     will be prepended to that list.</li>
	 * - $After: The method is invoked after the method on the base object.
	 *     If the method already has other "After" modifiers, then this one
	 *     will be appended to that list.</li>
	 * 
	 * Passes through to MQA.mixin in the lite toolkit.  See MQA.aop.js for 
	 * details on the implementation.
	 * 
	 * @param {Object} tgt  the object that we want to mix into
	 * @param {Object} src  the src object that we want to add to the target
	 */
	mixin : function(tgt, src) {
		if (this.hasToolkit() && MQA.mixin) {
			return MQA.mixin(tgt,src);
		}
	},
	
	/**
	 * Return a lightweight-closure function that when called will call
	 * tgtObj[tgtMethod].  Use in places such as elt.onload=function() { me.someMethod(); }
	 * to avoid capturing circular references in your context.  Any arguments
	 * following the second argument will be passed to the method.
	 * 
	 * Passes through to MQA.EventUtil.  See the mqa.event.js in the lite toolkit for details.
	 * 
	 * @param  {Object}   tgtObj      the object that we want to set the scope to
	 * @param  {Object}   tgtMethod   the method on the object that we want to execute
	 * @param  {Args}     boundArgs   additional arguments that we want to pass to the target method
	 * @return {Function}             the function with the specified scope
	 */
	hitch : function(tgtObj, tgtMethod /*, boundArgs */) {
		if (this.hasToolkit() && MQA.EventUtil) {
			return MQA.EventUtil.hitch.apply(null,arguments);
		}
	}
};

/**
 * @property {String} currentBaseUrl  the string representing the base url for the current page.
 */
m2.util.currentBaseUrl = location.protocol + "//" + location.host;

/**
 * TODO: should be somewhere else than util (e.g., m2.mymq)
 * @property {String}  the string representing url for secure requests (e.g., login)
 */	
m2.util.getSecureUrl = function() {
    var domain = ((typeof myMQHeaderConfig != "undefined") && (typeof myMQHeaderConfig.mqSecureDomain != "undefined")) ? myMQHeaderConfig.mqSecureDomain : null,
    url = (m2.util.isEmptyString(domain)) ?  this.currentBaseUrl : domain.replace(/\/$/, "");
    return url;
};

/**
 * TODO: should be somewhere else than util (e.g., m2.mymq)
 * @return {Object}  the current base url in a js object
 */
m2.util.getBaseUrlForHash = function() {
    var comm = ((typeof myMQHeaderConfig != "undefined") && (typeof myMQHeaderConfig.commFile != "undefined")) ? myMQHeaderConfig.commFile : null,
    url = (m2.util.isEmptyString(comm)) ? this.currentBaseUrl : comm;
    return { parentUrl: url  };
};

/**
 * TODO: should be somewhere else than util (e.g., m2.mymq)
 * @property {String}  the string representing url for displaying the welcome page
 */	
m2.util.getWelcomeUrl = function() {
    var url = ((typeof myMQHeaderConfig != "undefined") && (typeof myMQHeaderConfig.welcomeUrl != "undefined")) ? myMQHeaderConfig.welcomeUrl : null;
    return url;    
};

/**
 * @namespace
 * Holds basic functions for classes 
 */
m2.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 = m2.util.Class.create(def);
		m2.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 holds utilities related to managing and building functionality in widgets
 */
m2.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() {m2.foo();}},
	 * 	   key2 : {event:'mousedown',fn:foo,context:mq}
	 * }
	 * 
	 * 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 (m2.util.isString(baseEl)) {
			baseEl = m2.$(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("mqattachevent");
			st = st;
			if (st) {
				st = m2.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 mqattachevent : " + arr[j]);
					}
					def.context = def.context || null;
					m2.util.Event.add(els[i],def.event,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 (m2.$('scriptToEval')) {
			// eval the passed in script and remove the node from the tree
			var s = m2.$('scriptToEval');
			eval(s.innerHTML);
			s.parentNode.removeChild(s);
		}
	}
};
