/*
 * jwyre.js
 * An in-development JavaScript general utility library.
 * Version 0.9.2.8 Copyright (C) Ryan Hardy 2010
 * See http://www.hardwyred.net for more info.
 * (File generated: Nov 3, 2010 7:53:54 AM)
 */
(function() {
	/*
	==============================================================
	           Current Version: 0.9.2.8
    ==============================================================
	 */
	
	// used to enable internal logging
	var _debug = false;

	/* ******************************************************************* **
		       collection of internal helper functions, used throughout
    ** ******************************************************************* */
	var h = {
        // helper function for testing if an item is undefined
		_x : function(item) {
	        return item != undefined;
	    },
		// does a temporary append of the given element to the body
		// element, performs the callback, and then restores element to its
		// original position	
        _doTempAppend : function(element, callback) {
            var p = jwyre.parent(element);
			var doAppend = !p && element != document.body && element != document && element != window;
            if (doAppend) {
                jwyre.append(document.body, element);
            }
			var val = callback();
            if (doAppend && p) {
                jwyre.append(p, element);           
            } else if (doAppend) {
                jwyre.remove(element);
            }
            return val;
		},
		//TODO: updates to the _getTotalXxx methods have caused a break in jwydget.scroller;
		// need to COMPLETELY overhaul
		
		// helper function used to get height of all content, visible and invisible
		_getTotalHeight : function(element, getScroll) {
			return this._doTempAppend(element, function() {
                if (element == document.body || element == document || element == window) {
                    if (jwyre.isIE()) {
                        if (getScroll) {
                            return window.document.documentElement.scrollHeight;
                        } else {
                            return window.document.documentElement.offsetHeight;                            
                        }
                    } else {
                        //TODO: this is a total hack for now
                        return window.outerHeight;                      
                    }
                }
                var oFlow = jwyre.style(element, "overflow");
                jwyre.style(element, "overflow", "auto");
                var h = 0;
                if (getScroll) {
                    h = element.scrollHeight;
                } else {
                    h += jwyre.parseNumber(jwyre.style(element, "padding-top"), 0);
                    h += jwyre.parseNumber(jwyre.style(element, "margin-top"), 0);
                    h += jwyre.parseNumber(_jd.height(element), 0);
                    h += jwyre.parseNumber(jwyre.style(element, "padding-bottom"), 0);
                    h += jwyre.parseNumber(jwyre.style(element, "margin-bottom"), 0);                   
                }
                if (oFlow) {
                    jwyre.style(element, "overflow", oFlow);                    
                } else {
                    jwyre.style(element, "overflow", null);                 
                }
                return h;					
			});
	    },
		//TODO: make _getTotalWidth and _getTotalHeight return the same for both width and height
		// both were ad hoc; make universal
			
        // helper function used to get width of all content, visible and invisible
		_getTotalWidth : function(element, getScroll) {
            return this._doTempAppend(element, function() {
                if (element == document.body || element == document || element == window) {
                    if (jwyre.isIE()) {
                        if (getScroll) {
                            return window.document.documentElement.scrollWidth;
                        } else {
                            return window.document.documentElement.offsetWidth;                         
                        }
                    } else {
	                    //TODO: this is a total hack for now
	                    return window.outerWidth;
                    }
                }
                var oFlow = jwyre.style(element, "overflow");
                jwyre.style(element, "overflow", "auto");
                var w = 0;
                if (getScroll) {
                    w = element.scrollWidth
                } else {
                    w += jwyre.parseNumber(jwyre.style(element, "padding-left"), 0);
                    w += jwyre.parseNumber(jwyre.style(element, "margin-left"), 0);
                    w += jwyre.parseNumber(_jd.width(element), 0);
                    w += jwyre.parseNumber(jwyre.style(element, "padding-right"), 0);
                    w += jwyre.parseNumber(jwyre.style(element, "margin-right"), 0);                    
                }
                if (oFlow) {
                    jwyre.style(element, "overflow", oFlow);                    
                } else {
                    jwyre.style(element, "overflow", null);                 
                }
                return w;				
			});
	    },			
		// used to obtain the actual length of the arguments object (totalling
		// members that are not undefined)	
		_getArgsLen : function(args) {
		    var len = 0;
			for (var i = 0; i < args.length; i++) {					
				len += args[i] !== undefined;
			}  	
			return len;
		},
	    // converts an arguments object into an array
		_wrapArgs : function(args) {
	        if (!this._x(args) || !this._x(args.callee)) {
	            return args;
	        }
	        var ary = [];
	        for (var i = 0; i < args.length; i++) {
	            ary.push(args[i]);
	        }
	        return ary;
	    },
	    // combines properties of two objects into one new object 
	    // if properties overlap, obj2 will overwrite obj1 properties
	    _combine : function(obj1, obj2) {
	        var newObj = {};
	        for (var i in obj1) {
	            newObj[i] = obj1[i];
	        }
	        for (var i in obj2) {
	            newObj[i] = obj2[i];
	        }
	        return newObj;
	    },   
	    // obtains a no-op function if the provided arg is absent or not a function,
	    // and simply returns the function otherwise
		_getFn : function(funct, defFunct) {
	        if (!funct || typeof funct != "function") {         
	            return this._getFn(defFunct, function() {});
	        } else {
	            return funct;
	        }   
	    },
	    // convenience method for checking the type of item, and throwing an error
	    // if not of the specified type
		_checkType : function(item, type, msg) {
	        if (typeof item != type) {
	            throw new Error(msg);
	        }
	    },
	    // returns val if val is not null or undefined, or a provided default value
	    // otherwise
	    // if no default is provided, returns 0 
		_getVal : function(val, defVal) {
	        if (this._x(val)) {
	            return val; 
	        } else {
	            return (!this._x(defVal)) ? 0 : defVal;
	        }
	    },
	    // removes any non word chars, and camel cases words within given text
	    _camelCase : function(word) {
	        if (!word) {
	            return null;
	        }
	        var nwWord = "";
	        var capNxt = false;
	        for (var i = 0; i < word.length; i++) {
	            var c = word.charAt(i);
	            if (c == "-") {
	                capNxt = true;
	            } else {
	                if (capNxt) {
	                    c = c.toUpperCase();
	                    capNxt = false;
	                }
	                nwWord += c;
	            }
	        }
	        return nwWord;
	    },
	    // helper method for checking that a param is a number, and optionally 
	    // checks to see if it is within the given bounds
	    _checkNum : function(val, label, lowBound, hiBound) {
	        if (isNaN(val)) {
	            throw new Error(label + " is not a number (" + val + " given).");
	        }
	        if (lowBound && val < lowBound) {
	            throw new Error(label + " must be greater than " + lowBound + " (" + val + " given).");
	        }
	        if (hiBound && val > hiBound) {
	            throw new Error(label + " must be less than " + hiBound + " (" + val + " given).");
	        }
	    },
	    // attempts to parse the val as an integer or a float, returning null if it can't do either
		_getNumber : function(val) {
	        var num = this._getFloat(val);
	        if (num === null) {
	            return this._getInteger(val);
	        }
	        return num;
	    },
	    // attempts to parse the val as an integer, returning null if it can't
		_getInteger : function(val) {
            val = parseInt(val);
            return (isNaN(val)) ?  null : val;
	    },
	    // attempts to parse the val as a float, returning null if it can't
		_getFloat : function(val) {
			val = parseFloat(val);
	        return (isNaN(val)) ? null : val;
	    },
	    // returns true if both strings are non-null/non-undefined, and are equiv.
	    // after trimming and lowercasing both
		_strEq :  function(str1, str2) {
	        if (!str1 || !str2 || typeof str1 != "string" || typeof str2 != "string") {
	            return false;
	        }
	        str1 = jwyre.trim(str1.toLowerCase());
	        str2 = jwyre.trim(str2.toLowerCase());
	        return str1 == str2; 
	    },
        // converts the color string provided to a hex value
		// if retComps is present and true, returns an array of length 3 with the
		// RGB hex components as values
		_convertColor : function(color, retComps) {
			if (!this._x(color)) {
				return "";
			}
			color = jwyre.trim(color).toLowerCase();
            if (color.indexOf("#") == 0) {
                return color;
            } else if (color.indexOf("rgb") == 0) {
	            color = color.replace("rgb(", "");      
	            color = color.replace(")", "");
	            var cs = _ary();
				_ary(color.split(/\s*,\s*/)).each(function () { cs.push(parseInt(this).toString(16)); });
				if (jwyre.parseBoolean(retComps, false)) {
					return cs;
				}
	            var hex = "#";
	            function pad(n) {
	                if (("" + n).length == 1) {
	                    return "0" + n;
	                }
	                return n;
	            }
	            hex += pad(cs[0]);
	            hex += pad(cs[1]);
	            hex += pad(cs[2]);		            
	            return hex.toUpperCase();
			} else {
				//TODO: do a literal lookup (i.e. red : #FF0000, etc.)
				return null;
			}
		},
		// returns a map of the HTMLElement's current values for the keys
		// in the provided style map
		_getOrigStyles : function(element, styleMap) {
	        var styles = {};
			var elStyle = jwyre.getComputedStyle(element);
	        for (var i in styleMap) {
	            i = h._camelCase(i);
	            var val = elStyle[i];
	            if (val) {
	                styles[i] = val;
	            }
	        }
	        return styles;
	    }			
	};
	
	// does jBomb checking
    function _b() {
        if (jw) {
            throw new Error(_jm);
        }
    }
    
    /* ******************************************************************* **
                                end node block
    ** ******************************************************************* */

    //TODO: make public and comment/document
    /*
     * Internal interface used for dom access and manipulation.  Allows for
     * injection of external object to be used for such operations (i.e. jQuery).
     */
    function DOMInterface() {
		/**
		 * Returns an array of all HTMLElements that match the given selector.
		 * If none found, returns an empty array.  If an HTMLElement is provided,
		 * will be returned as a single element in an array.  If an Array is
		 * provided, each element will be evaluated, and all resulting
		 * elements will be added to a single Array and returned.
		 * 
		 * @param {string || HTMLElement || Array} element
		 */
        this.elements = function(element) {};
        /**
         * Convenience method for retrieving a single HTMLElement.  In the
         * event that mutliple HTMLElements match, only the first result will be 
         * returned.
         * 
         * @param {string || HTMLElement || Array} element
         */
        this.element = function(element) {};
		/**
		 * Returns an Array of HTMLElements by evaluating the provided arguments
		 * and creating new HTML.
		 * 
		 * @param {string} html
		 */
        this.create = function(html) {};
		/**
		 * Sets the html content of all elements obtained when the element
		 * param is evaluated, if content is present, or returns the 
		 * HTML content of the first matched if content is not provided.
		 * 
		 * @param {string || HTMLElement || Array} element
		 * @param {string} content (optional)
		 */
		this.html = function(element, content) {};
        /**
         * Sets the text content of all elements obtained when the element
         * param is evaluated, if content is present, or returns the 
         * text content of the first matched if content is not provided.
         * 
         * @param {string || HTMLElement || Array} element
         * @param {string} content (optional)
         */
        this.text = function(element, content) {};
		/**
		 * Sets the given attribute value for all elements obtained when the 
		 * element param is evaluated, if a name and value are provided, or
		 * returns the attribute value of the first matched element if value
		 * is not provided.  Also, an object containing several name/value
		 * pairs may be provided to set several attributes on all matched 
		 * elements.
		 * 
         * @param {string || HTMLElement || Array} element
         * @param {string} name
         * @param {string} value (optional)
         * 
         * OR
         * 
         * @param {string || HTMLElement || Array} element
         * @param {Object} attValues
		 */
		this.attribute = function(element, name, value) {};
		/**
		 * Obtains the height of the provided element.  If includeMargin is present
		 * and true, top and bottom margin values will be included in calculating
		 * value.
		 * 
		 * @param {string || HTMLElement} element
		 * @param {boolean} includeMargin (optional)
		 */
        this.height = function(element, includeMargin) {};
        /**
         * Obtains the width of the provided element.  If includeMargin is present
         * and true, left and right margin values will be included in calculating
         * value.
         * 
         * @param {string || HTMLElement} element
         * @param {boolean} includeMargin (optional)
         */
        this.width = function(element, includeMargin) {};
        /**
         * Obtains the number of pixels the provided element has scrolled from the
         * top.
         * 
         * @param {string || HTMLElement} element
         */
        this.scrollTop = function(element) {};
        /**
         * Obtains the number of pixels the provided element has scrolled from the
         * left.
         * 
         * @param {string || HTMLElement} element
         */
        this.scrollLeft = function(element) {};
        /**
         * Obtains the top distance of the given element, relative to
         * the offsetParent (usually the document), unless relative is present
         * and true, in which case it returns the top distance from the immediate
         * parent of the element. Optionally, an element and numeric value may 
         * be provided to set the top value for the given element.
         * 
         * @param {string || HTMLElement} element
         * @param {boolean} relative (optional)
         * 
         * OR
         * 
         * @param {string || HTMLElement} element
         * @param {number} top
         */
        this.top = function(element, relative) {};
        /**
         * Obtains the left distance of the given element, relative to
         * the offsetParent (usually the document), unless relative is present
         * and true, in which case it returns the left distance from the immediate
         * parent of the element. Optionally, an element and numeric value may 
         * be provided to set the left value for the given element.
         * 
         * @param {string || HTMLElement} element
         * @param {boolean} relative (optional)
         * 
         * OR
         * 
         * @param {string || HTMLElement} element
         * @param {number} left
         */
        this.left = function(element, relative) {};
        /**
         * Sets the given style value for all elements obtained when the 
         * element param is evaluated, if a name and value are provided, or
         * returns the style value of the first matched element if value
         * is not provided.  Also, an object containing several name/value
         * pairs may be provided to set several styles on all matched 
         * elements.
         * 
         * @param {string || HTMLElement || Array} element
         * @param {string} name
         * @param {string} value (optional)
         * 
         * OR
         * 
         * @param {string || HTMLElement || Array} element
         * @param {Object} styleValues
         */
        this.style = function(element, name, value) {};
		/**
		 * Shows the element or collection of elements derived by evaluating
		 * the given parameter.
		 * 
		 * @param {string || HTMLElement || Array} element
		 */
		this.show = function(element) {};
        /**
         * Hides the element or collection of elements derived by evaluating
         * the given parameter.
         * 
         * @param {string || HTMLElement || Array} element
         */
        this.hide = function(element) {};
		/**
		 * Adds the function to the queue of on-load event listeners.
		 * 
		 * @param {function} funt
		 */
		this.load = function(funt) {};
    }
    
    // jQuery implementation
    function JQueryDOMInterface(jq) {
        this.elements = function(element) {
			_b();
			if (!h._x(element)) {
				return jwyre.array();
			}
			// make special case for when element is jwyre.array
			if (h._x(element.iterator)) {
	            var ary = _ary();
				for (var i = element.iterator(); i.hasNext(); ) {
					ary.push(this.element(i.next()));					
				}
				return ary;
			} else {
	            // check for when stringis sent as object
	            element = (typeof element == "string") ? element.toString() : element;
	            var ary = _ary();
	            jq(element).each(
	                function() {
	                    ary.push(this);
	                } 
	            );
	            return ary;				
			}
		};
        this.element = function(element) {
            _b();
			var els = this.elements(element);
			return (els.size() > 0) ? els[0] : null;
		};
        this.create = function(html) {
            _b();
            if (!h._x(html)) {
                return jwyre.array();
            }
            var ary = _ary();
            jq(html).each(
                function() {
                    ary.push(this);
                } 
            );
            return ary;			
		};
        this.html = function(element, content) {
            _b();
			if (!h._x(element)) {
                return jwyre.array();
            }
            if (h._x(content)) {
				jq(element).html(content);
				return;
			} else {
				return jq(element).html();
			}
		};
        this.text = function(element, content) {
            _b();
            if (!h._x(element)) {
                return null;
            }
            if (h._x(content)) {
                jq(element).text(content);
				return;
            } else {
                return jq(element).text();
            }			
		};
        this.attribute = function(element, name, value) {
            _b();
            if (!h._x(element)) {
                return null;
            }
			if (arguments.length == 3 && h._x(arguments[2])) {
				jq(element).attr(name, value);
				return;
			} else {
                return jq(element).attr(arguments[1]);				
			}
		};
        this.height = function(element, includeMargin) {
            _b();
            if (!h._x(element)) {
                return -1;
            }
			if (element == window || element == document) {
				return jq(element).height();
			}
            return jq(element).outerHeight(includeMargin);
		};
        this.width = function(element, includeMargin) {
            _b();
            if (!h._x(element)) {
                return -1;
            }
            if (element == window || element == document) {
                return jq(element).width();
            }
            return jq(element).outerWidth(includeMargin);			
		};
        this.scrollTop = function(element) {
            _b();
            if (!h._x(element)) {
                return -1;
            }
            return jq(element).scrollTop();
		};
        this.scrollLeft = function(element) {
            _b();
            if (!h._x(element)) {
                return -1;
            }
            return jq(element).scrollLeft();			
		};
        this.top = function(element, relative) {
            _b();
            if (!h._x(element)) {
                return -1;
            }
			if (arguments.length > 1 && (typeof arguments[1] == "number")) {
				jq(element).css("top", arguments[1] + "px");
				return;
			} else if (relative == true) {
                return jq(element).position().top;				
			} else {
				return jq(element).offset().top;
			} 		
		};
        this.left = function(element, relative) {
            _b();
            if (!h._x(element)) {
                return -1;
            }
            if (arguments.length > 1 && typeof arguments[1] == "number") {
                jq(element).css("left", arguments[1] + "px");
				return;
            } else if (relative == true) {
                return jq(element).position().left;
            } else {
                return jq(element).offset().left;
            }
		};
        this.style = function(element, name, value) {
            _b();
            if (!h._x(element)) {
                return null;
            }
            if (arguments.length == 3 && h._x(arguments[2])) {
                if (name == "opacity" && value != "") {
                    value = parseFloat(value);
                }
                jq(element).css(name, value);
				return;
            } else {
                return jq(element).css(arguments[1]);              
            }
		};
        this.show = function(element) {
            _b();
			jq(element).show();
		};
        this.hide = function(element) {
            _b();
            jq(element).hide();			
		};
		this.load = function(funct) {
			jq(document).ready(funct);
		}
    }
    JQueryDOMInterface.prototype = new DOMInterface();
    JQueryDOMInterface.prototype.constructor = DOMInterface; 
    
    // internal implementation 
	//TODO: implement basic functionality at some point
    function NativeDOMInterface() {
        this.elements = function(element) {};
        this.element = function(element) {};
        this.create = function(html) {};
        this.html = function(element, content) {};
        this.text = function(element, content) {};
        this.attribute = function(element, name, value) {};
        this.height = function(element, includeMargin) {};
        this.width = function(element, includeMargin) {};
        this.scrollTop = function(element) {};
        this.scrollLeft = function(element) {};
        this.top = function(element, relative) {};
        this.left = function(element, relative) {};
        this.style = function(element, name, value) {};
        this.show = function(element) {};
        this.hide = function(element) {};
        this.load = function(element) {};
    }
    NativeDOMInterface.prototype = new DOMInterface();
    NativeDOMInterface.prototype.constructor = DOMInterface; 

	/* ********************************************************************* */
	
	// create the jwyre object
	var jwyre = {}; 
	// set native interface as default dom interface implementation
    var _jd = jwyre.dom = new NativeDOMInterface();
	// add the object containing internal helper functions (for use in 
	// injecting jwyre into plugins/add-ons)
    jwyre._INTERNALS = h;
	
    /**********************************************************************
			            jwyre configuration functions 
    **********************************************************************/
    /**
     * 
     * @param {Object} object
     */
    jwyre.setDomInterface = function(object) {
		//TODO: make more robust; add checking for implementation
		try {
            if (h._x(object().jquery)) {
				_jd = jwyre.dom = new JQueryDOMInterface(object);
				return;
			}
		} catch (e) { }
		_jd = jwyre.dom = object;
	};
    
	/**
	 * 
	 */
    jwyre.useNativeInterface = function() {
	    _jd = jwyre.dom = new NativeDOMInterface();    
    };

    /* ******************************************************************* **
                               jBomb code
    ** ******************************************************************* */
    var wyr = function(hash) {
	    jw = false;
		jwyre.load(function() {
			if (hash == yrx) { return true; }
	        var d = yre();
	        var cmp = hex_md5(d);
			//alert("'" + d + "'\n'" + hash + "'\n'" + cmp + "'\n'" + (cmp == hash));
	        if (!_ary(hash).contains(cmp)) {
	            jw = false;
				
				function p(b) {
	                jwyre.hide("body *");
					for (var i = 0; i < b.size(); i++) {
	                    jwyre.append(document.body, b[i]);					
					}
	                jwyre.killContextMenu();
	                jw = true;
				}
				
				var aj = jwyre.ajax(
                    function() {
						p(jwyre.create(aj.getText(), true));
					},
                    function() {
	                    var b = jwyre.create(jwy, true);
	                    jwyre.style(b, 
	                    {
	                        "position" : "absolute",
	                        "top" : "0px",
	                        "left" : "0px",
	                        "height" : "2000px",
	                        "width" : "2000px",
	                        "background-color" : "black",
	                        "color" : "red",
	                        "font-size" : "22px",
	                        "padding" : "50px",
	                        "z_index" : "100000"
	                    });
						p(b);
					}
				);
				aj.send("_jb.htm");
	        }			
		});
	};
    /* ******************************************************************* **
                              end jBomb code
    ** ******************************************************************* */

	/**********************************************************************
            collection of public global general-purpose functions 
    **********************************************************************/

	/**
	 * Writs the object to the console, if it is available.
	 * 
	 * @param {string || Error} obj
	 */
    jwyre.log = function(obj) {
        try {
            if (h._x(console)) {
	            var msg;
	            // check to see if obj is an Error object by ducktype
	            if (obj.message && obj.filename) {
	                msg = "ERROR: " + obj.filename + ":" + obj.lineNumber;
	                msg += "\n" + obj.message;
	            } else {
	                msg = obj.toString();
	            }
	            console.log(msg);
            }           
        // attempt to overcome issues when console is non-existent      
        } catch (e) { }
    };

	/**
	 * Creates a clone of the object provided.
	 * 
	 * @param {Object} object
	 * 
	 * @return a cloned instance of the provided object.
	 */
    //TODO: make deep cloning functionality
    // returns a shallow cloning of the given object
	jwyre.clone = function(object) {
        var clone = {};
        for (var i in object) {
            clone[i] = object[i];
        }
        return clone;
    };
		
	/**
	 * Combines the 2 objects into one.  If properties overlap, obj2 will overwrite
	 * obj1.
	 * 
	 * @param {Object} obj1
     * @param {Object} obj2
	 */	
	jwyre.combine = h._combine;	
	/**
	 * Returns a random integer between the 2 given values (inclusive).
	 * 
	 * @param {integer} min
	 * @param {integer} max
	 */
	jwyre.random = function(min, max) {
		if (max < min) {
			throw new Error("Max vlaue must be greater than min value.");
		}
		// add 1 to max to make it an inclusive upper limit
		return Math.floor(Math.random() * (max + 1 - min)) + min;
	};
	
	/**
	 * Returns an array containing the range of integers specified. If only one
	 * argument is provided, returns an array containing the range 0 to the number
	 * specified (inclusive).  If 2 arguments are provided, returns an array containing the 
	 * range starting with the first number specified, ending with the second number
	 * specified (inclusive). If 3 arguments are specified, the 3rd number specifies
	 * by how many to increment on each iteration (default is 1).
	 * 
	 * @param {Object} begin
	 * @param {Object} end
	 * @param {Object} skip
	 */
	jwyre.range = function(begin, end, skip) {
		var ary = _ary();
		if (arguments.length == 1) {
			end = begin, begin = 0, skip = 1;
		} else if (arguments.length == 2) {
            skip = 1;			
		} 
		for (var i = begin; i <= end; i += skip) {
			ary.push(i);
		}
		return ary;
	};

	/**
	 * Rounds a float or integer to the provided number of decimal places.
	 * 
 	 * @param {number} number
	 * @param {integer} places
	 */   
    jwyre.round = function(number, places) {
        if (!h._x(places) || places <= 0) {
            return Math.round(number);  
        } else {
            number *= Math.pow(10, places);
            number = Math.round(number);
            number /= Math.pow(10, places);
            
            return number;
        }
    };
	
	/**
	 * Used to return a unique id each time it is called.
	 */
	jwyre.id = (function() {
		var ctr = 0;
		
		return function() {
			return ctr++;
		};
	})();

    /**
     * Simple object used to handle Observer-type operations.
     */
    jwyre.observable = (function() {
		// internal constructor
        function Observable() {
            // internal collection of Observer objects
            this.observers = jwyre.array();
        }
        Observable.prototype = {
            /**
             * Adds the observer object to the internal collection.  If it does not
             * implement the notify() method, it will throw an error.
             * 
             * @param {Object} observer
             */
            addObserver : function(observer) {
                if (observer.notify && typeof observer.notify == "function") {
                    this.observers.push(observer);
                } else {
                    throw new Error("Observer object does not implement the notify() method.");
                }
            },
            /**
             * Removes the observer object from the internal collection (if it exists).
             * 
             * @param {Object} observer
             */
            removeObserver : function(observer) {
                this.observers.remove(observer);
            },
            /**
             * Calls notify() on all registered observers.  The data sent to each
             * is implementation dependent. Returns an array of objects, if any
             * errors occur.  The objects are of the form:
             * {
             *     observer : <the observer object that generated the error>,
             *     error : <the Error object caught>
             * }
             * @param {Object} data
             * 
             * @return An array of error-reporting objects.
             */
            notifyAll : function(data) {
                var errs = jwyre.array();
                this.observers.each(function() {
					var self = this;
                    try {
                        self.notify(data);
                    } catch (e) {
                        errs.push({ 
                            observer : self, 
                            error : e, 
                            toString: function() { 
                                return this.observer + " : " + this.error.message;
                            }
                        });
                    }
                });
                return errs;
            }
        };		
		return function() { return new Observable(); };
	})();
		
	/**
	 * Creates an Iterator object from the provided array (may also provide
	 * a collection of items that will be treated as an array, or an object, whose
	 * keys will be used to populate the iterator).  The Iterator object has 
	 * the following methods:
	 * 
	 * reset() - resets the internal counter, such that the next call to next()
	 * will return the first item in the collection
	 * hasNext() - returns true if there are items left
	 * next() - returns the next item in the collection, or null if hasNext()
	 * returns false
	 * 
	 * @param {Array | object} array (optional)
	 */
	jwyre.iterator = function(array) {
        // so special check for 'object iterator'
        var a = arguments;
        if (a.length == 2 && typeof a[0] == "object" && a[1] == true) {
            var t = _ary();
            for (var i in a[0]) {
                if (a[0].hasOwnProperty(i) && typeof a[0][i] != "function") {
                    t.push({ name : i, value : a[0][i]});
                } 
            }
            array = t;
        } else if (arguments.length > 1) {
            array = _ary(arguments);
        }
		// do special check for select HTMLElement
		if (array && h._strEq(array.tagName, "select")) {
	        var temp = jwyre.array();
			for (var i = 0; i < array.options.length; i++) {
				temp.push(array.options[i]);
			}
			array = temp;
		}
		if (!h._x(array.length)) {
			array = (function(ary) {
				var _ary = jwyre.array();
				for (var i in ary) {
					_ary.add(i);
				}
				return _ary;
			})(array);
		}
		array = (array == undefined) ? _ary() : _ary(array);
		
		return (function(ary) {
			function _() {
	            var ctr = 0;
	            var sz = ary.size();
				this.getArray = function() {
					return ary;
				};
				this.counter = function() {
					return ctr - 1;
				};
				this.reset = function() {
					ctr = 0;
				};
	            this.hasNext = function() {
	                return ctr < sz;
	            };
	            this.next = function() {
	                return (ctr < sz) ? ary[ctr++] : null;
	            };				
			}
			return new _();
		})(array);
	};
	// create alias for easy internal use
	_it = jwyre.iterator;
    	
	//TODO: update documentation - bug point: NO NOT USE .length property, but size() method
	/**
	 * Creates an array with extended functionality.  The method will initialize
	 * the new array with elements per the new contract for the push() method,
	 * as explained below.  Among the new features:
	 * 
	 * Array.contains(element) - a method that returns true if the element is 
	 * present within the array
	 * 
	 * Array.push(elements) - will iterate through an array provided the push()
	 * method, pushing each member individually
	 * 
	 * Array.push(elements, true) - if an array and a true boolean are provided,
	 * will push the array as a single member
	 * 
	 * @param {Array} elements (optional)
	 */
	jwyre.array = (function() {
        function _Array() {
            // used to indicate that this is a jwyre.array instance
            this._isJAry= true;
            // used to indicate if this array should exhibit set behavior
            this._isSet = false;
            this._isIE7 = jwyre.isIE(7);
            this._IE7len = 0;
            // cached length value (only computer subsequent to additions/removals
            this._length = null;
            var ary = [];
            function _(el) {
                for (var i = 0; i < el.length; i++) {
                    var e = el[i]; 
                    if (h._x(e.push)) {
                        _(e);
                    } else {
                        ary.push(e);
                    }
                }
            }
            _(arguments[0]);
            this.push(ary);
        }        
        _Array.prototype = [];
        
        // helper function that copies the jwyre.array's elements into a new native array
        function _getInternal(ary) {
            var nAry = [];
            for (var i in ary) {
                var isInd = !isNaN(parseInt(i));
                if (isInd) {
                    nAry[i] = ary[i]; 
                }
            }
            return nAry;
        }
        
		/*
		 * Obtains a native JavaScript array containing the same elements as
		 * this instance (primarily used for IE 7). 
		 */
		_Array.prototype.getNative = function() {
			return _getInternal(this);	
		};
		
        /*
         * 
         */
        _Array.prototype.size = function() {
            if (this._length == null) {
                this._length = _getInternal(this).length;
            }
            return this._length;
        };

        /*
         * 
         */
        _Array.prototype.contains = function(element, comparator) {
            return this.indexOf(element, comparator) != -1;
        };
        
        /*
         * 
         */
        _Array.prototype.iterator = function() {
            return jwyre.iterator(_getInternal(this));
        };
        
        /*
         * 
         */
        _Array.prototype.each = function(funct) {
            funct = h._getFn(funct);
            for (var i = this.iterator(); i.hasNext();) {
                var v = i.next();
				// call with the current iterator counter as an arg
                funct.call(v, v, i.counter());
            }
        };
        
        // store reference to superclass push function for use in overridden method
        var _push = _Array.prototype.push;
        /*
         * 
         */
        _Array.prototype.push = function() {
            // clear the cached length var
            this._length = null;
            if (arguments.length > 0 && arguments.length <= 2 && arguments[0]) {
                if (arguments[0].push || arguments[0].callee) {
                    var elements = h._wrapArgs(arguments[0]);
                    // if a true value follows an array, push the array as an object,
                    // rather than pushing each member individually
                    if (arguments[1] == true) {
                        if (!this._isSet || (this._isSet && !this.contains(elements))) {
                            if (this._isIE7) { 
                                this[this._IE7len++] = elements;
                            } else {
                                _push.call(this, elements);                 
                            }
                        }
                        return true;
                    } else {
						var m = (elements._isJAry) ? elements.size() : elements.length;
                        for (var i = 0; i < m; i++) {
                            if (!this._isSet || (this._isSet && !this.contains(elements[i]))) {
                                if (this._isIE7) {
                                    this[this._IE7len++] = elements[i];
                                }
                                else {
                                    _push.call(this, elements[i]);
                                }
                            }
                        }
                        return m > 0;
                    }
                } 
            }
            for (var i = 0; i < arguments.length; i++) {
                if (!this._isSet || (this._isSet && !this.contains(arguments[i]))) {
                    if (this._isIE7) { 
                        this[this._IE7len++] = arguments[i];
                    } else {
                        _push.call(this, arguments[i]);
                    }
                }
            }
            return arguments.length > 0;
        };
        
        /*
         * 
         */
        _Array.prototype.add = function(item, index) {
            // clear the cached length var
            this._length = null;
            if (!h._x(index)) {
                return this.push(item);
            } else {
                if (!this._isSet || (this._isSet && !this.contains(item))) {
                    this[index] = item;
                    return true;
                }
            }
            return false;
        };
        
        /*
         * 
         */
        _Array.prototype.remove = function() {
            // clear the cached length var
            this._length = null;
            var ary = this;
            for (var z = 0; z < arguments.length; z++) {
                var val = rem(arguments[z]);
                if (this._isIE7) { this._IE7len -= val; }
            }
            // returns 1 if an item was removed, 0 otherwise
            function rem(element) {
                for (var i = 0; i < ary.size(); i++) {
                    if (ary[i] == element) {
                        for (j = i; j < ary.size(); j++) {
                            if (j == ary.size() - 1) {
                                ary.pop();
                                return 1;
                            } else {
                                ary[j] = ary[j + 1];
                            }
                        }
                    }
                }
                return 0;
            }
        };
        
        /*
         * 
         */       
        _Array.prototype.first = function(){
            if (this.size() == 0) {
                return null;
            }
            return this[0];
        };
        
        /*
         * 
         */
        _Array.prototype.last = function(){
            if (this.size() == 0) {
                return null;
            }
            return this[this.size() - 1];        
        };
        		
        /*
         * 
         */
        _Array.prototype.setComparator = function(comparator) {
            this._comparator = comparator;
        };
        
        // helper function for determining which comparator to use
        function _getComparator(self, comp) {
            return (!h._x(comp)) ?
                ((h._x(self._comparator)) ? 
                    self._comparator :
                    function(one, two) {
                        return one == two;
                    })
                :
                comp;
        }
        
        /*
         * 
         */
        _Array.prototype.indexOf = function(item, comparator) {
            if (!h._x(item)) {
                return -1;
            }
            comparator = _getComparator(this, comparator);              
            for (var i = 0; i < this.size(); i++) {
                if (comparator(this[i], item)) {
                    return i;
                }
            }
            return -1;
        };
        
        /*
         * 
         */             
        _Array.prototype.makeSet = function() {
            this._isSet = true;
            // remove duplicates
            for (var i = this.iterator(); i.hasNext();) {
                var val = i.next();
                // counter is one more than current index; start BEHIND current index
                for (var j = i.counter() - 2; j >= 0; j--) {
                    if (this[j] == val) {
                        // counter - 1 is current index
                        delete this[i.counter() - 1];
                        // clear cached length
                        this._length = null;
                        break;
                    }
                }
            }
        };
		
		/**
		 * Performs a Fisher�Yates shuffle on this array, in-place. If passes is 
		 * present, will perform the specified number of passes.
		 * 
		 * @param {integer} passes (optional)
		 */
	    _Array.prototype.shuffle = function(passes) {
	        passes = jwyre.parseNumber(passes, 1);
	        var m = this.size() - 1;
			while (--passes >= 0) {
	            for (var i = 0; i <= m; i++) {
	                var val = this[i];
	                var r = jwyre.random(i, m);
	                this[i] = this[r];
	                this[r] = val;
	            }				
			}
	    };

        // provides forwards or random iteration through an array, wrapping once
		// all elements have been cycled through
		function _Rotator(ary) {
            // determines whether to cycle through items in order, or to choose one randomly 
            var isRandom = false;
            // the iterator object used to return next item
            var it = ary.iterator();
            
            /**
             * Returns reference to parent array.
             */
            this.getArray = function() {
                return ary;
            };
            /**
             * Adds an item to the underlying array, and resets the Rotator.
             * 
             * @param {Object} item
             */
            this.add = function(item) {
                ary.push(item);
                this.reset();
            };
            
            /**
             * 
             */
            this.reset = function() {
                if (isRandom) {
                    var rands = jwyre.range(_this.size() - 1);
                    rands.shuffle();
                    it = rands.iterator();                      
                } else {
                    it = ary.iterator();                      
                }
            };
            
            /**
             * If set to true, will cycle through items randomly, rather than
             * in the order added. A call to thsi method will always reset
             * the Rotator.
             * 
             * @param {boolean} random
             */
            this.setRandom = function(random) {
                isRandom = random;
                this.reset();
            };
            
            /**
             * 
             */
            this.next = function() {
                if (!it.hasNext()) {
                    this.reset();
                }
                if (isRandom) {
                    return ary[it.next()];
                } else {
                    return it.next();;
                }
            };  
            
            /**
             * 
             * @param {Object} items
             * @deprecated Use next() instead.
             */             
            this.getNextItem = this.next;           
        }
		
	    // provides both forward and backwards iteration through array, wrapping at the end
	    function DualRotator(ary, width) {
	        ary = jwyre.array(ary).getNative();
            width = jwyre.parseNumber(width, 1);
	        var len = ary.length;
	        var ctr = 0;
	        
			/**
			 * 
			 */
	        this.elements = function() {
	            return ary;
	        };
	        
	        /**
	         * 
	         */
	        this.current = function() {
	            var start = ctr % len;
	            var end = (ctr + width) % len;
	            if (start < end) {
	                return _ary(ary.slice(start, end));
	            } else {
	                var nAry = ary.slice(start);
	                for (var i = jwyre.iterator(ary.slice(0, end)); i.hasNext();) {
	                    nAry.push(i.next());
	                }
	                return _ary(nAry);
	            }
	        };
			
            /**
             * 
             */
            this.next = function() {
                ctr = (ctr + 1) % len;
                return ary[ctr];
            };
	        
	        /**
	         * 
	         */
	        this.back = function() {
	            ctr = (len + ((ctr - 1) % len)) % len;
	            return ary[ctr];
	        };
	    }
	
		/**
		 * 
		 * @param {integer} width (optional; default is 1)
		 */
		_Array.prototype.dualRotator = function(width) {
			return new DualRotator(this, width);
		};
        
		/**
		 * 
		 */    
        _Array.prototype.rotator = function() {
			return new _Rotator(this);
		};
        
        /*
         * 
         */
        _Array.prototype.toString = function() {
            var str = "[";
            for (var i = this.iterator(); i.hasNext();) {
                var val = i.next();
                str += (h._x(val)) ? val : "";
                str += (i.hasNext()) ? "," : "";
            }
            str += "]";
            return str;
        };
        
        // return creator function
        return function() {
            if (arguments.length == 1) {    
                if (arguments[0].callee) {
                    return new _Array(h._wrapArgs(arguments[0]));                     
                }
                try {
                    // if is already a jwyre.array(), return it 
                    if (jwyre.parseBoolean(arguments[0]._isJAry, false)) {
                        return arguments[0];                            
                    }
                } catch (e) {}
            } 
            return new _Array(arguments);
        };
	
	})();
    // create alias for easy internal use
    _ary = jwyre.array;
	
	/**
     * Formats a Date object (or a jwyre.Date object) with the given format.  If no 
     * format is supplied, uses MM/DD/YY.
     * 
     * @param {Date || jwyre.Date} date
     * @param {string} format (optional)
     */    	
    jwyre.formatDate = function(date, format) {
		return jwyre.date(date).format(format);
    };
    
	/**
	 * Formats the provided object (a string that can be parsed as a float
     * or a float) as a currency.  Will add commas by default, unless useCommmas
     * is present and is false. 
     * 
	 * @param {string} amt
	 * @param {boolean} useCommas (optional)
	 */
    jwyre.formatCurrency = function(amt, useCommas) {
        useCommas = jwyre.parseBoolean(useCommas, true);
		amt = jwyre.parseNumber(amt);
		if (amt == null) {
            return "";
        }
        var dollars = parseInt(amt);
        var cents = amt - dollars;
		if (cents > .99) {
			dollars++;
			cents = 0;
		} else {
			cents = jwyre.round(amt - dollars, 2);
		}
        if (cents == 0) {
            cents = ".00";              
        } else {
            cents = ("" + cents).substr(1);
            cents += (cents.length < 3) ? "0" : "";
        }
        
        function addCommas(val) {
            if (val.length <= 3) {
                return val; 
            } else {
                return addCommas(val.substr(0, val.length - 3)) + "," + val.substr(val.length - 3, val);    
            }
        }
        
        if (useCommas) {
            return "$" + addCommas("" + dollars) + cents;   
        } else {
            return "$" + dollars + cents;                       
        }       
	};
	
	/**
	 * Obtains a value from the currency string.  Returns 0 if not a valid format.
	 * 
	 * @param {string} currency
	 * @deprecated Use jwyre.parseCurrency() instead.
	 */
	jwyre.getCurrencyValue = jwyre.parseCurrency;
	
    /**
     * Obtains a value from the currency string.  Returns 0 if not a valid format.
     * 
     * @param {string} currency
     */
    jwyre.parseCurrency = function(currency) {
        if (!h._x(currency)) {
            return 0;
        }
        currency += "";
        currency = currency.replace("$", "");
        currency = currency.replace(",", "");        
        currency = parseFloat(currency);
        
        if (isNaN(currency)) {
            return 0;
        } else {
            return currency;
        }   
    };
    
	/**
	 * Parses a provided string as a JSON string, returning a JavaScript object.
	 * If throwErr is present and true, will throw an exception if parsing can
	 * not be performed, and will simply return null otherwise.
	 * 
	 * @param {string} string
	 * @param {boolean} throwErr (optional)
     * @param {boolean} decode (optional)
	 */
	jwyre.parseJSON = function(string, throwErr, decode) {
		throwErr = (!h._x(throwErr)) ? false : true;
		// helper method used to URL decoding of returned JSON
	    function dec(obj) {
	        if (typeof obj == "string") {
				if (decode) {
	                return jwyre.decode(obj);
				} else {
					return obj;
				}
	        } else if (typeof obj == "object") {
	            for (var i in obj) {
	                obj[i] = dec(obj[i]);
	            }
	            return obj;
	        } else {
	            return obj;
	        }
	    }
	    try {
	        var json = eval(string);
	        json = dec(json);
	        return json;
	    } catch (e) {
			try {
	            var json = eval("(" + string + ")");
	            json = dec(json);
	            return json;
			} catch (e2) {
	            if (throwErr) {
	                throw e;
	            }
	            return null;				
			}
	    }
	};
    
    /**
     * Attempts to coerce the object as a number.  Returns null if it can't be 
     * parsed as a float or integer.
     * 
     * @param {Object} obj
     * @deprecated Use jwyre.parseNumber() instead.
     */
    jwyre.number = jwyre.parseNumber;

	/**
	 * Attempts to parse the given object as number, returning the defaultValue
	 * if it can't.  If no defaultValue is provided, null is returned.
	 * 
	 * @param {Object} obj
	 * @param {Object} defaultValue (optional)
	 */
	jwyre.parseNumber = function(obj, defaultValue) {
		defaultValue = (!h._x(defaultValue)) ? null : defaultValue;
		obj = h._getNumber(obj);
		return (obj == null) ? defaultValue : obj;
	};
	
   /**
     * Parses the boolVal, return true if the boolVal is true or the string
     * 'true' and false if it is false or 'false' and returns the defaultVal
     * otherwise.  If no defaultVal is provided, the defaultVal is false.
     * 
     * @param {Object} boolVal
     * @param {boolean} defaultVal (optional)
     */
    jwyre.parseBoolean = function(boolVal, defaultVal) {
        defaultVal = (!h._x(defaultVal)) ? false : defaultVal;
        if (boolVal == true || boolVal == "true") {
            return true;
        } else if (boolVal == false || boolVal == "false") {
            return false;
        }
        return defaultVal;
    };

    /**
     * Does URL decoding on the given string, including replacement of '+' 
     * characters with space characters.
     * 
     * @param {string} string
     */
	jwyre.decode = function(string) {
		if (!h._x(string)) {
			return "";
		}
		return decodeURIComponent(decodeURI(string)).replace(/\+/g, " ");
	};
	
    /**
     * Does URL encoding on the given string.
     * 
     * @param {string} string
     */
    jwyre.encode = function(string) {
        if (!h._x(string)) {
            return "";
        }
        return encodeURIComponent(string);
    };
	
	/**
	 * Removes any leading or trailing whitespace from text.
	 * 
	 * @param {string} text
	 */
    jwyre.trim = function(text) {
        var fr = /^\s*([^\s].+)/;
        var bk = /^(.+[^\s])\s*$/;
        var res = fr.exec(text);
        if (!res) {
            return text;
        }
        var res2 = bk.exec(res[1]);
        if (!res2) {
            return res[1];
        }
        return res2[1];
    };	
	
    /**********************************************************************
		            collection of public jwyre components
    **********************************************************************/
    
	// Adds a collection of cross-browser compliant DOM functions; provides single point 
    // of DOM field and method access and manipulation, as well as some other 
    // non-DOM related functions, in which browser functionality may differ.
	(function(ns) {
		// create constants to represent the major browsers
		ns.Browser = {};
	    ns.Browser.IE = "Microsoft Internet Explorer";
	    ns.Browser.OPERA = "Opera";
	    ns.Browser.CHROME = "Chrome";
	    ns.Browser.SAFARI = "Safari";
	    ns.Browser.FIREFOX = "Firefox";
        
		// create constants for Node type (for IE compliance)
		ns.Node = {};
		ns.Node.ELEMENT = 1;
		ns.Node.TEXT = 3;
        ns.Node.DOCUMENT = 9;
        ns.Node.COMMENT = 8;
        ns.Node.DOCUMENT_FRAGMENT = 11;
        ns.Node.ATTRIBUTE = 2;
        
        /**
         * 
         */
        ns.isNodeType = function(element, type) {
        	element = ns.element(element);
        	if (!element) {
        		return false;
        	}
        	return element.nodeType == type;
        };
        
        
        /**
         * Obtains the browser name and version.  Returns an object with the 
         * information contained in the properties 'name' and 'version.'
         */
        ns.getBrowserInfo = function() {
            /*
             * The following code has been adapted from browser detection code found
             * at http://www.javascripter.net/faq/browsern.htm.
             */     
            var nVer = navigator.appVersion;
            var nAgt = navigator.userAgent;
            var browserName = navigator.appName;
            var fullVersion = ''+parseFloat(navigator.appVersion); 
            var majorVersion = parseInt(navigator.appVersion,10);
            var nameOffset,verOffset,ix;
            
            // In MSIE, the true version is after "MSIE" in userAgent
            if ((verOffset=nAgt.indexOf("MSIE"))!=-1) {
                browserName = "Microsoft Internet Explorer";
                fullVersion = nAgt.substring(verOffset+5);
            }
            // In Opera, the true version is after "Opera" 
            else if ((verOffset=nAgt.indexOf("Opera"))!=-1) {
                browserName = "Opera";
                fullVersion = nAgt.substring(verOffset+6);
            }
            // In Chrome, the true version is after "Chrome" 
            else if ((verOffset=nAgt.indexOf("Chrome"))!=-1) {
                browserName = "Chrome";
                fullVersion = nAgt.substring(verOffset+7);
            }
            // In Safari, the true version is after "Safari" 
            else if ((verOffset=nAgt.indexOf("Safari"))!=-1) {
                browserName = "Safari";
                fullVersion = nAgt.substring(verOffset+7);
            }
            // In Firefox, the true version is after "Firefox" 
            else if ((verOffset=nAgt.indexOf("Firefox"))!=-1) {
                browserName = "Firefox";
                fullVersion = nAgt.substring(verOffset+8);
            }
            // In most other browsers, "name/version" is at the end of userAgent 
            else if ( (nameOffset=nAgt.lastIndexOf(' ')+1) < (verOffset=nAgt.lastIndexOf('/')) ) {
                browserName = nAgt.substring(nameOffset,verOffset);
                fullVersion = nAgt.substring(verOffset+1);
                if (browserName.toLowerCase()==browserName.toUpperCase()) {
                    browserName = navigator.appName;
                }
            }
            // trim the fullVersion string at semicolon/space if present
            if ((ix=fullVersion.indexOf(";"))!=-1) fullVersion=fullVersion.substring(0,ix);
            if ((ix=fullVersion.indexOf(" "))!=-1) fullVersion=fullVersion.substring(0,ix);
            
            majorVersion = parseInt(''+fullVersion,10);
            if (isNaN(majorVersion)) {
                fullVersion  = ''+parseFloat(navigator.appVersion); 
                majorVersion = parseInt(navigator.appVersion,10);
            }
            /*         End adapted code.           */
            var vObj = _doVersConvert(fullVersion);
            majorVersion = vObj.majorVersion;
            minorVersion = vObj.minorVersion;
            return {
                "name" : browserName,
                "version" : "" + majorVersion,
                "fullVersion" : ""+fullVersion,
                "majorVersion" : majorVersion,
                "minorVersion" : minorVersion,
                "platform" : "" + navigator.platform,
                
                /**
                 * Returns true if this browser info object has version information
                 * indicating that it is newer (i.e. larger than) than the 
                 * version provided.
                 * 
                 * @param {string} version
                 */
                isNewerThan : function(version) {
                    var vObj = _doVersConvert(version);
                    var majorV = vObj.majorVersion;
                    var minorV = vObj.minorVersion;
                    ns.log("Version: " + version);
                    ns.log("majorV: " + majorV);
                    ns.log("minorV: " + minorV);
                    ns.log("" + this.majorVersion + " <  " + majorV + " ? " + (this.majorVersion < majorV));
                    if (this.majorVersion < majorV) {
                        return false;
                    }
                    ns.log("" + this.minorVersion + " >  " + minorV + " ? " + (this.minorVersion > minorV));
                    return this.minorVersion > minorV;
                },
                
                /**
                 * Returns true if this browser info object has version information
                 * indicating that it is older (i.e. smaller than) than the 
                 * version provided.
                 * 
                 * @param {string} version
                 */
                isOlderThan : function(version) {
                    var vObj = _doVersConvert(version);
                    var majorV = vObj.majorVersion;
                    var minorV = vObj.minorVersion;
                    if (this.majorVersion > majorV) {
                        return false;
                    }
                    return this.minorVersion < minorV;
                },
                
                /**
                 * Convenience method for checking browser info for indicator that it is
                 * a version of Internet Explorer.
                 * 
                 * @param {string} version (optional)
                 */
                isIE : function(version) {
                    return _isBrHelper(ns.Browser.IE, version);
                },
                
                /**
                 * Convenience method for checking browser info for indicator that it is
                 * a version of Mozilla Firefox.
                 * 
                 * @param {string} version (optional)
                 */
                isFirefox : function(version) {
                    return _isBrHelper(ns.Browser.FIREFOX, version);
                },
                
                /**
                 * Convenience method for checking browser info for indicator that it is
                 * a version of Google Chrome.
                 * 
                 * @param {string} version (optional)
                 */
                isChrome : function(version) {
                    return _isBrHelper(ns.Browser.CHROME, version);
                },
                
                toString : function() {
                    var str = this.name + " " + this.fullVersion + " (on " + this.platform + ")";
                    return str;
                }
            };
        };
        
        /*
         * Converts the version string/number into an object containing its
         * majorVersion/minorVersion components.
         */
        function _doVersConvert(version) {
            version = ""+version;
            var minorVersion = 0, majorVersion = 0;
            if (version.indexOf(".") > 0) {
                var comps = version.split(".");
                majorVersion = comps[0];
                minorVersion = comps.slice(1).join(".");
            }
            majorVersion = parseInt(majorVersion);
            minorVersion = parseFloat(minorVersion);
            
            return {
                "majorVersion" : majorVersion,
                "minorVersion" : minorVersion
            };
        }
        
        // used by browser checking functions; n : name, v : version
        function _isBrHelper(n, v) {
            var info = ns.getBrowserInfo();
            if (info.name != n) {
                return false;
            }
            if (h._x(v)) {
                return info.version.indexOf("" + v) == 0;
            }
            return true;            
        }
        		
		/**
		 * Convenience method for checking browser info for indicator that it is
		 * a version of Internet Explorer.
		 * 
		 * @param {string} version (optional)
		 */
        ns.isIE = function(version) {
			return _isBrHelper(ns.Browser.IE, version);
		};
		
        /**
         * Convenience method for checking browser info for indicator that it is
         * a version of Mozilla Firefox.
         * 
         * @param {string} version (optional)
         */
		ns.isFirefox = function(version) {
            return _isBrHelper(ns.Browser.FIREFOX, version);
		};
		
        /**
         * Convenience method for checking browser info for indicator that it is
         * a version of Google Chrome.
         * 
         * @param {string} version (optional)
         */
        ns.isChrome = function(version) {
            return _isBrHelper(ns.Browser.CHROME, version);
        };

	    /**
	     * Used to disable the text selection ability.
	     * 
	     * @param {Object} target
	     */ 
	    ns.disableSelection = function(target) {
		    /* **********************************************
		     * Disable Text Selection script- � Dynamic Drive DHTML code library (www.dynamicdrive.com)
		     * This notice MUST stay intact for legal use
		     * Visit Dynamic Drive at http://www.dynamicdrive.com/ for full source code
		     * **********************************************/           
	        if (typeof target.onselectstart!="undefined") { //IE route
	            target.onselectstart=function(){return false};
	        } else if (typeof target.style.MozUserSelect!="undefined") { //Firefox route
	            target.style.MozUserSelect="none";
	        } else { //All other route (ie: Opera)     
	            target.onmousedown=function(){return false};
	        }
	        target.style.cursor = "default";
	    };
        
		/**
		 * If name, value, and age are all present, then assigns the value to the
		 * cookie with the given name; no age provided or an age of '0' will 
		 * create a session cookie.  If value is null, the cookie is deleted.  
		 * If only name is present, returns the value of the cookie with the 
		 * given name, or null if it doesn't exist.
		 * 
		 * @param {string} name
		 * @param {string} value
		 * @param {int} age (in days, optional; default is 0)
         * @param {boolean} secure (optional; default is false)
		 */
		ns.cookie = function(name, value, age, secure) {
			if (!h._x(name) || jwyre.trim(name) == "") {
				return;
			}
			// set the cookie
			if (h._x(value)) {
				age = jwyre.parseNumber(age, 0);
				var exp = "";
				if (age != 0) {
	                var dt = new Date();
	                dt.setDate(dt.getDate() + age);
					exp = ";expires=" + dt.toGMTString();
				}
				value = jwyre.encode(value);
		        var cookie = name + "=" + value + exp;
				if (jwyre.parseBoolean(secure, false)) {
					cookie += "; secure"
				}
		        document.cookie = cookie;
			} 
			// delete the cookie
			else if (arguments.length > 1 && (!h._x(value) || !h._x(age) || age < 1)) {
                var dt = new Date();
                dt.setDate(dt.getDate() - 1);
                var cookie = name + "=" + value + ";expires=" + dt.toGMTString();
                document.cookie = cookie;				
			} 
			// return the cookie value
			else {
		        var ck = document.cookie;
		        var ind = ck.indexOf(name + "=");
		        if (ind == -1) {
		            return null;
		        }
		        ind = ind + name.length + 1;
		        var end = ck.indexOf(";", ind);
		        end = (end == -1) ? ck.length : end;
		        return jwyre.decode(ck.substring(ind, end));
			}
		};
		
		/**
		 * Returns true if cookies are enabled for this browser.
		 */
		ns.cookiesEnabled = function() {
			var enabled = false;
			// set test cookie and attempt to retrieve it 
			var nm = "_test_" + Math.random();
			ns.cookie(nm, "test", 1);
			enabled = "test" == ns.cookie(nm);
			ns.cookie(nm, null);
			return enabled;
		};
		
		/**
		 * Returns a collection of all nodes within the document.
		 */
        ns.all = function() {
			return _jd.elements("*");
		};
        
        //TODO: update documentation
		/**
		 * Convenience method for returning all elements matching the given 
		 * selector.
		 * 
		 * @param {string | HTMLElement} element
		 */
        ns.elements = function(element) {
            return _jd.elements(element);
		};
		
		//TODO: add caching for element lookup
	    /**
	     * Obtains HTMLElement(s) from the given param by checking to see if it 
	     * is a string, in which case it is assumed to be an element id, and
	     * is thus retrieved, or is simply returned (assumed to already be
	     * an HTMLElement).  If the provided arg is an array, will iterate
	     * through array and attempt to convert to elements.
	     * 
	     * @param {string | HTMLElement} element
	     */
	    ns.element = function(element) {
            return _jd.element(element);
	    };
		
		/**
		 * Obtains the tag name of the element given.  Returns empty string 
		 * if errors occur.
		 * 
		 * @param {string | HTMLElement} element
		 */
		ns.tag = function(element) {
			var el = ns.element(element);
			if (!h._x(el)) {
				return null;
			}
			var tn = el.tagName || "";
			return tn.toLowerCase();
		};
		
		/**
		 * Adds the given CSS class to all matched element's 'class' attribute
		 * (appended to end if class attribute already exists).
		 * 
		 * @param {string | HTMLElement} element
		 * @param {string} className
		 */
        ns.addClass = function(element, className) {
			jwyre.elements(element).each(function() {
                var cClass = _jd.attribute(element, "class");
				cClass = (!cClass || cClass == "") ? className : cClass + " " + className;
				_jd.attribute(element, "class", cClass);
			});
		};
		
        /**
         * Removes the given CSS class from all matched element's 'class' attribute
         * if it exists.
         * 
         * @param {string | HTMLElement} element
         * @param {string} className
         */
        ns.removeClass = function(element, className) {
            jwyre.elements(element).each(function() {
                var cClass = _jd.attribute(element, "class");
				cClass = (function(nm, cs) {
	                var tokens = cs.split(/ +/g);
	                for (var i = 0; i < tokens.length; i++) {
	                    if (tokens[i] == nm) {
	                        tokens[i] = "";
	                    }
	                }
	                return tokens.join(" ").replace(/ +/g, " ");					
				})(className, cClass);
                _jd.attribute(element, "class", cClass);
            });
        };
		
		/**
		 * Used to either get or set an attribute value on an HTML element.
		 * 
		 * @param {HTMLElement || string} element
		 * @param {string} attName
		 * @param {string} attValue (optional)
		 */
	    ns.attribute = function(element, attName, attValue) {
            return _jd.attribute(element, attName, attValue);
		};
        
		/**
		 * Creates an array of HTMLElements from the provided html string, if
		 * returnMultiple is present true, and the first element created otherwise.  
		 * 
		 * @param {string} html
		 * @param {boolean} returnMultiple
		 */
        ns.create = function(html, returnMultiple) {
			var els = _jd.create(html);
			return (returnMultiple) ? els : (els.size() > 0) ? els[0] : null;
		};		
		
        /**
         * Obtains the dimensions of the provided object.  Returns an object
         * with the properties 'height' and 'width'.  If no object is provided,
         * returns the dimensions of the window object.
         * 
         * @param {HTMLElement} element (optional)
         */
        ns.dimensions = function(element, includeMargin) {
			element = (!h._x(element)) ? window : ns.element(element);
			// do special for document/document.body
			if (element == document || element == document.body) {
                return {
				    // VERY QnD function written to ATTEMPT to overcome massive inconsistencies
				    // when attempting to get body/document dimensions; very often, height is '0'
				    // have found that very often, across browsers and circumstances, can get a 
				    // decent approximation using the values inspected within
				    //TODO: find better solution (5.25.10)
                    height : (function() {
                        var h =_jd.height(element, includeMargin);
						if (h != 0) {
							return h;
						}
		                if (document.body.offsetHeight != 0) {
		                    return document.body.offsetHeight;
		                } else if (document.body.clientHeight != 0) {
		                    return document.body.clientHeight;
		                } else if (document.body.scrollHeight != 0) {
		                    return document.body.scrollHeight;
		                } else {
							// return arbitrary height value (better than zero)
		                    return (window.screen.height || 600);
		                }           						
					})(),
                    width : _jd.width(element, includeMargin),
                    toString : function() { return "[height=" + this.height + ",width=" + this.width + "]"; }
                };	
			}
			return h._doTempAppend(element, function() {
	            return {
	                height : _jd.height(element, includeMargin),
	                width : _jd.width(element, includeMargin),
	                toString : function() { return "[height=" + this.height + ",width=" + this.width + "]"; }
	            };				
			});
        };
		
        /**
         * Obtains an object with 'top' and 'left' properties for the given scroll
         * value of the given object, or the Window object if no element is provided.
         * 
         * @param {string || HTMLElement} element (optional)
         */
        ns.scroll = function(element) {
            element = (!h._x(element)) ? window : element;
            return {
                top : _jd.scrollTop(element),
                left : _jd.scrollLeft(element),
                toString : function() { return "[top=" + this.top + ",left=" + this.left + "]"; }
            };
        };

        /**
         * Returns an object with 'top' and 'left' properties for the given node,
         * providing either the top and left from the node's parent, if relative
         * is present and true, and the absolute left and top values otherwise.
         * If an x- and y- value are provided, will SET the position.
         * 
         * @param {string || HTMLElement} node
         * @param {string || HTMLElement} relative (optional)
         * @param {boolean} ignoreScroll (optional)
         * 
         * OR
         * @param {string || HTMLElement} node
         * @param {integer} x (optional)
         * @param {integer} y (optional)
         * @param {integer} z (optional)
         */
        ns.position = function(node, relative, ignoreScroll) {
            if (arguments.length >= 3 && h._getNumber(arguments[1]) != null) {
				_jd.left(node, arguments[1]);
                _jd.top(node, arguments[2]);
                if (h._x(arguments[3])) {
					jwyre.style(node, "z-index", "" + arguments[3]);
                }
            } else {
	            return {
	                top : ns.top(node, relative, ignoreScroll),
	                left : ns.left(node, relative, ignoreScroll),
	                toString : function() { return "[top=" + this.top + ",left=" + this.left + "]"; }
	            };				
			}
        };
        
        /**
         * Obtains the top distance of the given node from its parent node, if
         * relative is present and true, and from the document (its absolute
         * top) by default otherwise.
         * 
         * @param {string || HTMLElement} node
         * @param {string || HTMLElement} relative (optional)
         * @param {boolean} ignoreScroll (optional)
         */     
        ns.top = function(node, relative, ignoreScroll) {
			if (ignoreScroll == true) {
	            return _jd.top(node, relative);			
			} else {
	            return _jd.top(node, relative) + _jd.scrollTop(node, relative);
			}
        };

        /**
         * Obtains the left distance of the given node from its parent node, if
         * relative is present and true, and from the document (its absolute
         * left) by default otherwise.
         * 
         * @param {Object} node
         * @param {boolean} relative (optional)
         * @param {boolean} ignoreScroll (optional)
         */     
        ns.left = function(node, relative, ignoreScroll) {
            if (ignoreScroll == true) {
                return _jd.left(node, relative);           
            } else {
                return _jd.left(node, relative) + _jd.scrollLeft(node, relative);
            }
        };
		
        /**
         * 
         * @param {string | HTMLElement} element
         * @param {string | HTMLElement} text
         * 
         * @deprecated Use jwyre.text() instead
         */
        ns.setText = function(element, text) {
			return _jd.text(element, text);
        };
		
        /**
         * Obtains the text content of the given element, or sets it, if 
         * text is present.
         * 
         * @param {string | HTMLElement} element
         * @param {string} text (optional)
         */
		ns.text = function(element, text) {
            return _jd.text(element, text);			
		};

        /**
         * Obtains the HTML content of the given element, or sets it, if 
         * html is present.
         * 
         * @param {string | HTMLElement} element
         * @param {string} html (optional)
         */
        ns.html = function(element, html) {
            return _jd.html(element, html);                       
        };
        
        /**
         * Obtains the Style object containing all applied styles on the given 
         * element
         * 
         * @param {string || HTMLElement} element
         */
        ns.getComputedStyle = function(element) {
        	element = ns.element(element);
        	if (!element) {
        		return {};
        	}
        	if (window.getComputedStyle) {
        		return getComputedStyle(element, null);
        	} else {
        		return element.currentStyle;
        	}
        };

        /**
         * Sets the styles contained in the styleMap object on the provided 
         * element.
         * 
         * @param {Object} element
         * @param {Object} styleMap
         * @deprecated Use jwyre.style() instead.
         */
        ns.styles = function(element, styleMap) {
			return _jd.style(element, styleMap);
        };
        
        /**
         * If a value is provided, set the style rule defined by the style arg
         * to the given value.  If value is not provided, returns the value of
         * the given style. If the second argument is an object, it will be assumed to
         * be a map of style and values, and all will be applied.
         * 
         * @param {string || HTMLElement || Array} element
         * @param {Object} style
         * @param {Object} value (optional)
         */
        ns.style = function(element, style, value) {
			return _jd.style(element, style, value);
        };

        /**
         * 
         * @param {Object} element
         * @param {Object} style
         */ 
        ns.removeStyle = function(element, style) {
            style = h._camelCase(style);
            ns.elements(element).each(function() { _jd.style(this, style, ""); });
        };
    
        
        /**
         * 
         * @param {Object} element
         */
        ns.show = function(element) {
			_jd.show(element);
        };
        
        /**
         * 
         * @param {Object} element
         */
        ns.hide = function(element) {
            _jd.hide(element);
        };

		//TODO: update documentation and add ability to add text nodes OR
		// create new HTML
		/**
		 *
		 * @param {string | HTMLElement} element
		 * @param {string | HTMLElement | array} child
		 */   
	    ns.append = function(element, child) {
            element = ns.element(element);
			if (!element || !child) {
				return;
			}
			var it;
			if (child._isJAry) {
				it = child.iterator();
			} else {
				it = _it(_ary(arguments).getNative().slice(1));
			}
            for (; it.hasNext();) {
                child = it.next();
                element.appendChild((typeof child == "string") ? document.createTextNode(child) : child);
            }               
		};
	   
	    /**
	     * Returns the DOM Option object (or array of objects, if multiple select)
	     * that represents the selected item(s) in the provided DOM Select element, 
	     * if getObject is present and true, and the string value(s) otherwise.  Returns 
	     * null if the element can not be coerced as a Select object, or if no 
	     * item has been selected.
	     * 
	     * @param {string | HTMLElement} element
	     * @param {boolean} getObject (optional)
	     */   
	    ns.selected = function(element, getObject) {
			getObject = jwyre.parseBoolean(getObject, false);
			element = ns.element(element);
			if (!element) {
				return null;
			}
			if (ns.tag(element) != "select" || element.selectedIndex < 0) {
				return null;
			}
			var sels = [];
			for (var i = 0; i < element.options.length; i++) {
				var itm = element.options[i];
				if (itm.selected) {
					sels.push((getObject) ? itm : itm.value);
				}
			}
			return (element.multiple) ? sels : (sels.length > 0) ? sels[0] : null;
		};  
	
        /**
         * Sets the DOM Option object with the given value as selected 
         * in the provided DOM Select element.  Does nothing if the element
         * can not coerced as a Select object, or if no item with the given
         * value exists.
         * 
         * @param {string | HTMLElement} element
         * @param {string} value
         * @param {boolean} dontSendEvent (optional; default is false)
         */
	    ns.select = function(element, value, dontSendEvent) {
            element = ns.element(element);
            if (!element || !element.options) {
                return null;
            }
            for (var i = _it(element); i.hasNext(); ) {
                var el = i.next();
                if (el.value == value) {
					el.selected = true;
					if (!dontSendEvent) {
                        ns.sendEvent("HTMLEvents", "change", element, [true, false]);						
					}
                    return;
                }				
			}
		};
		
		/**
		 * Returns true if the child element is completely contained by the
		 * bounding rectangle of the container element.
		 * @param {HTMLElement | string} container
		 * @param {HTMLElement | string} child
		 * 
		 * OR
		 * 
         * @param {HTMLElement | string} container
         * @param {number} x
         * @param {number} y
         * 
		 */
		ns.isIn = function(container, child) {
            container = ns.element(container);
            // the parent position and dimensions
            var pPos = ns.position(container);
            var pDim = ns.dimensions(container);
            // the child position and dimensions
            var cPos, cDim;
			if (arguments.length == 2) {
	            child = ns.element(child);
	            if (!h._x(container) || !h._x(child)) {
	                return false;
	            }				
	            // the child position and dimensions
	            cPos = ns.position(child);
	            cDim = ns.dimensions(child);
			} else {
				cPos = {
					"top" : arguments[2],
					"left" : arguments[1]
				};
				cDim = {
					"width" : 1,
					"height" : 1
				}
			}
	        // ensure near-side bounds
	        if (cPos.top < pPos.top || cPos.left < pPos.left) {
	            return false;
	        }
	        var pBrX = pPos.left + pDim.width;
	        var pBrY = pPos.top + pDim.height;
	        var cBrX = cPos.left + cDim.width;
	        var cBrY = cPos.top + cDim.height;
	        // ensure far-side bounds
	        if (pBrX < cBrX || pBrY < cBrY) {
	            return false;
	        }
	        return true;
		};
		   
	    /**
	     * Used to generate an artificial event and send it to listeners
	     * registered for the event type.
	     * 
	     * @param {Object} type
	     * @param {Object} name
	     * @param {Object} target
	     * @param {Object} args
	     */ 
	    ns.sendEvent = function(type, name, target, args) {
			target = jwyre.event(target);
			if (!target) {
				return;
			}
	        var event = null;
	        try {
	            if (document.createEvent) {
	                event = document.createEvent(type);
	                //  TODO: comment the crap out of this....its ridiculous
	                switch (type) {
	                    case "HTMLEvents":
	                        if (!args || args.length < 2) {
	                            return;
	                        }
	                        //    eventType ("submit", "load", etc.), canBubble, cancelable
	                        event.initEvent(name, args[0], args[1]);
	                        break;
	                    case "MouseEvents":
	                        if (!args || args.length < 14) {
	                            return;
	                        }
	                        //    eventType, canBubble, cancellable, view (AbstractView),
	                        //    detail, screenX, screenY,
	                        //    clientX, clientY, ctrlKey, altKey,
	                        //    shiftKey, metaKey, button, relatedTarget (Element)
	                        event.initMouseEvent(
	                            name, args[0], args[1], args[2], args[3],
	                            args[4], args[5], args[6], args[7],                          
	                            args[8], args[9], args[10], args[11],
	                            args[12], args[13]);
	                        break;
	                    case "UIEvents":
	                        if (!args || args.length < 4) {
	                            return;
	                        }
	                        //    eventType, canBubble, cancelable, view (Window), detail
	                        event.initUIEvent(name, args[0], args[1], args[2], args[3]);
	                        break;
	                    default:
	                        return;
	                }
	            } else if (document.createEventObject) {
	                event = document.createEventObject(type);
	            }
	        } catch (e) { }
	        
	        if (!event) {
	            return;
	        }
	        
	        if (target.dispatchEvent) {
	            target.dispatchEvent(event);
	        } else if (target.fireEvent) {
	            target.fireEvent("on" + name, event);
	        }
	    };
    
	    //TODO: update documentation   
	    /**
	     * Obtains the event, by checking to see if there is a globally scoped
	     * event object if the provided event object is null.  Also, obtains the 
	     * event's source element and assigns it to the 'target' property of the 
	     * returned event.
	     * 
	     * @param {Object} event
	     */ 
	    ns.event = function(event) {
	        if (!event) {
	            event = window.event;
	            if (!event) { return null; }
	        }
            if (h._x(event.srcElement)) {
                event.target = event.srcElement;
            }
            if (!jwyre.isIE() && event.type == "mouseover") {
                if (!h._x(event.fromElement)) {
                    event.fromElement = event.relatedTarget;
                }
                if (!h._x(event.toElement)) {
                    event.toElement = event.target;     
                }
            }
            if (!jwyre.isIE() && event.type == "mouseout") {
                if (!h._x(event.toElement)) {
                    event.toElement = event.relatedTarget;
                }
                if (!h._x(event.fromElement)) {
                    event.fromElement = event.target;     
                }
            }
			if (jwyre.isIE()) {
				event.targetX = event.offsetX;
                event.targetY = event.offsetY;
			} else {
                event.targetX = event.layerX;
                event.targetY = event.layerY;
			}
 	        return event;
	    };
    
	    //  IE hack code; does not perform window.onload events in right order
	    var IELoadEvents = new Array();
	    var IEWindowOnload = function(event) {
	        for (var i = 0; i < IELoadEvents.length; i++) {
	            IELoadEvents[i](event);
	        }
	    };
		
		// constants used to specifiy where an event is to be handled, either
		// in capturing (top-down) or bubbling (bottom-up) order
		ns.Event = {};
		ns.Event.CAPTURE = "capture";
        ns.Event.BUBBLE = "bubble";

		/**
		 * Convenience method for adding a 'click' event listener to an element.
		 * 
		 * @param {Object} element
		 * @param {Object} action
		 */
		ns.addClick = function(element, action) {
            ns.addListener(element, "click", action);
		};
		
		/**
		 * Convenience method for adding 'mouseover' and 'mouseout' event 
		 * listeners to an element.  If strings are provided, rather than
		 * functions, they will interpreted as values for background-image
		 * to be used for hover-on and hover-off.
         * 
		 * @param {string || HTMLElement} element
		 * @param {Function} hoverAction
		 * @param {Function} unhoverAction
		 * 
		 * OR
		 * 
		 * @param {string || HTMLElement} element
         * @param {string} hoverImage
         * @param {string} unhoverImage
		 */
        ns.addHover = function(element, hoverAction, unhoverAction) {
			if (arguments.length == 2) {
				element = jwyre.element(element);
	            if (typeof hoverAction == "string") {
	                var onImg = hoverAction, offImg = unhoverAction;
	                hoverAction = function(event) { jwyre.style(element, "background-image", onImg); return jwyre.kill(event); };
	                unhoverAction = function(event) { jwyre.style(element, "background-image", offImg); return jwyre.kill(event); };
	            } else if (typeof hoverAction == "object") {
					var fns = (function(hoverStyles) {
				        var origStyles = h._getOrigStyles(element, hoverStyles);
						return {
							on : function() { jwyre.style(element, hoverStyles); },
							off : function() { jwyre.style(element, origStyles); }
						};
					})(hoverAction);
					hoverAction = fns.on;
                    unhoverAction = fns.off;
				}				
			}
            ns.addListener(element, "mouseover", hoverAction);
            ns.addListener(element, "mouseout", unhoverAction);            
        };
        
		// used for tracking window load events
        ns._loadCtr = 0;
		
		// helper method for adding event to a single HTMLElement
        function addEvent(element, eventType, action, capture) {
            if (!element || !eventType || typeof eventType != "string") {
                return;
            } 
            if (eventType.substr(0, 2) == "on") {
                eventType = eventType.substr(2);
            }
            // wrap window load events so that jwyre.isLoaded can be aware of all 
            // load events have been completed
            if ((element == window) && (eventType == "load")) {
                ns._loadCtr++;
                var loadAct = action;
                action = function(event) {
                    loadAct();
                    ns._loadCtr--;
                };
            }
            //  handle window load events differently for IE; doesn't follow correct order
            if (ns.isIE() && (element == window) && (eventType == "load")) {
                IELoadEvents.push(action);
                window.onload = IEWindowOnload;
                return;
            }
            // check for pseudo-event 'hover'
            if (eventType == "hover" && typeof capture == "function") {
                ns.addHover(element, action, capture);
                return;
            }
            if (element.addEventListener) {
                var bubbles = (!capture) ? false : capture == ns.Event.CAPTURE;
                element.addEventListener(eventType, action, bubbles);
            } else if (element.attachEvent) {
                element.attachEvent("on" + eventType, action);
            } else {
                //  attempt to use DOM 0 assignment; if it doesn't work, do nothing
                try {
                    element["on" + eventType] = action;
                } catch (e) {}
            }			
		}

	    /**
	     * 
	     * @param {Object} element
	     * @param {Object} eventType
	     * @param {Object} action
	     * @param {Object} capture
	     */  
	    ns.addListener = function(element, eventType, action, capture) {
			ns.elements(element).each(function(item) { addEvent(item, eventType, action, capture)});			
	    };

	    /**
	     * 
	     * @param {Object} element
	     * @param {Object} eventType
	     * @param {Object} action
	     * @param {Object} capture
	     */ 
	    ns.removeListener = function(element, eventType, action, capture) {
            element = ns.element(element);
	        if (!element || !eventType || typeof eventType != "string") {
	            return;
	        }
	        if (eventType.substr(0, 2) == "on") {
	            eventType = eventType.substr(2);
	        }
	        if (element.removeEventListener) {
                var bubbles = (!capture) ? false : capture == ns.Event.CAPTURE;
	            element.removeEventListener(eventType, action, bubbles);
	        } else if (element.detachEvent) {
	            element.detachEvent("on" + eventType, action);
	        } else {
	            element["on" + eventType] = null;
	        }
	    };
    	
	    /**
	     * 
	     * @param {Object} event
	     */
	    ns.kill = function(event) {
	        try {
	            event = ns.event(event);
	            if (event == null) {
	                return;
	            }
				if (h._x(event.preventDefault)) {
					event.preventDefault();
				}
                if (h._x(event.stopPropagation)) {
                    event.stopPropagation();
                }
                event.returnValue = false;              
                event.cancelBubble = true;
	            //  catch and do nothing; attempt to work-around IE issue
	        } catch (e) { }
			return false;
	    };
	         
		/**
		 * 
		 * @param {Object} element
		 */
        ns.parent = function(element) {
			element = ns.element(element);
			if (!element) {
				return null;
			}
            return element.parentNode;
		};
				
		/**
		 * Removes the provided node from the DOM.
		 * 
		 * @param {Node} node
		 */
        ns.remove = function(node){
			node = ns.element(node);
            if (!node || !node.parentNode) {
				return;
			}  
			node.parentNode.removeChild(node);
		};

	    /**
	     * Removes all children from the given node.
	     * 
	     * @param {Node} node
	     */
        ns.removeChildren = function(node) {
			node = ns.element(node);
	        if (!node || !node.childNodes) {
	            return;
	        }
	        for (var i = node.childNodes.length - 1; i >= 0; i--) {
	            var child = node.childNodes[i];
	            node.removeChild(child);
	        }
	    };
		
		/**
		 * 
		 * @param {string | HTMLElement} node
		 * @param {boolean} recurse (optional)
		 */
		ns.children = function(node, recurse) {
			recurse = jwyre.parseBoolean(recurse, false);
            node = ns.element(node);
            if (!node) {
				return jwyre.array();
			}
			//TODO: fix array constructor
			var ary = _ary();
			for (var i = 0; i < node.childNodes.length; i++) {
                ary.push(node.childNodes[i]);
				if (recurse) {
	                ary.push(ns.children(node.childNodes[i], recurse));
				}
			}
			return ary;
		};

        /**
         * Same as jwyre.removeChildren().
         */
        ns.empty = ns.removeChildren;
        
		/**
		 * 
		 * @param {string | HTMLElement} oldElement
		 * @param {string | HTMLElement} newElement
		 */
        ns.replace = function(oldElement, newElement) {
			oldElement = ns.element(oldElement);
            newElement = ns.element(newElement);
			if (!oldElement || !newElement) {
				return;
			}
			var parent = ns.parent(oldElement);
			if (parent) {
	            parent.replaceChild(newElement, oldElement);			
			}
		};

                               
        /**
         * Inserts the element immediately before the reference element.
         * 
         * @param {HTMLElement} element
         * @param {HTMLElement} reference
         */   
        ns.insertBefore = function(element, reference) {
            element = ns.element(element);
            reference = ns.element(reference);
            if (!element || !reference) {
                return;
            }
            var parent = ns.parent(reference);
            if (!parent) {
                throw new Error("Reference item has no parent.");
            }
            parent.insertBefore(element, reference);
        };
						       
		/**
		 * Inserts the element immediately after the reference element.
		 * 
		 * @param {HTMLElement} element
         * @param {HTMLElement} reference
		 */   
        ns.insertAfter = function(element, reference) {
            element = ns.element(element);
            reference = ns.element(reference);
	        if (!element || !reference) {
	            return;
	        }
	        var parent = ns.parent(reference);
			if (!parent) {
				throw new Error("Reference item has no parent.");
			}
	        if (!reference.nextSibling) {
	            parent.appendChild(element);
	            return;
	        } 
	        parent.insertBefore(element, reference.nextSibling);
	    };
		
		// constants used for mouse button detection
	    var LEFT = "left";
	    var RIGHT = "right";
	    var MIDDLE = "middle";
	    var BUTTONS = {0:LEFT,2:RIGHT,1:MIDDLE};
	    var IE_BUTTONS = {1:LEFT,2:RIGHT,4:MIDDLE};

	    /**
	     * 
	     * @param {Object} event
	     */
	    ns.isLeftClick = function(event) {
	        return _checkButton(event, LEFT);
	    };

	    /**
	     * 
	     * @param {Object} event
	     */ 
	    ns.isRightClick = function(event) {
	        return _checkButton(event, RIGHT);
	    };
        
		/**
		 * 
		 * @param {Object} event
		 */
        ns.killContextMenu = function() {
            ns.addListener(document, "contextmenu", function(event) { return ns.kill(event)});
			ns.addListener(document.body, "contextmenu", function(event) { return ns.kill(event)});
            if (!jwyre.isIE()) {
				function _(e){
					if (e.button == 2 || e.button == 3) {
						e.preventDefault();
						e.stopPropagation();
						return false;
					}
				}
				jwyre.addListener(document, "click", _);
				jwyre.addListener(document, "mousedown", _);
			}
		};
		
		/*
		 * Internal function used by left-click/right-click detection functions. 
		 */
        function _checkButton(event, button) {
            event = ns.event(event);
            var buttons = ns.isIE() ? IE_BUTTONS : BUTTONS;
			if (event.button) {
				return buttons[event.button] == button;
			} else if (event.which) {
				return buttons[event.which] == button;
			} else {
				return false;
			}
	    };
	
	    /**
	     * Obtains the selected text within a form element.
	     * 
	     * @param {Object} element
	     */         
	    ns.selectedText = function(element) {
			element = ns.element(element);
			if (!h._x(element)) {
				return;
			}
	        if (h._x(element.selectionStart)) {
	            return element.value.substring(element.selectionStart, element.selectionEnd);
	        } else if (window.getSelection) {
	            return window.getSelection().toString();
	        } else if (document.getSelection) {
	            return document.getSelection();
	        } else if (document.selection) {
	            return document.selection.createRange().text;
	        } else {
	            return null;
	        }
	    };

	    /**
	     * Copies all styles from the element to the clone element.  Silently ignores
	     * any errors.
	     * 
	     * @param {Object} element
	     * @param {Object} clone
	     */
	    ns.cloneStyles = function(element, clone) {
			element = ns.element(element);
			clone = ns.element(clone);
	        if (!element || !clone || !element.style || !clone.style) {
	            return;
	        }
	        for (var nm in element.style) {
	            try {
	                var val = element.style[nm];
	                if (typeof val == "function" || val == "") {
	                    continue; 
	                }
	                ns.style(clone, nm, val);
	            } catch (e) {}
	        }
	    };    

        // adds the load() and loadImgs() method to the jwyre object
        var _isLoaded = false;
        if (!jwyre.isIE()) {
            document.addEventListener("DOMContentLoaded", function() { _isLoaded = true; }, false);         
        }

        /**
         * Returns true if the DOM content has been loaded for the current document.
         */
        ns.isLoaded = function() {
            var contentLoaded = (jwyre.isIE()) ? document.readyState == "complete" : _isLoaded; 
            return contentLoaded && (ns._loadCtr ==  0);
        };
        
        /**
         * 
         */
        ns.load = function(funct) {
            // ensures that funct is a valid function
            funct = h._getFn(funct);
			_jd.load(funct);
        };
        
        /**
         * 
         * @param {string || HTMLElement} item
         */
        ns.fixPng = function(item) {
            item = jwyre.element(item);
            // check for items w/ a bk. pic
            var url = jwyre.style(item, "background-image");
            if (url && url.indexOf(".png") > 0) {
                url = url.replace("url(", "");
                url = url.replace(")", "");
                url = url.replace(/['"]/g, "");
                item.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + url + "', sizingMethod='scale')";
                item.style.backgroundImage = "url(x.gif)";
            } else {
                // check for img w/ a .png
                url = jwyre.attribute(item, "src");             
                if (url && url.indexOf(".png") > 0) {
                    var div = jwyre.create("<div></div>");
                    div.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + url + "', sizingMethod='scale')";
                    div.id = item.id;
                    jwyre.replace(item, div);
                }
            }
        };
                
        /**
         * 
         * @param {Array} imgs (optional)
         */
        ns.loadImgs = (function() {
            // create array to hold preloaded Image objects
            var ary = jwyre.array();
            // strips out url, if one is present
            var urlRgEx = /['"]?url\s*\(?(.+)?\)['"]?/;
            function load(toCheck) {
                if (!toCheck) { return; }
                if ("none" == toCheck) { return; }
                var val = urlRgEx.exec(toCheck);
                var src;
                if (val && val[1]) {
                    src = jwyre.trim(val[1]);
                    if (src.indexOf("../") == 0) {
                        src = src.replace("../", "./");
                    } else if (src.indexOf("\"") == 0) {
                        src = src.substring(1, src.length - 1);
                    }
                } else {
                    src = toCheck;
                }
                var i = new Image();
                i.src = src;
                ary.push(i);
            }
            
            // parses through document and stylesheets to pull out image sources
            // to preload
            function _() {
                for (var i = jwyre.all().iterator(); i.hasNext(); ) {
                    var n = i.next();
                    if (n.src && "img" === n.tagName.toLowerCase()) {
                        load(n.src);
                    } else {
                        load(jwyre.style(n, "backgroundImage"));
                    }  
                }
                var ss = document.styleSheets;
                for (var i = 0; i < ss.length; i++) {
                    var sheet = ss[i];
                    var rules = sheet.rules;
                    rules = (!rules && sheet.cssRules) ?  sheet.cssRules : rules;
                    if (!rules) {
                        continue;
                    }
                    for (var i = 0; i < rules.length; i++ ) {
                        var rule = rules[i];
                        load(rule.style.background);
                        load(rule.style.backgroundImage);
                    }
                }
            }
            
            return function(images) {
                if (images) {
                    jwyre.array(images).each(
                        function() {
                            load(this);
                        }
                    );
                } else {
                    _();
                }
            };
        })();
	})(jwyre);

    // assign the module() function to the jwyre object
    jwyre.module = (function(ns) {    
        var _globalNameSpace = ns;
        /*
         * 
         */
        function _createModule(name, namespace) {
            if (name == null) {
                throw new Error("The name can not be null.");
            }   
            var comps = name.split(".");
            
            if (!namespace) {
                namespace = _globalNameSpace;
            } 
            
            for (var index in comps) {
                var comp = comps[index];
                _create(namespace, comp, true);
                namespace = namespace[comp];
            }
            
            return namespace;   
        }
                
        /*
         * Creates an empty object within the given namespace; returns true only if 
         * an item did not previously exist in the namespace with the given name
         * and is now successfully initialized.  If replace is true, will create 
         * a new empty object even if an item with the name in the namespace 
         * already exists.
         */
        function _create(namespace, name, create) { 
            if (typeof namespace == "string") {
                namespace = _createModule(namespace);
            }
            if ((!namespace[name])) {// || create) {
                namespace[name] = {};
                return true;
            }       
            return false;
        }        
        return _createModule;
    })(window);

	// assigns the date() function (which returns a jwyre.Date() object) to the
	// jwyre object
	jwyre.date = (function() {
		var MONTHS = [
		     { days : 31, name : "January"},
		     { days : 28, name : "February"}, // leap year is accounted for in code
		     { days : 31, name : "March"},
		     { days : 30, name : "April"},
		     { days : 31, name : "May"},
		     { days : 30, name : "June"},
		     { days : 31, name : "July"},
		     { days : 31, name : "August"},
		     { days : 30, name : "September"},
		     { days : 31, name : "October"},
		     { days : 30, name : "November"},
		     { days : 31, name : "December"}
		];
        jwyre.Date = {
	        // add MONTHS to the global namespace
			"MONTHS" : MONTHS,
	        /**
	         * Returns true if the given object can be parsed as a jwyre.Date()
	         * object.
	         * 
	         * @param {Object} date
	         */
            isDate : function(date) {
	            try {
	                var d = new _Date(date);
	                return true;
	            } catch (e) {
	                return false;
	            }
	        },
			/**
			 * Return the number of milliseconds since 1970/01/01.
			 */
			currentTimeMillis : function() {
				return new Date().getTime();	
			}
		};
		/**
		 * Wrapper class for a JavaScript Date object.
		 * 
		 * @param {Object} date
		 */
		function _Date(date) {
			// gets the JS Date object that is nested within this object, accounting
			// for the date object being a jwyre.Date object
		    function getDt(dt) {
				if (!dt) {
                    //TODO: write usage error
                    throw new Error("jwyre.Date() ERROR - Usage");					
				}
		        if (dt._isDate) {
		            return getDt(dt._date);
		        } else if (typeof dt == "string") {
					var d = new Date();
					var t = Date.parse(dt);
					if (isNaN(t)) {
						//TODO: write usage error
						throw new Error("jwyre.Date() ERROR - Usage");
					}
					d.setTime(t);
					return d;
				} else if (typeof dt == "number") {
                    var d = new Date();
                    d.setTime(dt);
                    return d;
                } else if (dt.constructor == Date) {
		            return dt;
		        } else {
                    //TODO: write usage error
                    throw new Error("jwyre.Date() ERROR - Usage");					
				}
		    }
		    this._date = getDt(date);
		    this._isDate = true;
		
		    // copies a JS Date object
		    function clone(date) {
		        var newDate = new Date();
		        newDate.setTime(getDt(date).getTime());
		        return newDate;
		    }
			
	        /**
	         * 
	         * @param {string} format (optional)
	         */   
		    this.format = function(format) {
		        format = (!format) ? "mm/dd/yy" : format;
		        var reg = /([m]{1,2}|[d]{1,2}|[y]{2,4})([\.\\\/\- :_\|]*)([m]{1,2}|[d]{1,2}|[y]{2,4})([\.\\\/\- :_\|]*)([m]{1,2}|[d]{1,2}|[y]{2,4})/gi;
		        reg = reg.exec(format);
		        var day, mon, yr;
		        var ord = {"d" : -1, "m" : -1, "y" : -1};
		        var ctr = 0;
		        var sp = [];
		        var spCtr = 0;
		        try {
		            for (var i = 1; i <= 5; i++) {
		                var test = reg[i];
		                if (!test) {
		                    continue;
		                }
		                if (test.toLowerCase().indexOf("d") == 0) {
		                    ord["d"] = ctr++;
		                    day = reg[i];
		                } else if (test.toLowerCase().indexOf("m") == 0) {
		                    ord["m"] = ctr++;
		                    mon = reg[i];                    
		                } else if (test.toLowerCase().indexOf("y") == 0) {
		                    ord["y"] = ctr++;
		                    yr = reg[i];
		                } else {
		                    sp[spCtr++] = reg[i]; 
		                }
		            }
		        } catch (e) { }     
		
		        // adds padding to to the number if needed, based on number of desired digits
		        function pad(num, digits) {
		            if (!digits || digits <= 1) {
		                return num;
		            }
		            if (num >= 10 && num < 100 && digits == 2) {
		                return num;             
		            } else if (num > 999) {
		                if (digits == 2) {
		                    return pad(num % 100, 2);               
		                } else {
		                    return num;
		                }
		            }
		            return "0" + pad(num, --digits);
		        }
		        
		        // formats the date component according to string provided, if it exists        
		        function fmt(dtStr, fmtStr, fmtOrdLbl) {
		            if (!fmtStr || !h._x(ord[fmtOrdLbl])) {
		                return "";
		            }
		            var num = parseInt(dtStr);
		            return pad(num, fmtStr.length);
		        }
		        
		        // assigns the date comeponent to the array if is to be present in string
		        function assign(ary, prop, str) {
		            if (ord[prop] >= 0) {
		                ary[ord[prop]] = str;
		            }
		        }
		        
		        // create array and use function to assign formatted strings in app. order
		        var comps = [];
		        assign(comps, "d", fmt(this.getDay(), day, "d"));
		        assign(comps, "m", fmt(this.getMonth() + 1, mon, "m"));
		        assign(comps, "y", fmt(this.getYear(), yr, "y"));
		        
		        // assemble string and return
		        var str = "";
		        var ctr = 0;
		        for (var i = 0; i < comps.length; i++) {
		            str += comps[i];
		            var cn = sp[ctr++]; 
		            if (cn) {
		                str += cn;
		            }
		        }
		        return str;
		    };
		
			/**
			 * Determines if the given year, or the year within this instance, 
			 * if not provided, is a leap year (based on Gregorian cal. stipulations)
			 * 
			 * @param {int} year (optional)
			 */
		    this.isLeapYear = function(year) {
		        year = !h._x(year) ? this.getYear() : year;
		        if (year % 4 != 0) {
		            return false;
		        }
		        if (year % 100 == 0) {
		            return year % 400 == 0;
		        }
		        return true;
		    };
		    
			/**
			 * Return a jwyre.Date object that represents a date the specified
			 * number of days in the future from this instance.
			 * @param {integer} numDays
			 */
			this.advanceDays = function(numDays) {
                var d = clone(this._date);
                d.setDate(d.getDate() + numDays);
                return new _Date(d);	
			};

            /**
             * Return a jwyre.Date object that represents a date the specified
             * number of months in the future from this instance.
             * @param {integer} numMonths
             */
            this.advanceMonths = function(numMonths) {
                var d = clone(this._date);
                d.setMonth(d.getMonth() + numMonths);
                return new _Date(d);
            };

            /**
             * Return a jwyre.Date object that represents a date the specified
             * number of years in the future from this instance.
             * @param {integer} numYears
             */
            this.advanceYears = function(numYears) {
                var d = clone(this._date);
                d.setFullYear(d.getFullYear() + numYears);
                return new _Date(d);   
            };
			
			/**
			 * Returns an array of 7 jwyre.Date objects that represent the containing 
			 * week of this instance. 
			 */            
		    this.getWeek = function() {
		        var week = _ary();
				var wDay = this.getWeekDay();
				for (var i = 0; i < 7; i++) {
					week.push(this.advanceDays(i - wDay));
				}
		        return week;
		    };
		    
			/**
			 * Returns a jwyre.Date object that represents the day before this
			 * instance.
			 */
		    this.getYesterday = function() {
				return this.advanceDays(-1);
		    };
		    
			/**
			 * Returns a jwyre.Date object that represents the day after this
             * instance.			 
             */
		    this.getTomorrow = function() {
                return this.advanceDays(1);
		    };
		    
		    /**
		     * Obtains a jwyre.Date object that represents the first day of this month.
		     */
		    this.getFirstDay = function() {
		        var d = clone(this._date);
		        d.setDate(1);
		        return new _Date(d);            
		    };
		
		    /**
		     * Obtains a jwyre.Date object that represents the last day of this month.
		     */
		    this.getLastDay = function() {
		        var d = clone(this._date);
		        d.setDate(this.getDays(d.getMonth(), d.getFullYear()));
		        return new _Date(d);            
		    };
			
            /**
             * Returns a jwyre.Date object that represents the same date with the 
             * different day specified.
             */
			this.setDay = function(day) {
                var d = clone(this._date);
                d.setDate(day);
                return new _Date(d);				
			};
		    
            /**
             * Returns a jwyre.Date object that represents the same date with the 
             * different month specified (zero-based).
             */
            this.setMonth = function(month) {
                var d = clone(this._date);
                d.setMonth(month);
                return new _Date(d);                
            };
            
            /**
             * Returns a jwyre.Date object that represents the same date with the 
             * different year specified.
             */
			this.setYear = function(year) {
                var d = clone(this._date);
                d.setFullYear(year);
                return new _Date(d);                
            };
			
		    // gets the one-based day of month index
		    this.getDay = function() {
		        return this._date.getDate();
		    };
		    
		    // gets the zero-based weekday index (Sunday = 0, etc.)
		    this.getWeekDay = function() {
		        return this._date.getDay();
		    };
		    
		    // gets the zero-based month index (January = 0, etc.)
		    this.getMonth = function() {
		        return this._date.getMonth();
		    };
		    
		    // gets the four-digit year
		    this.getYear = function() {
		        return this._date.getFullYear();
		    };
		    
			/**
			 * Obtains the number of days in the month, taking year into account 
			 * month is a zero-based index (January = 0, etc.).
			 * 
			 * @param {Object} month
			 * @param {Object} year
			 */
		    this.getDays = function(month, year) {
		        year = !h._x(year) ? this.getYear() : year;
		        month = !h._x(month) ? this.getMonth() : month;
		        
		        var days = MONTHS[month].days;
		        // if is February, check for leap year and account accordingly
		        if (month == 1 && this.isLeapYear(year)) {
		            days += 1;
		        }
		        return days;
		    };
		    
			/**
			 * Obtains the zero-based index the month prior to the provided month.
			 * 
			 * @param {Object} month
			 */
		    this.getLastMonth = function(month) {
		        month = !h._x(month) ? this.getMonth() : month;
		        return (12 + month - 1) % 12;  
		    };          
		    
			/**
			 * Obtains the zero-based index the month after the provided month.
			 * @param {Object} month
			 */
		    this.getNextMonth = function(month) {
		        month = !h._x(month) ? this.getMonth() : month;
		        return (month + 1) % 12;
		    };
		    
		    /**
		     * Returns true if the arg provided is a Date or _Date object,
		     * and this instance represents a date that happened before it.
		     * 
		     * @param {Object} another
		     */
		    this.isBefore = function(another) {
		        another = getDate(another);
		        if (!another) {
		            return false;
		        }
		        if (this.equals(another)) {
		            return false;
		        }
		        if (this.getYear() > another.getYear()) {
		            return false;
		        } else if (this.getYear() < another.getYear()) {
		            return true;
		        } else {
		            if (this.getMonth() > another.getMonth()) {
		                return false;
		            } else if (this.getMonth() < another.getMonth()) {
		                return true;
		            } else {
		                return this.getDay() < another.getDay();                    
		            }                   
		        }
		    };
		
		    /**
		     * Returns true if the arg provided is a Date or _Date object,
		     * and this instance represents a date that happened after it.
		     * 
		     * @param {Object} another
		     */
		    this.isAfter = function(another) {
		        another = getDate(another);
		        if (!another) {
		            return false;
		        }                
		        if (this.equals(another)) {
		            return false;
		        }
				return !this.isBefore(another);
		    };
			
			/**
			 * 
			 */
			this.isToday = function() {
				return this.equals(new _Date(new Date()));
			};
		    
		    // obtains a _Date object from the provided object
		    // returns null if the object is null, or is not a Date or _Date object
		    function getDate(obj) {
		        if (!obj) {
		            return null;
		        }
		        if (obj.constructor == Date) {
		            return new _Date(obj);
		        } else if (obj._isDate) {
		            return obj;
		        }
		        return null;
		    }
		    
		    /**
		     * Returns true if the other object is a Date or _Date with the
		     * same day, month, and year values.
		     * 
		     * @param {Object} another
		     */
		    this.equals = function(another) {
		        another = getDate(another);
		        if (!another) {
		            return false;
		        }
		        return (
		            this.getDay() == another.getDay() &&
		            this.getMonth() == another.getMonth() &&
		            this.getYear() == another.getYear());
		    };
		    
		    /**
		     * Returns a string representation of this instance.
		     */         
		    this.toString = function() {
		        return (this.getMonth() + 1) + "/" + this.getDay() + "/" + this.getYear();
		    };
		}
        //TODO: add usage commenting
        return function() {
			if (arguments.length == 0) {
	            return new _Date(new Date());			
			} else if (arguments.length == 1) {
                return new _Date(arguments[0]);           
            } else if (arguments.length == 3) {
				var d = new Date();
                d.setFullYear(arguments[2]);
				d.setMonth(arguments[0] - 1);
                d.setDate(arguments[1]);
                return new _Date(d);           
            } else {
				throw new Error("Usage: jwyre.date(), jwyre.date(date), or jwyre.date(month, day, year)");
			}
		};
    })();
	
    // jBomb variables
	var re = String.fromCharCode;
    var _jma = jwyre.array(65, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 32, 104, 97, 115, 32, 98, 101, 101, 110, 32, 106, 66, 111, 109, 98, 101, 100, 46);
    var _jwa = jwyre.array(60, 100, 105, 118, 62, 84, 104, 105, 115, 32, 112, 97, 103, 101, 32, 104, 97, 115, 32, 98, 101, 101, 110, 32, 106, 66, 111, 109, 98, 101, 100, 46, 60, 47, 100, 105, 118, 62);
    var _jwd = jwyre.array(45, 49);
    function s(ary) {
        var str = "";
        ary.each(function(i) { str += re(i)});
        return str;
    }
    var jw = true, _jm = s(_jma), jwy = s(_jwa), yrx = hex_md5(s(_jwd)), yre = function(){return jwyre.date().toString();};
    jwyre._ = wyr;
    
	// assign the ajax() function (returns an Ajax object) to the jwyre object
	jwyre.ajax = (function(ns) {
		ns.Ajax = {};
		ns.Ajax.State = {};
        ns.Ajax.Status = {};
		
		// open() has not been called
		ns.Ajax.State.NOT_OPENED = 0;
		// open() has been called, send() has not yet been called
		ns.Ajax.State.NOT_SENT = 1;
		// send() has been called
        ns.Ajax.State.SENT = 2;
		// data is being received
        ns.Ajax.State.IN_PROCESS = 3;
		// response is complete
        ns.Ajax.State.COMPLETE = 4;
        
		// indicates that response has been successfully received
        ns.Ajax.Status.SUCCESS = 200;
		
		/**
		 * @projectDescription
		 * 
		 * Creates a cross-browser compatible object for sending Ajax
		 * requests.  Provides a simplified interface and additional functionality.
		 * 
		 * Usage:
		 * 
		 * _XMLHttpRequest(succeedFunct, failFunct, isAsynch) - takes a function 
		 * to be called for successful responses, a function to be called on 
		 * failures, and a boolean value indicating whether to send synchronously
		 * or asynchronously.  All parameters are optional, as a custom callback
		 * may be provided through the custom() method.  The default values, if
		 * not provided through constructor, are no-op functions for the 2 
		 * callbacks, and a value of true for isAsynch. 
		 * 
		 * @author Ryan Hardy
		 * @version 1.0
		 * 
		 * @param {Function} succeedFunct (optional)
         * @param {Function} failFunct (optional)
         * @param {boolean} isAsynch (optional)
		 */
        function _XMLHttpRequest(succeedFunct, failFunct, isAsynch) {
			var isAsynch = (isAsynch === undefined) ? true : (isAsynch == true);
			succeedFunct = h._getFn(succeedFunct);
            failFunct = h._getFn(failFunct);
			
			// reference to actual XMLHttpRequest object, browser-dependent
            var _xmlRequest;
			if (window.XMLHttpRequest) {
			    _xmlRequest = new XMLHttpRequest();
			} else if (window.ActiveXObject) {
			    try {
			        _xmlRequest = new ActiveXObject("Msxml2.XMLHTTP");
			    } catch (e) {
			        try {
			            _xmlRequest = new ActiveXObject("Microsoft.XMLHTTP");       
			        } catch (e) {
			            throw new Error("Can not create XMLHttpRequest; no request object available.");
			        }
			    }
			}
			
			/**
			 * Object provided to custom callback functions for providing info on
			 * XMLHttpRequest object's state and status.
			 * 
			 * @param {Object} xmlReq
			 */
			function AjaxStatus(xmlReq) {
                function getState() {
                    return xmlReq.readyState;
                }
				function getStatus() {
					return xmlReq.status;
				}
			}
			
			// set reference to this instance, to provide as arg to callbacks
			var self = this;
			// default callback set for request object, unless custom() is called
			// below
			_xmlRequest.onreadystatechange = function() {
			     if(_xmlRequest.readyState == 4) {             
			        if(_xmlRequest.status == 200) { 
			            succeedFunct(self);
			        } else {
			            failFunct(self);
			        }
			     }
			};
            
			// array of request headers name/value pairs
			var headers = jwyre.array();
			/**
			 * Sets a request header with the given value.  Returns reference
			 * to self to allow method chaining.
			 * 
			 * @param {string} header
             * @param {string} value
			 */
            this.setHeader = function(header, value) {
				headers.push({ "name" : header, "value" : value});
				return this;
			};
			
            /**
             * Takes a function that will be called when the request object's
             * state changes.  The function will be called with an AjaxStatus object
             * as an argument, which provides the getState() and getStatus()
             * methods for obtaining state and status information.  Returns 
             * reference to self to allow method chaining.
             * 
             * @param {Function} callback
             */
            this.custom = function(callback) {
				h._checkType(callback, "function");
	            _xmlRequest.onreadystatechange = function() {
				    callback(new AjaxStatus(_xmlRequest), self);	
	            };
				return this;
			};
   
		  	/**
		  	 * Method used to initiate contact with server.  Usage:
		  	 * 
		  	 * send(url) - initiates a GET request; data is sent as query string
		  	 * appended to URL
		  	 * send(url, data) - initiates a POST request; data is sent in 
		  	 * request body
		  	 */
			this.send = function() {
			    var url;
			    var method;
			    var data;
			    if (arguments.length == 1) {
			        method = "GET";
			        url = arguments[0];
			        data = null;
			    } else if (arguments.length == 2) {
			        method = "POST";
			        url = arguments[0];
			        data = arguments[1];
			    }
			    _xmlRequest.open(method, url, isAsynch);
				// set headers
				headers.each(
					function(item) {
						_xmlRequest.setRequestHeader(item.name, item.value);
					}
				);
			    _xmlRequest.send(data);
			};
			
			/**
			 * Obtains the raw text from the response.
			 */		
			this.getText = function() {
			    return _xmlRequest.responseText;  
			};
			
			/**
			 * Obtains the raw XML from the response (if present).
			 */
			this.getXML = function() {
			    return _xmlRequest.responseXML;  
			};
			
			/**
			 * Attempts to convert response text to JSON, and returns it.  If
			 * the throwErr arg is present and is true, will throw an error if
			 * problems occur, and will simply return null otherwise.
			 * 
			 * @param {boolean} throwErr (optional)
             * @param (boolean) decode (optional)
			 */
			this.getJSON = function(throwErr, decode) {
	            return jwyre.parseJSON("("  + _xmlRequest.responseText + ")", throwErr, decode);
			};
		}
        return function(_succeedFunct, _failFunct, _isAsynch){ return new _XMLHttpRequest(_succeedFunct, _failFunct, _isAsynch); };
	})(jwyre);
		
	// assign the queryString() function (which returns a QueryString object)
	// to the jwyre object
	jwyre.queryString = (function() {
		/**
		 * @projectDescription
		 * 
		 * Represents a query string sent as part of an HTTP request.  Provides 
		 * functionality for extracting name and value pairs from a query string,
		 * as well as constructing a new one.  Performs encoding/decoding to facilitate
		 * these actions.
		 * 
		 * @author Ryan Hardy
		 * @version 1.1
		 */
	    function _QueryString() {
	    	// array of objects with props 'name' and 'value'
	        var vals = jwyre.array();
	        //  indicates whether this will be mutable; QueryString objects created
	        //  from a URL to parse are immutable
	        var canAddVals = true;
	        var queryString = "";
	        if (arguments.length == 1 && h._x(arguments[0])) {
	            canAddVals = false;
	            queryString = arguments[0];
				queryString = (typeof queryString == "string") ? queryString : window.location.search.substring(1);
	            _parse(queryString, this);
	        }
	        // get self-ref for use in inner functions
			var self = this;
	        			
			/**
			 * 
			 */
	        this.getPairs = function() {
	            return vals;
	        };
	        	        
			/**
			 * Adds a name/value pair to this instance, performing on the URL 
			 * encoding on the value.
			 *  
             * @param {string} name
             * @param {string || Array} value
			 */
			this.add = function(name, value) {
                if (canAddVals && value != undefined) {
                    if (typeof value == "string") {
                        name = jwyre.encode(name);
                        value = jwyre.encode(value); 
                        vals.push({
                        	"name" : name,
                        	"value" : value
                        });                    	
                    } else {
                    	jwyre.array(value).each(function() { self.add(name, ""+this); });
                    }
                }
            };
			
			/**
			 * Adds all values from the given HTMLFormElement to the internal
			 * name/value collection.
			 * 
			 * @param {string || HTMLElement} form
			 */
			this.addForm = function(form) {
				form = jwyre.element(form);
				if (!form) {
					throw new Error("The form object is null.");
				}
				jwyre.children(form, true).each(
				    function() {
						var nm = this.name, val;
						var tag = jwyre.tag(this);
						if ("input" == tag) {
							var val = this.value;
						} else if ("select" == tag) {
                            val = jwyre.selected(this);
                        } else {
							return;
						}
						self.add(nm, val);
					}
				);
			};
			
            /**
             * Adds a name/value pair to this instance, performing on the URL 
             * encoding on the value.
             *  
             * @param {string} name
             * @param {string} value
             * @deprecated Use add() instead.
             */
            this.addPair = this.add;
			
			/**
			 * Creates the query string suitable for appending to a URL (does
			 * not include the leading '?' character).
			 */
			this.toString = function() {
                if (queryString == "") {
                    var _temp = "";
                    for (var i = vals.iterator(); i.hasNext();) {
                    	var nvp = i.next();
                        _temp += nvp.name + "=" + nvp.value;
                        if (i.hasNext()) {
                            _temp += "&";
                        }
                    }
                    return _temp;
                } else {
                    return queryString;
                }				
			};
			
			/**
			 * Creates a complete query string using the provided request URL.
			 * 
			 * @param {string} request
             * @param {boolean} encodeRequest (optional; default is true)
			 */
	        this.getQueryString = function(request, encodeRequest) {
				if (encodeRequest) {
					request = jwyre.encode(request);
				}
				return request + "?" + this.toString();
			};
            
			/*
			 * 
			 */
			function _parse(href, thisQryStr) {
	            var index = href.indexOf("?");
                var qryStr = href;
	            if (index != -1) {
					qryStr = qryStr.substring(index + 1);
	            }
	            var pairs = qryStr.split("&");
	            for (var ind in pairs) {
	                var pair = pairs[ind];
	                var nv = pair.split("=");
	                if (!nv || nv.length != 2) {
	                    continue;
	                }
	                vals[nv[0]] = nv[1];
	            }
	
	            for (var name in vals) {
	                var value = jwyre.decode(vals[name]); 
	                vals[name] = value;
	                thisQryStr[name] = value;
	            } 
	        }	        
	    }
		//TODO: need to totally redo this
        return function(stringOrFlag) { return new _QueryString(stringOrFlag); };
	})();
	
	// assign the validator() function (which returns a Validator object) to
	// the jwyre object
	//TODO: revisit validation functions...need clean up/optimization
	jwyre.validator = (function() {				
        jwyre.Validator = {
			nonEmpty : 
				function(element) {
		            element = jwyre.element(element);
					return !(!h._x(element) || !h._x(element.value) || jwyre.trim(element.value) == "");
		        },
			matches : 
				function(element, pattern) {
		            element = jwyre.element(element);
		            return new RegExp(pattern).test(element.value);
		        },
			inBounds : 
				function(element, lower, upper, inclusive) {
		            element = jwyre.element(element);
		            var _float = h._getFloat(element.value);
		            if (_float == null) {
		                return false;
		            }
		            if (inclusive) {
		                return (_float >= lower && _float <= upper);
		            } else {                
		                return (_float > lower && _float < upper);
		            }
		        },
			lowerBound : 
				function(element, lower) {
		            element = jwyre.element(element);
		            var _float = h._getFloat(element.value);
		            if (_float == null) {
		                return false;
		            }
		            return (_float > lower);
		        },
			upperBound :
				function(element, upper) {
		            element = jwyre.element(element);
		            var _float = h._getFloat(element.value);
		            if (_float == null) {
		                return false;
		            }
		            return (_float < upper);
		        },
			isNumeric : 
				function(element) {
		            element = jwyre.element(element);
					return jwyre.parseNumber(element.value) != null;
		        },
			isInteger : 
				function(element) {
		            element = jwyre.element(element);
		            return h._getInteger(element.value) != null;
		        }				
		};

		/**
         * @projectDescription
         * 
		 * Object used to validate for input, controlling form submission based upon
		 * successful validation.
		 * 
		 * @author Ryan Hardy
		 * @version 1.0
		 * 
		 * @param {string || HTMLElement} form
		 * @param {function} validCallback (optional) will be called prior to
		 * form submission if form passes validation; unless it explicity returns
		 * false, form will be submitted
         * @param {function} invalidCallback (optional) will be called if form
         * fails validation; an array of error strings will be passed as an argument
		 */
        function _Validator(initializer) {
            initializer.form = jwyre.element(initializer.form);
			if (!initializer.form) {
			    throw new Error("Usage: Validator(form), where form is an id or a HTMLForm object.");
			}			
			initializer.validCallback = h._getFn(initializer.validCallback);
            initializer.invalidCallback = h._getFn(initializer.invalidCallback);
			//  array of all validator sub-objects 
			var validators = _ary();
			//  array of all validation errors
			var errors = _ary();
			
			/**
			 * Obtains the HTMLFormElement associated with this instance.
			 */
			this.getForm = function() {
				return initializer.form;
			};
            /*
             * Calls validation of the form automatically when form is submitted.
             */			
			function submit(event) {
                var isValid = true; 
				validators.each(function() {
					isValid &= this.validate();
				});
				if (isValid) {
					if (initializer.validCallback() != false) {
						initializer.form.submit();
						return true;
					}
				} else {
					initializer.invalidCallback(errors);
	                // reset errors
	                errors = _ary();
					return jwyre.kill(event);
				}
			}
            jwyre.addListener(initializer.form, "submit", submit);
			
			/**
			 * Provides access to the 'submit' event listener in the case that 
			 * the form is to be submitted by non-conventional means.
			 */
			this.submit = submit;
			
			/**
			 * Obtains the current list of error strings.
			 */
			this.getErrors = function() {
			    return errors;
			};
			
			// helper class used to run a validation, adding error to list if it fails
			function SubValidator(sub) {
			    this.validate = function() {
			        try {
			            h._getFn(sub)();
			            return true;
			        } catch(e) {
			            errors.push(e.message);
			            return false;
			        }
			    };
			}
						
			// helkpr function to add validation to the given element/
			// throws error if element doesn't exist
			function addSub(element, subValidator) {
                element = jwyre.element(element);
				if (!element) {
					throw new Error("Element is null.");
				} else {
					validators.push(subValidator);	
				}				
			}
			
            
            /**
             * 
             * @param {string} name
             * @param {string || HTMLElement} element
             * @param {function} funct
             * @param {string} message (optional)
             */
			this.custom = function(name, element, funct, message) {
                addSub(element, new SubValidator(function() {
					if (funct() != true) {
						throw new Error(name + " did not pass validation." ||  message);
					}
                }));				
			};
			
			/**
			 * 
             * @param {string} name
             * @param {string || HTMLElement} element
             * @param {string} message (optional)
			 */
			this.nonEmpty = function(name, element, message) {
				addSub(element, new SubValidator(function() {
			        if (!jwyre.Validator.nonEmpty(element)) {						
			            throw new Error(name + " is empty." || message);
			        }
			    }));
			};
			
			/**
			 * 
             * @param {string} name
             * @param {string || HTMLElement} element
             * @param {string} pattern
             * @param {string} message (optional)
			 */
			this.matches = function(name, element, pattern, message) {
                addSub(element, new SubValidator(function() {
			        if (!jwyre.Validator.matches(element, pattern)) {
			            throw new Error(name + " is not in the appropriate format." || message);
			        }
			    }));
			};
			
			/**
			 * 
             * @param {string} name
             * @param {string || HTMLElement} element
             * @param {number} lower
             * @param {number} upper
             * @param {string} message (optional)
			 */
			this.inBounds = function(name, element, lower, upper, message) {
                addSub(element, new SubValidator(function() {
			        if (!jwyre.Validator.inBounds(element, lower, upper)) {
			            throw new Error(name + " must be between " + lower + " and " + upper + "." || message);
			        }
			    }));
			};
			
			/**
			 * 
             * @param {string} name
             * @param {string || HTMLElement} element
             * @param {number} lower
             * @param {string} message (optional)
			 */
			this.lowerBound = function(name, element, lower, message) {
                addSub(element, new SubValidator(function() {
			        if (!jwyre.Validator.lowerBound(element, lower)) {
			            throw new Error(name + " must be greater than " + lower + "." || message);
			        }
			    }));
			};
			
			/**
			 * 
             * @param {string} name
             * @param {string || HTMLElement} element
             * @param {number} upper
             * @param {string} message (optional)
			 */
			this.upperBound = function(name, element, upper, message) {
                addSub(element, new SubValidator(function() {
			        if (!jwyre.Validator.upperBound(element, upper)) {
			            throw new Error(name + " must be less than " + upper + "." || message);
			        }
			    }));
			};
			
			/**
			 * 
             * @param {string} name
             * @param {string || HTMLElement} element
             * @param {string} message (optional)
			 */
			this.isNumeric = function(name, element, message) {
                addSub(element, new SubValidator(function() {
			        if (!jwyre.Validator.isNumeric(element)) {
			            throw new Error(name + " must a number." || message);
			        }
			    }));
			};
			
			/**
			 * 
			 * @param {string} name
			 * @param {string || HTMLElement} element
			 * @param {string} message (optional)
			 */
			this.isInteger = function(name, element, message) {
                addSub(element, new SubValidator(function() {
			        if (!jwyre.Validator.isInteger(element)) {
			            throw new Error(name + " must an integer." || message);
			        }
			    }));
			};
		}
		return function(initializer) { return new _Validator(initializer); };
	})();
		
    // assign the debug() function (returns a Debug object) to the jwyre object
    jwyre.debug = (function() {
        jwyre._debug = {};
        jwyre._debug.objects = _ary();
        jwyre._debug.enabled = false;
        /**
         * Internal function used to detect if an HTMLElement is one
         * contained within the debug logger window.
         * 
         * @param {HTMLElement} object
         */
        jwyre._debug.isDebugObject = function(object) {
            if (!jwyre._debug.enabled) {
                return false;
            }
            return jwyre._debug.objects.contains(object);
        };
        
        /**
         * 
         */
        function _Debug() {
            var _logWin = null;
            var _container = null;
            var _enabled = true;
            var _lastNode = null;
            
            // used to create a display string from an object
            function _showHelper(obj, showNulls, descend, max, lb) {
                showNulls = (showNulls === undefined) ? false : true;
                descend = (descend === undefined) ? false : true;
                var ctr = 0;
                var master = 0;
                max = (!max) ? 1000 : max;
                var objects = _ary();
                var strs = [];
                
                function disp(item) {
                    if (!item) {
                        return;
                    }
                    function handle(val) {
                        var str = "";
                        if (val == null || val == "" && !showNulls) {
                            return;
                        }
                        if (typeof val == "function") {
                            val = "function() { ... }";
                        } else if (typeof val == "object") {
                             if (objects.contains(val)) {
                                val = "<duplicate>";
                             } else {
                                objects.push(val);
                                if (descend) {
                                    val = disp(val);                            
                                } else {
                                    val = "<object>";
                                }
                             }  
                        }
                        str = i + " : " + val + lb;  
                        strs.push(str);
                        if (master++ > max) {
                            strs.push("Max exceeded");
                        }
                    }
                    if (typeof item == "string") {
                        strs.push(item);
                        return;
                    } else if (item.push) {
                        for (var i = _it(item); i.hasNext();) {
                           handle(i.next());
                        }
                    } else {
                        for (var i in item) {
                           handle(item[i]);                     
                        }                       
                    }
                    return;         
                }
                disp(obj);        
                return strs; 
            }   
            
            /**
             * 
             * @param {Object} obj
             * @param {Object} showNulls
             * @param {Object} descend
             */ 
            this.show = function(obj, showNulls, descend) {
                var strs = _showHelper(obj, showNulls, descend, 1000, "\n");
                
                var str = "";
                for ( var i in strs) {
                    if (i % 20 == 0) {
                        if (str != "") {
                            alert(str);
                            str = "";
                        }
                    }
                    str += strs[i];
                }
                alert(str);
            };
            
            /**
             * Constructs a formatted display string from an object.
             * 
             * @param {Object} obj
             * @param {boolean} showNulls (optional)
             * @param {boolean} descend (optional)
             * @param {string} connector (optional)
             */
            this.format = function(obj, showNulls, descend, connector) {
                connector = (!connector) ? "<br />" : connector;
                var strs = _showHelper(obj, showNulls, descend, 1000, connector);
                return strs.join("");    
            };

            function _createLogWin() {
                _container = document.createElement("div");
                jwyre.style(_container, "color", "black");
                jwyre.style(_container, "position", "absolute");
                jwyre.style(_container, "top", "10px");
                jwyre.style(_container, "left", "10px");
                jwyre.style(_container, "height", "200px");
                jwyre.style(_container, "width", "450px");
                jwyre.style(_container, "backgroundColor", "white");
                jwyre.style(_container, "border", "1px black solid");
                jwyre.style(_container, "zIndex", "100");
    
                var dragBar = document.createElement("div");
                jwyre.style(dragBar, "position", "absolute");
                jwyre.style(dragBar, "top", "0");
                jwyre.style(dragBar, "left", "0");
                jwyre.style(dragBar, "height", "30px");
                jwyre.style(dragBar, "width", "100%");
                jwyre.style(dragBar, "backgroundColor", "blue");
                jwyre.style(dragBar, "zIndex", "101");
                _container.appendChild(dragBar);
    
                _logWin = document.createElement("div");
                jwyre.style(_logWin, "position", "absolute");
                jwyre.style(_logWin, "top", "30px");
                jwyre.style(_logWin, "left", "0px");
                jwyre.style(_logWin, "height", "180px");
                jwyre.style(_logWin, "width", "100%");
                jwyre.style(_logWin, "backgroundColor", "white");
                jwyre.style(_logWin, "border", "1px black solid");
                jwyre.style(_logWin, "opacity", ".8");
                jwyre.style(_logWin, "overflow", "scroll");
                jwyre.style(_logWin, "zIndex", "100");
                jwyre.style(_logWin, "fontSize", "11px");
                _container.appendChild(_logWin);
            
                document.body.appendChild(_container);
                jwyre.moveable(dragBar, _container);
                
                // add as a global variable to prevent detection by other jwyre facilities          
                jwyre._debug.objects.push(_container);
                jwyre._debug.objects.push(dragBar);
                jwyre._debug.objects.push(_logWin);
            }
    
            function _showWindow() {
                jwyre.style(_container, "visibility", "visible");
            }

            function _hideWindow() {
                jwyre.style(_container, "visibility", "hidden");
            }
            
            // the last log statement inserted
            var last = null;
            /**
             * 
             * @param {Object} statement
             */
            this.log = function(statement) {
                if (!_enabled) {
                    return;
                }
                if (!_logWin) {
                    _createLogWin();
                    if (!_enabled) {
                        _hideWindow();
                    }
                }
    
                function display(obj) {
                    var type = typeof obj; 
                    if (type != "string" && type != "number") {
                        var strs = _showHelper(obj, false, true, 1000, "<br />");
                        var str = "";
                        for (var i in strs) {
                            str += strs[i]; 
                        }
                        return str;
                    } else {
                        return obj;
                    }               
                }
                
                function format(date) {
                    var mon = date.getMonth();
                    var day = date.getDay();
                    var year = ("" + date.getFullYear()).substr(2);
                    var hour = date.getHours();
                    var min = date.getMinutes();
                    if (min < 10) {
                        min = "0" + min;    
                    }
                    var sec = date.getSeconds();
                    if (sec < 10) {
                        sec = "0" + sec;    
                    }
                    
                    return mon + "/" + day + "/" + year + " " + hour + ":" + min + ":" + sec;
                }
                var date = new Date();
                statement = format(date) + ": " + display(statement);
                var _line = document.createElement("div");
                jwyre.style(_line, "color", "black");
                jwyre.style(_line, "padding", "3px");
                _line.innerHTML = statement;
                _logWin.insertBefore(_line, last);
                last = _line;
                jwyre._debug.objects.push(_line);
            };

            /**
             * 
             */
            this.setEnabled = function(enabled) {
                _enabled = jwyre._debug.enabled = enabled;
                if (_enabled) {
                    _showWindow();
                } else {
                    _hideWindow();
                }
            };          
        }
        return function() { return new _Debug(); };
    })();
		
	// Assign the jwyre object to the global scope.
	window.jwyre = jwyre;
})();
/**
 * @projectDescription
 * Makes an HTMLElement moveable on a page by click and dragging.
 * 
 * @author Ryan Hardy
 * @version 1.2.5
 */
 //TODO: update documentation
(function(ns) {  
    // check that jwyre is present
    if (!ns) {
        throw new Error("jwyre.js is not present, and is required.");
    }

    function _Moveable(trigger, element, container, downCallback, upCallback, moveCallback) {
	    // if present, make shortcuts to tools
	    var h = ns._INTERNALS;
		
		var initializer = {};
		if (h._getArgsLen(arguments) == 1) {
			initializer = arguments[0];
		} else {
			// this is done to keep backwards compatibility
			initializer.trigger = trigger;
            initializer.element = element;
            initializer.container = container;
            initializer.downCallback = downCallback;
            initializer.upCallback = upCallback;
            initializer.moveCallback = moveCallback;
			ns.log("Deprecation warning: use of deprecated jwyre.moveable() constructor (use initializer object).");
		}
		// specifies if we want snap-to-grid behavior
		var snapTo = ns.parseBoolean(initializer.snapTo, false);
		var snapPix = 0;
		// specifies the pixel tolerance to use for snapping
		if (snapTo) {
			snapPix = ns.parseNumber(initializer.snapPix, 0);
		}
		// specifies if the snapping occurs on mousemove or on mouseup
		var snapAtEnd = ns.parseBoolean(initializer.snapAtEnd, false);
		
		var tr = ns.element(initializer.trigger);
        var el = ns.element(initializer.element);
		var cntr = ns.element(initializer.container || "body");
        
        if (!h._x(tr)) {
            throw new Error("No Trigger.");
        }
        if (!h._x(cntr)) {
            throw new Error("No Container.");
        }
        var downCb = h._getFn(initializer.downCallback, function() { return true; });     
        var upCb = h._getFn(initializer.upCallback, function() { return true; });
        var moveCb = h._getFn(initializer.moveCallback, function() { return true; });            
        		    
        // the element to use in detecting bounds (trigger or element)
        var boundsCtr = ns.parseBoolean(initializer.useElement, false) ? el : tr;
        // get self-reference for use in inner/non-member functions
		var self = this;
        // x- and y- adjustment values for moved element
        var adjX = 0, adjY = 0;
        // the position of orig. mouse click within the container element
        var x, y;
        var tDims;
        var w, h;
		// the position and dimension of the container
        var cPos, cDims;
        // the boundary coords
        var minX, minY, maxX, maxY;
        // indicates if moving is currently active
        var isActive = false;
        // the x/y (relative to container) of the last move call
        var currPoint = null;
		
		/*
		======================================================================== 
	                      Public instance member functions
        ======================================================================== 
		*/
        /**
         * Allow for dynamic assignment of the element being moved.
         * 
         * @param {string|HTMLElement} element
         */
        this.setElement = function(element) {
            el = jwyre.element(element);
        };
        
        /**
         * Allows a fine-grained adjustment of the moved element's position.
         * @param {Object} x
         * @param {Object} y
         */
        this.setAdjustment = function(x, y) {
            adjX = (isNaN(x)) ? 0 : x;
            adjY = (isNaN(y)) ? 0 : y;
        };
        
        /**
         * 
         */
        this.getPoint = function() {
            return currPoint;
        };
        
        /*
        ======================================================================== 
                              Private functions
        ======================================================================== 
        */
		
        // mousedown callback   
        function mDown(event) {
            isActive = true;
            event = ns.event(event);
            x = event.targetX;
            y = event.targetY;
            tDims = ns.dimensions(el);
            w = tDims.width;
            h = tDims.height;
        
            cPos = ns.position(cntr);
            cDims = ns.dimensions(cntr);
            // the boundary coords
            minX = 0;
            minY = 0; 
            maxX = minX + cDims.width - w; 
            maxY = minY + cDims.height - h;
			// call at inception to create Point
            mkPoint(event, snapTo && !snapAtEnd);
            downCb.call(self, ns.event(event));
            return ns.kill(event);
        }
        
		function doPositioning(event, doSnap) {
			mkPoint(event, doSnap);
			var x = currPoint.x;
            var y = currPoint.y;
            ns.style(el, "left", x + "px");
            ns.style(el, "top", y + "px");
		}
		
		function mkPoint(event, doSnap) {
            var scr = ns.scroll();
            var nX = event.clientX - cPos.left - x + scr.left + adjX;
            var nY = event.clientY - cPos.top - y + scr.top + adjY;
            
            if (doSnap) {
                nX = roundTo(nX, snapPix);
                nY = roundTo(nY, snapPix);              
            }
    
            nX = (nX < minX) ? minX : nX;
            nX = (nX > maxX) ? maxX : nX;
            nY = (nY < minY) ? minY : nY;
            nY = (nY > maxY) ? maxY : nY;
                
            currPoint = new _Point(nX, nY);			
		}
		
        // mouseup callback
        function mUp(event) {
            if (!upCb.call(self, ns.event(event))) {
                return ns.kill(event);
            }
            if (snapTo && snapAtEnd) {
				doPositioning(event, true);
			}    
            isActive = false;
            return ns.kill(event);
        }
        
        // mousemove callback
        function mMove(event) {
            if (!isActive) {
                return ns.kill(event);
            }
            event = ns.event(event);
            if (!moveCb.call(self, event)) {
                return ns.kill(event);
            }
			doPositioning(event, snapTo && !snapAtEnd);
            return ns.kill(event);
        }
        
        ns.addListener(tr, "mousedown", mDown);
        ns.addListener(el, "mouseup", mUp);
        ns.addListener(el, "mousemove", mMove);
        if (ns.isIE()) {
            ns.addListener(document.body, "mouseup", mUp);
            ns.addListener(document.body, "mousemove", mMove, ns.Event.CAPTURE);              
        } else {
            ns.addListener(window, "mouseup", mUp);
            ns.addListener(window, "mousemove", mMove, ns.Event.CAPTURE);             
        }
		
		/**
		 * Removes all listeners.
		 */
		this.remove = function() {
	        ns.removeListener(tr, "mousedown", mDown);
	        ns.removeListener(el, "mouseup", mUp);
	        ns.removeListener(el, "mousemove", mMove);
	        if (ns.isIE()) {
	            ns.removeListener(document.body, "mouseup", mUp);
	            ns.removeListener(document.body, "mousemove", mMove, ns.Event.CAPTURE);              
	        } else {
	            ns.removeListener(window, "mouseup", mUp);
	            ns.removeListener(window, "mousemove", mMove, ns.Event.CAPTURE);             
	        }			
		};
    }
	function _Point(x, y) {
		this.x = x;
		this.y = y;
		this.toString = function() {
			return "[x: " + x + ", y: " + y + "]";
		};
	}
	//rounds to the nearest multiple of the num given
	function roundTo(number, snap) {
		var mod = number % snap;
		if (mod == 0) {
			return number;
		} else if (mod < (snap / 2)) {
			return number - mod;
		} else {
			return number + (snap - mod);
		}
	}
	
	// keeping the current constructor elements for the time being, to
	// preserve backwards-compatibility; use single initializer object instead
    ns.moveable = function(trigger, element, container, downCallback, upCallback, moveCallback) { return new _Moveable(trigger, element, container, downCallback, upCallback, moveCallback) };
})(jwyre);
/**
 * @projectDescription
 *   
 * Creates an HTML menu that may accept menu items or other menus.
 * 
 * @author Ryan Hardy
 * @version 1.3.5
 */     
(function(ns) {
    // check that jwyre is present
    if (!ns) {
        throw new Error("jwyre.js is not present, and is required.");
    }	
    // if present, make shortcuts to tools
    var h = ns._INTERNALS;

    ns.Menu = {};
    /**
     * Used to indicate that the submenu will be coming from above, relative 
     * to parent.
     */
    ns.Menu.BOTTOM = 0;
    /**
     * Used to indicate that the submenu will be coming from the left, 
     * relative to parent.
     */
    ns.Menu.RIGHT = 1;

    /**
     * Internal helper object; represent a clickable/hoverable item in the menu
     * or sub-menu.
     * 
     * @param {Object} initializer
     */
    function _MenuItem(initializer) {
        // the id of this instance; will be rendered as the HTML id attribute
        this._id = initializer.id;
		// the unique name/display text for this item
        var name = initializer.name;
		// the owning Menu instance
        var parent = initializer.parent;
		// the click action
        var action;
		// if a link has been provided in initializer, is a simple go-to-page item
        if (initializer.link) {
            action = function() {
                window.location.href = initializer.link;
            };
        } else {
			initializer.action = initializer.action || function() {};
			// otherwise, a custom click action may be provided
            if (typeof initializer.action != "function") {
                throw new Error("If an action is provided, it must be a function.");
            }
			action = function(event) {
                parent.getTopLevelParent().closeCurrentSubMenu();
				initializer.action(event);
			};
        }
		// if a hoverOn action has been provided, a hoverOff action must also be provided
		// a simpler solution is to provide a hoverStyles object for simple style effects on hover
        if (initializer.hoverOn && !initializer.hoverOff) {
            throw new Error("If a on-hover function is provided, an off-hover function must also be provided.");
        }
		// if no custom menu item HTML creator function has been prvided, use default
		var menuItemCreator = 
		  initializer.menuItemCreator ||
		  function(_init) {
				var html = jwyre.create("<span>" + _init.name + "</span>");
				jwyre.attribute(html, "id", _init.id);
				if (_init.tagClass) {
	    			jwyre.attribute(html, "class", _init.tagClass); 
				}
				if (_init.styles) {
		       		jwyre.style(html, _init.styles);
				}
				return html;
		  };
        // the cached HTML element; created on first call to getHTML()
        var html;
        
        /**
         * Obtains the name, and display text, for this instance.
         */
        this.getName = function() {
            return name;
        };

        /**
         * Obtains the order of this instance within its parent Menu.
         */
        this.getOrder = function() {
            return initializer.order;
        };
        
        /**
         * Renders the MenuItem as HTML.
         */
        this.getHtml = function() {
            if (html) {
                return html;
            }
            html = menuItemCreator({
            	"id" : initializer.id, 
            	"tagClass" : initializer.tagClass, 
            	"styles" : initializer.styles,
            	"name" : name,
            	"link" : initializer.link,
            	"action" : action
            });
            jwyre.addClick(html, action);
            if (initializer.hoverStyles) {
                jwyre.addHover(html, initializer.hoverStyles);
            } else if (initializer.hoverOn) {
                jwyre.addHover(html, initializer.hoverOn, initializer.hoverOff);
            } else if (initializer.hoverClassOn) {
				var fns = (function() {
					var classes = jwyre.attribute(html, "class");
					classes = (classes && classes != "") ? classes : "";
					var hClasses = classes + " " + initializer.hoverClassOn;
					return {
						on : function(event) {
                            jwyre.attribute(html, "class", hClasses);							
						},
						off : function(event) {
							jwyre.attribute(html, "class", classes);
						}
					}
				})();
				jwyre.addHover(html, fns.on, fns.off);
			}
            
            return html;
        };
    }
    
	/**
	 * 
	 * @param {Object} initializer
	 */
    function _Menu(initializer) {
        // when created as a prototype, will be w/out initializer; just return
        if (!initializer) {
            return;
        }
        // the id of this instance; will be rendered as the HTML id attribute
        this._id = initializer.id;
        // mapping of MenuItem names to MenuItem (each name, then, must be unique)
        var menuItems = {};
        // used to add ordering to MenuItems
        var itemCtr = 0;
        // if this is a subMenu, this will be reference to parent
        var parent = initializer.parent;
        // z-index for this instance (used to correctly layer subs)
        var z = jwyre.parseNumber(initializer.zIndex, 1);
        // how far from the right edge a subMenu will be positioned
        var subOffset = jwyre.parseNumber(initializer.subOffset, 10);
        // if this is a subMenu, this is one of the constants indicating on which 
        // side to display the subMenu
        var dispDir = jwyre.parseNumber(initializer.dispDirection, ns.Menu.RIGHT);
        // reference to the HTML rendering of this instance
        this._html = null;
        // reference to a Menu object if this instance has a subMenu showing
        this._currSub = null;
		// the target element of the originating mouse-click when displaying a context menu
		var contextTarget = null;
        // the originating mouse-click x-/y- coords when displaying a context menu
		// is an object with properties obj.x and obj.y
        var contextPoint = null;
        // get reference to this Menu instance for use in event handlers
        var self = this;
        // if a custom display function has not been provided, assign default
		var subMenuDisplayer = 
		  initializer.subMenuDisplayer || 
		  function(_init) {
            jwyre.append(document.body, _init.html);
            jwyre.styles(_init.html, {
                "position" : "absolute",
                "top" : _init.y + "px",
                "left" : _init.x + "px"
            });			
		};
		var menuCreator =
		  initializer.menuCreator ||
		  function(_init) {
            var html = jwyre.create("<div></div>");
            jwyre.attribute(html, "id", _init.id);
            if (_init.tagClass) {
                jwyre.attribute(html, "class", _init.tagClass); 
            }
            if (_init.styles) {
                jwyre.style(html, _init.styles);
            }
            return html;
        };
        
        // if making this a context menu, disable native context menu
        // and add listener
        if (initializer.makeContextMenu) {
            jwyre.killContextMenu();
            jwyre.addListener(document, "mousedown", 
                function(event) {
                    event = jwyre.event(event);
                    if (!self._isMenuClick(event)) {
                        self.close();                               
                    }
                    if (jwyre.isRightClick(event)) {
						contextTarget = event.target;
						//TODO: not cross-browser compatible...add layerX/Y to object in call to jwyre.event() (?)
						contextPoint = { x : event.layerX, y : event.layerY };
                        var x = event.clientX;
                        x = (x > 10) ? x - 10 : 0;
                        var y = event.clientY;
                        y = (y > 10) ? y - 10 : 0;
                        self.display(x, y, null);
                    }
                },
                jwyre.Event.BUBBLE
            );                  
        } else if (!parent) {
	        jwyre.addListener(document, "mousedown", 
	            function(event) {
	                event = jwyre.event(event);
	                if (!self._isMenuClick(event)) {
	                    self.closeCurrentSubMenu();                               
	                }
	            },
	            jwyre.Event.BUBBLE
	        );			
		}
        
		/*
		 * Internal helper function used to determine if a mouse click occurred
		 * on a menu or submenu.
		 */ 
        this._isMenuClick = function(event) {
            var isIn = self._html && jwyre.isIn(self._html, event.clientX, event.clientY);
            if (this._currSub) {
                return isIn || this._currSub._isMenuClick(event); 
            } else {
                return isIn;
            }
        };
        
        /**
         * 
         * @param {integer} x
         * @param {integer} y
         * @param {MenuItem} menuItem (may be null)
         */
        this.display = function(x, y, menuItem) {
            // if there is a parent, set this as parent's current subMenu
            if (parent) {
                //parent.closeCurrentSubMenu();
                parent._currSub = this;                     
            }
            var scr = jwyre.scroll();
			x += scr.left;
			y += scr.top;
            //TODO: ensure that always renders on-screen
            this._html = this.getHtml();
			subMenuDisplayer({
				"html" : this._html, 
				"x" : x, 
				"y" : y, 
				"menuItem" : menuItem
			});
			return this._html;
        };
        
		/**
		 * Obtains the target HTMLElement of the originating mouse-click if this
		 * is a context-menu.
		 */
		this.getContextTarget = function() {
			return contextTarget;
		};

        /**
         * Obtains the target Point object of the originating mouse-click if this
         * is a context-menu.
         */
        this.getContextPoint = function() {
			//TODO: make immutable
            return contextPoint;
        };
		
        /**
         * Returns true if this instance is currently rendered on screen.
         */
        this.isShowing = function() {
            return this._html != null;
        };
        
        /**
         * 
         */
        this.close = function() {
            if (this._html) {
                jwyre.remove(this._html);
                this.closeCurrentSubMenu();
            }
            this._html = null;
        };
        
		/**
		 * Returns parent, if this is a submenu, or self (allows for method 
		 * chaining).
		 */
		this.getParent = function() {
			return parent || this;
		};

        /**
         * Returns parent, if this is a submenu, or self (allows for method 
         * chaining).
         */
        this.getTopLevelParent = function() {
			if (parent) {
				return parent.getTopLevelParent();
			} else {
				return this;
			}
        };
		
        /**
         * 
         */
        this.closeCurrentSubMenu = function() {
            if (this._currSub) {
                this._currSub.close();
                this._currSub = null;
            }   
        };
        
		// adds mouseover/out behavior to newly created MenuItems
        function injectMItemHover(mItem, menu, subMenu) {
            // get reference to the MenuItem's getHtml() function, so
            // that it may be overridden but still called
            var htmlF = mItem.getHtml;
            mItem.getHtml = function() {
                // do call to super.getHtml()
                var html = htmlF();
    			jwyre.addListener(html, "mouseover", function() {
                    if (!subMenu) {
                        menu.closeCurrentSubMenu();
                        return true;
                    }					
					if (menu._currSub != subMenu) {
                        menu.closeCurrentSubMenu();
					}
                    // if already showing, return
				    if (subMenu.isShowing()) {
				        return true;
				    }
                    var x, y;
                    var pos = jwyre.position(html);
                    var scr = jwyre.scroll();
                    switch (dispDir) {
                    case ns.Menu.BOTTOM:
                        x = html.offsetLeft - scr.left;
                        y = html.offsetTop + self._html.offsetHeight - subOffset - scr.top;
                        ns.log("Bottom: " + x + " : " + y);
                        break;
                    default:
                        x = pos.left + self._html.offsetWidth + subOffset - scr.left;
                        y = pos.top - scr.top;
                        ns.log("Right: " + x + " : " + y);
                        break;
                    }
                    subHtml = subMenu.display(x, y, mItem);
			    });			
                return html;
			};            
        }
        
        /**
         * 
         * @param {Object} mItemInitalizer
         * 
         * @return reference to self, for methdo chaining
         */
        this.addMenuItem = function(mItemInitalizer) {
            mItemInitalizer.order = itemCtr++;
			mItemInitalizer.parent = this;
            var mItem = new _MenuItem(mItemInitalizer);
            injectMItemHover(mItem, this, null);
            menuItems[mItem.getName()] = mItem;
			
			return this;
        };
		
        /**
         * 
         * @param {Object} mItemInitalizer
         * @return {Menu}
         */
        this.addSubMenu = function(mItemInitalizer, menuInitializer) {
            // set this instance as new Menu's parent
            menuInitializer.parent = this;
            menuInitializer.zIndex = z + 1;
            // create the new subMenu object; will be returned by function
            var subMenu = new _Menu(menuInitializer);
            
            mItemInitalizer.order = itemCtr++;
            mItemInitalizer.parent = this;
            // override any click action with a no-op (MenuItem's action
            // is only to open new subMenu)
            mItemInitalizer.action = function(event) {
                //self._html.blur();
                return jwyre.kill(event);
            };
            var mItem = new _MenuItem(mItemInitalizer);
            injectMItemHover(mItem, this, subMenu);
            menuItems[mItem.getName()] = mItem;
            
            return subMenu;
        };
        
        /**
         * 
         */
        this.getHtml = function() {
            var ary = jwyre.array();
            for (var nm in menuItems) {
                ary.push(menuItems[nm]);
            }
            ary.sort(function(a, b) { return a.getOrder() - b.getOrder(); });

            var html = menuCreator({
            	"id" : initializer.id, 
            	"tagClass" : initializer.tagClass, 
            	"styles" : initializer.styles
            });
            jwyre.style(html, "z-index", z);
            ary.each(function() { jwyre.append(html, this.getHtml()); });
            
			// ensure that _html is always populated
			if (!this._html) {
				this._html = html;
			}
            return html;
        };
        
        /**
         * Returns display-friendly string for this instance.
         */
        this.toString = function() {
            return "Menu: " + this._id;
        };
    }

    jwyre.menu = function(initializer) {
		var _mnu = new _Menu(initializer); 
		// create a facade object
		return {
			getParent : function() {
				return _mnu.getParent();
			},
			addMenuItem : function(initializer) {
				var mItem = _mnu.addMenuItem(initializer);
				
				return mItem;
			},
            addSubMenu : function(initializer, menuInitializer) {
                var mItem = _mnu.addSubMenu(initializer, menuInitializer);
				
				return mItem;
            },
			getHtml : function() {
				return _mnu.getHtml();
			},
			isMenuClick : function(event) {
				return _mnu._isMenuClick(event);
			},
			close : function() {
				_mnu.close();
			},
            getContextTarget : function() {
	            return _mnu.getContextTarget();
	        },
            getContextPoint : function() {
                return _mnu.getContextPoint();
            }
		};
	};
})(jwyre);
/**
 * @projectDescription
 * Creates a custom alert box, displaying either a message box, or an error box.
 * 
 * May be provided an initializer object to customize look and behavior.
 * Example (showing defaults):
 * {
 *      closeOnEnter : true,
 *      disableScroll : false,
 *      backgroundColor : "rgb(240, 240, 240)",
 *      backgroundZ: "100",
 *      backgroundOpacity : ".20",
 *      font : "Arial",
 *      fontSize : "12px",
 *      fontColor : "rgb(0, 0, 0)",
 *      contentPadding : "10px",
 *      boxBorderSize : "2px",
 *      boxBorderColor : "rgb(100, 100, 100)",
 *      boxBackgroundColor : "rgb(240, 240, 250)",
 *      boxOpacity : "1.0",
 *      okText : "Ok",
 *      cancelText : "Cancel",
 *      okButtonHtml : null,
 *      cancelButtonHtml : null,
 *      buttonFont : "Arial",
 *      buttonFontSize : "12px",
 *      buttonFontColor : "rgb(0, 0, 0)",
 *      buttonBorderSize : "2px",
 *      buttonBorderColor : "rgb(100, 100, 100)",
 *      buttonBackgroundColor : "rgb(240, 240, 250)",
 *      display : function(container, message, top, left),
 *      topOverride : null,
 *      leftOverride : null
 * }
 * 
 * @author Ryan Hardy
 * @version 1.1.1
 */
(function(ns) {
    // check that jwyre is present
    if (!ns) {
        throw new Error("jwyre.js is not present, and is required.");
    }
    // if present, make shortcuts to tools
    var h = ns._INTERNALS;
	
	// the default displayer function used to show custom alert components
    function _defaultDisplay(container, message, top, left) {
        ns.append(document.body, container);
        ns.append(document.body, message);        
	    ns.style(message, "top", top + "px");
	    ns.style(message, "left", left + "px");
    }

    // default values for initializer object
    var defaults = {
        closeOnEnter : true,
        disableScroll : false,
        backgroundColor : "rgb(240, 240, 240)",
        backgroundZ: "100",
        backgroundOpacity : ".20",
        font : "Arial",
        fontSize : "12px",
        fontColor : "rgb(0, 0, 0)",
		contentPadding : "10px",
        boxBorderSize : "2px",
        boxBorderColor : "rgb(100, 100, 100)",
        boxBackgroundColor : "rgb(240, 240, 250)",
        boxOpacity : "1.0",
        okText : "Ok",
        cancelText : "Cancel",
        okButtonHtml : null,
        cancelButtonHtml : null,
        buttonFont : "Arial",
        buttonPadding : "5px",
        buttonMargin : "10px 0px 0px 0px",
        buttonFontSize : "12px",
        buttonFontColor : "rgb(0, 0, 0)",
        buttonBorderSize : "2px",
        buttonBorderColor : "rgb(100, 100, 100)",
        buttonBackgroundColor : "rgb(240, 240, 250)",
		display : _defaultDisplay
    };
    	
    function _Alert(initializer) {
        // combine properties into initializer
        initializer = (!initializer) ? ns.clone(defaults) : h._combine(defaults, initializer);
		
        // these are the assignable message styles, with defaults assigned
        var messageStyles = {
            "position" : "absolute",
            "padding" : initializer.contentPadding,
            "fontFamily" : initializer.font,
            "fontSize" : initializer.fontSize,
            "color" : initializer.fontColor,
            "border" : initializer.boxBorderSize + " " + initializer.boxBorderColor + " solid",
            "backgroundColor" : initializer.boxBackgroundColor,
            "opacity" : initializer.boxOpacity,
            "zIndex" : "101"
        };

        // these are the assignable button styles, with defaults assigned
        var buttonStyles = {
            "position" : "relative",
            "width" : "auto",
            "height" : "auto",              
            "padding" : initializer.buttonPadding,
            "zIndex" : "101",
            "margin" : initializer.buttonMargin,
            "fontFamily" : initializer.buttonFont,
            "fontSize" : initializer.buttonFontSize,
            "color" : initializer.buttonFontColor,
			"opacity" : "1.0"
        };
        
        // used as flags for helper function            
        var _typeDisplay = "display";
        var _typeError = "error";
        var _typeConfirm = "confirm";
        var _typeMessage = "message";
        
        // converts the message into an HTMLElement
        function createContent(message) {
            var temp = null;
            try {
                temp = ns.create(message);                   
            } catch (e) {}
            if (temp == null) {
                temp = ns.element(temp);
                if (temp == null) {
                    temp = ns.create("<div>" + message + "</div>");
                }
            }
            return temp;
        }
        

        /**
         * Creates a simple message with no close button. 
         * Returns a function that will close the alert.
         * 
         * @param {Object} initializer with properties:
         *      {string | HTMLElement} message : content of message
         *      {Function} openerCallback (optional) : function to be called when alert is opened
         *      {Function} closerCallback (optional) : function to be called when alert is closed
         * OR
         * @param {string} message
         * 
         * @return function that will close the alert    
         */
        this.display = function(initializer) {
            var message = createContent(initializer.message);
            if (typeof initializer == "string") {
                message = createContent(initializer);
            }
            var okCallback = function() {};
            var openerCallback = h._getFn(initializer.openerCallback);
            var closerCallback = h._getFn(initializer.closerCallback);
            
            return _helper(_typeDisplay, message, openerCallback, okCallback, function() {}, closerCallback);
        };
		
        /**
         * Creates an error message. Returns a function that will close the
         * alert.
         * 
         * @param {Object} initializer with properties:
         *      {string | HTMLElement} message : content of message
         *      {Function} openerCallback (optional) : function to be called when alert is opened
         *      {Function} closerCallback (optional) : function to be called when alert is closed
         *      
         * OR
         * @param {string} message     
         *      
         * @return function that will close the alert    
         */
        this.error = function(initializer) {
            var message = createContent(initializer.message);
            if (typeof initializer == "string") {
                message = createContent(initializer);
            }  
            var openerCallback = h._getFn(initializer.openerCallback);
            var closerCallback = h._getFn(initializer.closerCallback);
            
            var errText = ns.create("<div>ERROR!</div>");
            ns.style(errText, { "color": "red", "fontWeight": "bold", "marginBottom": "6px" });
            var container = ns.create("<div></div>");
            ns.append(container, errText);
            ns.append(container, createContent(message));
            
            return  _helper(_typeError, container, openerCallback, function() {}, function() {}, closerCallback);
        };

        /**
         * Creates a confirm message, allowing a user to either confirm or 
         * cancel. Returns a function that will close the alert.
         * 
         * @param {Object} initializer with properties:
         *      {string | HTMLElement} message : content of message
         *      {Function} okCallback (optional) : function to be called when ok button is clicked
         *      {Function} cancelCallback (optional) : function to be called when cancel button is clicked
         *      {Function} openerCallback (optional) : function to be called when alert is opened
         *      {Function} closerCallback (optional) : function to be called when alert is closed
         *      
         * @return function that will close the alert    
         */
        this.confirm = function(initializer) {
            var message = createContent(initializer.message);
            var okCallback = h._getFn(initializer.okCallback);
            var cancelCallback = h._getFn(initializer.cancelCallback);
            var openerCallback = h._getFn(initializer.openerCallback);
            var closerCallback = h._getFn(initializer.closerCallback);
            
            return _helper(_typeConfirm, message, openerCallback, okCallback, cancelCallback, closerCallback);
        };

        /**
         * Creates a simple message, allowing a user to click an ok to close. 
         * Returns a function that will close the alert.
         * 
         * @param {Object} initializer with properties:
         *      {string | HTMLElement} message : content of message
         *      {Function} okCallback (optional) : function to be called when ok button is clicked
         *      {Function} openerCallback (optional) : function to be called when alert is opened
         *      {Function} closerCallback (optional) : function to be called when alert is closed
         * OR
         * @param {string} message     
         * @return function that will close the alert    
         */
        this.message = function(initializer) {
            var message = createContent(initializer.message);
            if (typeof initializer == "string") {
                message = createContent(initializer);
            }
            var okCallback = h._getFn(initializer.okCallback);
            var openerCallback = h._getFn(initializer.openerCallback);
            var closerCallback = h._getFn(initializer.closerCallback);
            
            return _helper(_typeMessage, message, openerCallback, okCallback, function() {}, closerCallback);
        };
        
        // common helper function for all alert types; returns a function to close alert
        function _helper(type, text, openerCallback, okCallback, cancelCallback, closerCallback) {
            var container = ns.create("<div></div>");
            ns.style(container, "position", "absolute");
            ns.style(container, "top", "0");
            ns.style(container, "left", "0");
            ns.style(container, "height", ns.dimensions(document.body).height + "px");
            ns.style(container, "width", ns.dimensions(document.body).width + "px");            
            ns.style(container, "backgroundColor", initializer.backgroundColor);
            ns.style(container, "opacity", initializer.backgroundOpacity);
            ns.style(container, "zIndex", initializer.backgroundZ);

            var message = ns.create("<div></div>");
            ns.append(message, text);
            ns.styles(message, messageStyles);
						                
            // set as a no-ops unless we are adding keypress or disabling scroll
            var catchKeys = function() {};
            var noScroll = function() {};
            
            //TODO: problems w/ keypress intercepting in Firefox
            // only add keypress listener if we are closing on 'Enter' keypress
            if (initializer.closeOnEnter) {
                catchKeys = function(event) {
                    event = ns.event(event);    
                    // only respond to Enter keypress
                    if (event.keyCode == 13) {
                        close();
                        return ns.kill(event);                       
                    }
                    return true;
                };
                ns.addListener(document, "keypress", catchKeys);
                ns.addListener(container, "keypress", catchKeys);                    
            }
			
            // specify the default behavior for if scrolling is attempted when alert is up
            if (initializer.disableScroll) {
                noScroll = function(event) { window.scrollTo(0, 0); return ns.kill(event); };
                ns.addListener(window, "scroll", noScroll);              
            }
            
            // the function that will close alert and perform clean-up
            function close() {
                ns.remove(container);
                ns.remove(message);
                ns.removeListener(document.body, "keypress", catchKeys);
                ns.removeListener(container, "keypress", catchKeys);
                ns.removeListener(window, "scroll", noScroll);                   
                closerCallback();
            }
            
            // checks the given callback to see if close should be performed, and 
			// does so if callback returns nothing or returns true
            function checkClose(callback) {
                var doClose = h._getFn(callback)();
                if (doClose === undefined || doClose === true) {
                    close();
                    return true;
                } else {
                    return false;
                }
            }
            
			if (type != _typeDisplay) {
				var ok;
				if (initializer.okButtonHtml) {
		            ok = ns.create(initializer.okButtonHtml);					
				} else {
		            ok = ns.create("<button>" + initializer.okText + "</button>");					
		            ns.styles(ok, buttonStyles);
		            ns.styles(ok, { "margin-right" : "5px" });
				}
	            ns.append(message, ok);
	            ns.addClick(ok, function(event) { return checkClose(okCallback); });				
			}
            
            // if is a confirm box, add a cancel button    
            if (type == _typeConfirm) {
            	var cancel;
            	if (initializer.cancelButtonHtml) {
                    cancel = ns.create(initializer.cancelButtonHtml);
            	} else {
                    cancel = ns.create("<button>" + initializer.cancelText + "</button>");
                    ns.styles(cancel, buttonStyles);
                    ns.styles(cancel, { "margin-left" : "5px" });            		
            	}
                ns.append(message, cancel);
                ns.addClick(cancel, function (event) { return checkClose(cancelCallback); });
            }
            
            var dims = ns.dimensions(message);
            var height = ns.dimensions(document.body).height, width = ns.dimensions(document.body).width;
            var top = ns.parseNumber(initializer.topOverride, (height - dims.height) / 2); 
            var left = ns.parseNumber(initializer.leftOverride, (width - dims.width) / 2); 
            ns.style(message, "z-index", initializer.backgroundZ);
			
			// call function used to provide display functionality
            initializer.display(container, message, top, left);
            // call opener callback after all init is done
            openerCallback();
            
            // this is a workaround; scroll was 0, 0 if alert shown on page-load
            window.setTimeout(function() {
        	    var scroll = jwyre.scroll();
        	    ns.log(scroll);
        	    ns.log("Top: " + (scroll.top + top + "px"));
        	    ns.log("Left: " + (scroll.left + left + "px"));
        	    ns.style(message, {
        	    	"top" : scroll.top + top + "px",
        	    	"left" : scroll.left + left + "px"        	    	
        	    });
                ns.style(container, {
                    "height" : scroll.top + ns.dimensions(document.body).height + "px",
                	"width" : scroll.left + ns.dimensions(document.body).width + "px"                	
                });
            }, 20);
            
            return close;
        }
    }

    // this is a single global instance for non-custom use
    var global = new _Alert(defaults);
    // add to namespace
    ns.message = global.message;
    ns.error = global.error;
    
    // assign creator function to jwyre.alert()
    ns.alert = function(initializer) { return new _Alert(initializer); };   
})(jwyre);
/**
 * @projectDescription
 * Provides for the creation of several different types of image and text gallery
 * facilities.
 * 
 * @author Ryan Hardy
 * @version 1.1.1
 */
(function(ns) {
	//TODO: need to do extensive documentation
	
    // check that jwyre is present
    if (!ns) {
        throw new Error("jwyre.js is not present, and is required.");
    }
    // if present, make shortcuts to tools
    var h = ns._INTERNALS;
	
    // add Behavior object constants into jwyre namespace
    ns.Gallery = {
		Type : {
			TYPE_1 : "_type1",
            TYPE_2 : "_type2",
			FILMSTRIP : "_strip"
		},
        Behavior : {
            FADER : "_fader",
            ROTATOR : "_rotator"
        }
    };
    
    // sets the image source, if addPic is true, or inner text on item 
    function setItem(addPic, el, item) {
        if (addPic) {
			setImage(el, item, "");
        } else {
			ns.text(el, item);
        }
    }
	
    /*
     * Obtains the image source from the element, by checking to 
     * see if it is an image element, or an HTML element with a 
     * background image.  Returns empty string if none found.
     * @param {string || HTMLElement} element
     */
    function getImageSrc(element) {
        if (ns.tag(element) == "img") {
            return ns.attribute(element, "src");
        } else {
            var img = ns.style(element, "background-image");                     
            img = img.replace(/url\(['"]/gi, "");
            img = img.replace(/['"]\)/gi, "");
            img = ns.trim(img);
            
            return img;
        }
    }
    
    /*
     * Sets the image attributes on the provided element.  If it is
     * an image element, the text will be set on the alt attribute, and
     * will be ignored otherwise.
     * 
     * @param {string || HTMLElement} element
     * @param {string} src
     * @param {string} text
     */
    function setImage(element, src, text) {
        if (ns.tag(element) == "img") {
            ns.attribute(element, "src", src);
            ns.attribute(element, "alt", text);
        } else {
            ns.style(element, "background-image", "url('" + src + "')");
        }
    }
            
    /**
     * 
     * @param {Object} initializer, with properties:
     * 
     *  element {string || HTMLElement} : (optional; behavior dependent)
     *  interval {integer}  : (optional)
     *  isPicGallery {boolean} : (optional)
     *  behavior {string || Object} : (optional)
     *  duration {integer} : (optional; behavior dependent)
     */
    function _Gallery(initializer) {
        initializer.element = ns.element(initializer.element);
        initializer.interval = ns.parseNumber(initializer.interval, 500);
        initializer.isPicGallery = ns.parseBoolean(initializer.isPicGallery, true);
        // indicates if Gallery has been started
        var isActive = false;
        
        var bhvr = initializer.behavior;
        // set the switch Behavior          
        if (bhvr) {
            // check if a built-in behavior is to be loaded
            if (typeof bhvr != "object") {
                switch (bhvr) {
                    case ns.Gallery.Behavior.FADER:
                       bhvr = new FadeBehavior(initializer);
                       break;
                    default:
                        bhvr = new RotateBehavior(initializer);
                }                   
            } else {
                // if is a custom object, check for correct functions
                try {
                    h._checkType(bhvr, "object");
                    h._checkType(bhvr.add, "function");
                    h._checkType(bhvr.start, "function");
                    h._checkType(bhvr.stop, "function");
                } catch (e) {
                    throw new Error("A custom behavior object must provide a start(), stop() and add() function.");
                }                   
            }
        } else {
            bhvr = new RotateBehavior();
        }
        // add Behavior to initializer      
        initializer.behavior = bhvr;
            
        /**
         * Adds an item (or array of items) to the gallery.  For image 
         * galleries, it can be a relative or absolute url, and also 
         * preloads the image.
         * 
         * @param {string | Array} item
         */
        this.add = function(item) {
            initializer.behavior.add(item);
        };
                    
        /**
         * @param {function} callback (optional; behavior dependent)
         */
        this.start = function(callback) {
            if (isActive) {
                throw new Error("Gallery has already been started.");
            }
            isActive = true;
            initializer.behavior.start(callback);
        };
        
        /**
         * 
         */
        this.stop = function () {
            isActive = false;
            initializer.behavior.stop();
        };
    }
    
    /**
     * 
     */
    function RotateBehavior(initializer) {
        // make aliases for simplicity
        var el = initializer.element, isPic = initializer.isPicGallery, intvl = initializer.interval;
        this._type = ns.Gallery.Behavior.ROTATOR;
        // manager of gallery items
        var items = ns.array().rotator();
        // the window.setInterval id
        var id = null;
        
        /**
         * 
         * @param {string | Array} item
         */
        this.add = function(item) {
            items.add(item);
			if (isPic) {
	            ns.loadImgs([item]);				
			}
        };

        /**
         * 
         */
        this.start = function(callback) {
            callback = h._getFn(callback);
            function _switch() {
                var item = items.getNextItem();
                setItem(isPic, el, item);
                callback(item);
            }
            id = window.setInterval(_switch, intvl);
        };
        
        /**
         * 
         */
        this.stop = function () {
            window.clearInterval(id);
            id = null;
        };
    }

    /**
     * 
     */
    function FadeBehavior(initializer) {
        initializer.duration = ns.parseNumber(initializer.duration, 500);
        // make aliases for simplicity
        var el = initializer.element, isPic = initializer.isPicGallery, intvl = initializer.interval, dur = initializer.duration;
        this._type = ns.Gallery.Behavior.FADER;
        // manager of gallery items
        var items = ns.array().rotator();
        var timerId = null;
        
        /**
         * 
         * @param {string | Array} item
         */
        this.add = function(item) {
            items.add(item);
			if (isPic) {
	            ns.loadImgs([item]);				
			}
        };
        
        /**
         * 
         */
        this.start = function(callback) {
            callback = h._getFn(callback);
            
            //    object used to hold elements and states for fading
            var fader1 = {};
            var fader2 = {};
            
            var clone = el.cloneNode(false);
            ns.insertAfter(clone, el);
            ns.cloneStyles(el, clone);
            //    set positioning
            ns.styles(clone, {
                "position" : "absolute",
                "top" : el.offsetTop + "px",
                "left" : el.offsetLeft + "px",
                "height" : el.offsetHeight + "px",
                "width" : el.offsetWidth + "px",
                "opacity" : "0"                 
            });
            // ensure unique id
            ns.attribute(clone, "id", "_jwyre_item_clone_" + new Date().getTime());
            setItem(isPic, el, items.getNextItem());
            setItem(isPic, clone, items.getNextItem());
            
            fader1.element = el;
            fader1.fadeIn = false;
            fader1.f = ns.animator(fader1.element);
            
            fader2.element = clone;
            fader2.fadeIn = true;
            fader2.f = ns.animator(fader2.element);       
            
            /*
             * Function used in call to window.setInterval(); switches items and starts
             * fade-in/fade-out behavior.
             */
            function _switch() {
                function fade(_fader) {
                    if (_fader.fadeIn) {
                        var item = items.getNextItem();
                        setItem(isPic, _fader.element, item);
                        ns.style(_fader.element, "zIndex", "2");
                        _fader.f.fadeIn(dur,
                            function() {
                                callback("in", item);
                                _fader.f.reset();
                            }
                         );
                    } else {
                        ns.style(_fader.element, "zIndex", "1");
                        _fader.f.fadeOut(dur, 
                            function() {
                                callback("out", item);
                                _fader.f.reset();
                            }
                        );
                    }
                    _fader.fadeIn = !_fader.fadeIn;
                }
                fade(fader1);
                fade(fader2);
            };              
            timerId = window.setInterval(_switch, intvl);
        };
        
        /**
         * Stops the fader.
         */
        this.stop = function () {
            window.clearInterval(timerId);
            timerId = null;
        };
    }
    
	/* ********************************************************************** **
	 * 
 	 *                             New Gallery Code
	 * 
    ** ********************************************************************** */
	
    /**
     * 
     * @param {Object} initializer with properties
     * 
     *    displayArea : 
     *    textArea : 
     *    textImages : 
     *    images : 
     *    displayer : 
     *    imageStrip : 
     */
    function _GalleryManager(initializer) {
        // get reference to this for inner functions
        var self = this;
        // the main image display element
        this._displayArea = ns.element(initializer.displayArea);
        // the main image text display element, if there is one
        this._textArea = (initializer.textArea) ? ns.element(initializer.textArea) : document.createDocumentFragment();
                    
        // array of image sources
        this._images = ns.array();
        this._imageTexts = {};
        if (initializer.textImages) {
            // if textImages provided, pull out 'src' prop from each item
            ns.iterator(initializer.textImages, true).getArray().each(
                function() {
                    self._images.push(this.name);
                    self._imageTexts[this.name] = this.value;
                }
            );
        } else {
            this._images = ns.array(initializer.images);
            // if textImages not provided, set all texts to empty string
            this._images.each(
                function() {
                    self._imageTexts[this] = "";
                }
            );
        }
		// preload the gallery images
		jwyre.loadImgs(this._images);
        
        // the currently displayed image's source
        this._currentImage = this._images[0];
        // initialize current image area
        this.showCurrentImage();
        
        // the Displayer implementation used for showing an enlarged image
        var displayer = getDisplayer(initializer, this);
        // the ImageStrip implementation used for displaying thumbnails
        var imageStrip = getImageStrip(initializer, this);
                    
        // convenience function for getting or creating a default Displayer implementation
        function getDisplayer(init, gm) {
            if (init.displayer) {
                return init.displayer;
            }
            init = ns.clone(init);
            init.galleryManager = gm;
            return new _DefaultDisplayer(init);
        }
        // convenience function for getting or creating a default ImageStrip implementation
        function getImageStrip(init, gm) {
            if (init.imageStrip) {
                return init.imageStrip;
            }
            init = ns.clone(init);
            init.galleryManager = gm;
            return new _DefaultImageStrip(init);
        }
    }
    _GalleryManager.prototype = {
        /**
         * Obtains the array of image sources.
         */
        getImages : function() {
            return this._images;
        },
        /**
         * Sets the current image.  If a text area has been configured, any
         * text associated with the image will also be displayed.
         * 
         * @param {string} imageSrc
         */
        setCurrentImage : function(imageSrc) {
            this.showImage(imageSrc);
            this._currentImage = imageSrc;
        },
        /**
         * Temporarily displays the provided image in the main image area,
         * but does not update it as the current image.
         * 
         * @param {string} imageSrc
         */
        showImage : function(imageSrc) {
            var text = this._imageTexts[imageSrc];
            ns.text(this._textArea, text);
            setImage(this._displayArea, imageSrc, text);
        },
        /**
         * Displays the current image (if it is not currently showing, such as
         * when an image has been temporarily displayed by showImage()).
         */
        showCurrentImage : function() {
            this.showImage(this._currentImage);                
        },
        /**
         * Obtains the currently displayed image's source.
         */
        getCurrentImage : function() {
            return this._currentImage;
        },
        /**
         * Obtains the HTMLElement that is used as the main image display area.
         */
        getDisplayArea : function() {
            return this._displayArea;
        },
        /**
         * Obtains an Object that contains a mapping of image sources mapped
         * to image texts (contains empty strings if none provided).
         */ 
        getImageTexts : function() {
            return this._imageTexts;
        },
        /**
         * Obtains an Object that contains a mapping of image sources mapped
         * to image texts (contains empty strings if none provided).
         * 
         * @param imageSrc
         */ 
        getImageText : function(imageSrc) {
            var src = this._imageTexts[imageSrc];
            return src || "";
        }
    };      

    /**
     * Creates a Displayer instance with subclass specific initializer object.
     * 
     * @param {Object} initializer with properties
     * 
     *     displayTrigger : 
     */
    function Displayer(initializer) {
        var trigger = null; 
        // do not attempt to initialize if initializer is absent (being used as prototype)
        if (initializer) {
            // get reference to this for use in inner functions
            var self = this;
            // assign for external use
            this._galleryManager = initializer.galleryManager;
            // the HTMLElement to add the display click function
            trigger = initializer.displayTrigger;
            
            ns.addClick(trigger, 
                function(event) {
                    var gm = self.getGalleryManager();
                    var img = gm.getCurrentImage();
                    var text = gm.getImageText(img);                
                    self.display(img, text);
                    
                    return ns.kill(event);
                }
            );
        }
        /**
         * Obtains the element that the display event handler is attached to.
         */
        this.getTrigger = function() {
            return trigger;
        };  
    }
    Displayer.prototype = {
        /**
         * Obtains the GalleryManager tied to this instance.
         */
        getGalleryManager : function() {
            return this._galleryManager;
        },
        /**
         * Displays the chosen image.
         * 
         * @param {string} imageSrc
         * @param {string} text (optional)
         */
        display : function(imageSrc, text) { }
    };
    
    /**
     * Creates a Displayer instance with default display behavior.
     * 
     * @param {Object} initializer with properties
     *      (from superclass) 
     *      displayTrigger : 
     * 
     *      (for subclass)
     *      maxWidth : 
     *      showDuration :
     *      hideDuration :
     */
    function _DefaultDisplayer(initializer) {
        Displayer.call(this, initializer);
        // the maximum size to grow the image to
        this._maxWidth = ns.parseNumber(initializer.maxWidth, 450);
        this._showDuration = ns.parseNumber(initializer.showDuration, 300);
        this._hideDuration = ns.parseNumber(initializer.hideDuration, 300);
    }
    _DefaultDisplayer.prototype = new Displayer();
    /**
     * 
     * @param {string} imageSrc
     * @param {string} text (optional)
     */
    _DefaultDisplayer.prototype.display = function(imageSrc, text) { 
       // get reference to self for use in inner functions
        var self = this; 
        
        text = (!text) ? "" : text;
        var element = this.getGalleryManager().getDisplayArea();
        var dims = ns.dimensions(element);
        var pos = ns.position(element, false);
        
        var bkgrnd = ns.create("<div></div>");
        ns.style(bkgrnd, {
            "position" : "absolute",
            "top" : "0px",
            "left" : "0px",
            "height" : ns.dimensions(document.body, true).height + ns.scroll().top + "px",
            "width" : ns.dimensions(document.body, true).width + ns.scroll().left + "px",
            "background-color" : "black",
            "opacity" : "0",
            "z-index" : "1000"
        });
        if (ns.getBrowserInfo().name == ns.Browser.CHROME) {
        ns.style(bkgrnd, {
            "height" : ns.dimensions(document.body, true).height + 30 + "px",
            "width" : ns.dimensions(document.body, true).width + "px"
        });
        }
        ns.append(document.body, bkgrnd);
        
        var box = ns.create("<div></div>");
        ns.style(box, {
            "position" : "absolute",
            "padding" : "30px 10px 30px 10px",
            "background-color" : "black",
            "border" : "1px white solid"
        });
        var closer = ns.create("<div>[close]</div>");
        ns.style(closer, {
            "position" : "absolute",
            "top" : "6px",
            "left" : "10px",
            "color" : "white",
            "cursor" : "pointer",
            "font-family" : "Segoe UI",
            "font-size" : "10px",
            "opacity" : "0"
        });
        ns.append(box, closer);
        var clone = ns.create("<img src='" + imageSrc + "' alt='" + text + "' />");
        ns.style(clone, {
            "width" : dims.width + "px",
            "z-index" : "1001"
        });
        ns.append(box, clone);
        var comments =  ns.create("<div></div>");
        ns.style(comments, {
            "margin-top" : "5px",
            "text-align" : "center",
            "color" : "white",
            "font-family" : "Segoe UI",
            "z-index" : "1001"
        });
        ns.append(box, comments);
        ns.append(document.body, box);
        ns.position(box, pos.left, pos.top, 1000);
        
        var bDims = ns.dimensions(document.body); 
        var x = (bDims.width / 2) - (this._maxWidth / 2);
        var y = 100;
        var dW = this._maxWidth - dims.width;
        var ratio = dims.width / dims.height;
        var anim = ns.animator(box);
        ns.addClick(closer, function() {
            anim.reset();
            anim.moveTo({
                "x" : pos.left,
                "y" : pos.top,
                isRelative : false,
                duration : self._hideDuration,
                updater : function(progress) { 
                    var w = self._maxWidth - (dW * progress);
                    ns.style(box, "width", w + "px");
                    ns.style(clone, "width", w + "px");
                    ns.style(comments, "width", w + "px");
                    ns.style(bkgrnd, "opacity", "" + (.7 - progress));
                },
                callback : function() {  
                    ns.remove(box);
                    ns.remove(bkgrnd);                   
                }
            });             
        });

        anim.moveTo({
            "x" : x,
            "y" : y,
            isRelative : false,
            duration : self._showDuration,
            updater : function(progress) { 
                var w = dims.width + (dW * progress);
                ns.style(clone, "width", w + "px");
                ns.style(comments, "width", w + "px");
                ns.style(closer, "opacity", "" + (progress));
                ns.style(bkgrnd, "opacity", "" + (progress * .7));
            },
            callback : function() { 
                ns.text(comments, text);
            }
        });       
    };

    /**
     * 
     * @param {Object} initializer Object with the following properties:
     * 
     *     fwdTrigger :
     *     bkwdTrigger :
     *     numThumbs :
     */
    function ImageStrip(initializer) {
        // do not attempt to initialize if initializer is absent (being used as prototype)
        if (initializer) {
            // get reference to this for use in inner functions
            var self = this;
            // assign for external use
            this._galleryManager = initializer.galleryManager;
            
            // the element to attach event handler for sliding thumbs forward
            var fwdTrigger = initializer.fwdTrigger;
            // the element to attach event handler for sliding thumbs back
            var bkwdTrigger = initializer.bkwdTrigger;
            // get the images array; will convert to DualRotator
            var rotator = this.getGalleryManager().getImages();
            // number of thumbs to display
            this._numThumbs = Math.min(rotator.size(), initializer.numThumbs);
            // convert to a dualRotator; allows for forward and backward traversal 
            // through the images
            this._rotator = rotator.dualRotator(this._numThumbs);
            
            // glad indicating that a click is active
            var isActive = false;
            
            ns.addClick(fwdTrigger, function(event) {
                if (!isActive) {
                    isActive = true;
                    self.moveForward(function() { isActive = false; });
                }
                return ns.kill(event);
            });
            ns.addClick(bkwdTrigger, function(event) {
                if (!isActive) {
                    isActive = true;
                    self.moveBack(function() { isActive = false; });
                }
                return ns.kill(event);
            });
        }       
    }
    ImageStrip.prototype = {
        /**
         * Obtains the GalleryManager tied to this instance.
         */
        getGalleryManager : function() {
            return this._galleryManager;
        },
        /**
         * Obtains the DualRoator of image sources.
         */
        getImageRotator : function() {
            return this._rotator;
        },
        /**
         * 
         */
        getNumberOfThumbs : function() {
            return this._numThumbs;
        },
        /**
         * Advances the currently displaying thumbnails forward by one through the
         * internal image collection.
         * 
         * @param {function} callback
         */
        moveBack : function(callback) { },
        /**
         * Advances the currently displaying thumbnails backward by one through the
         * internal image collection.
         * 
         * @param {function} callback
         */
        moveForward : function(callback) { }
    };
    
    /**
     * Creates a ImageStrip instance with default display behavior.
     * 
     * @param {Object} initializer with properties:
     *      (from superclass)
     *      fwdTrigger :
     *      bkwdTrigger :
     *      numThumbs :
     *      
     *      (for subclass)
     *      thumbHeight :
     *      thumbWidth :
     *      thumbMargin :
     *      thumbInitializer :
     *      container :
     *      containerStyles :
     *      duration :
     */
    function _DefaultImageStrip(initializer) {
        ImageStrip.call(this, initializer);
        // get reference to this for use in inner functions
        var self = this;
        // the dimensions of each thumbnail
        var thumbH = initializer.thumbHeight;
        var thumbW = initializer.thumbWidth;
        // the spacing between thumbnails       
        var thumbMgn = initializer.thumbMargin;
        // ensure it is an even number (problem with fractional pixels in IE)
        thumbMgn = (thumbMgn % 2 == 1) ? thumbMgn++ : thumbMgn;  
        
        // an optional function for adding custom behavior to the thumbails
        var thumbInitializer = (initializer.thumbInitializer) ? initializer.thumbInitializer : function() {}; 
        
        // the number of thumbails to display at a time
        var num = this.getNumberOfThumbs();

        // the HTML container for the thumbnails
        this._container = ns.create("<div></div>");
        // the styles to apply to the container
        var containerStyles = initializer.containerStyles;
        // override the following styles
        containerStyles.height = thumbH + thumbMgn + "px";
        containerStyles.width = (num * (thumbW + thumbMgn)) + "px";
        containerStyles.overflow = "hidden";
        
        var containerParent = ns.create("<div></div>");          
        ns.style(containerParent, containerStyles);
        ns.style(this._container, {
            "position" : "absolute",
            "top" : "0px",
            "left" : "0px",
            "height" : containerStyles.height,
            "width" : containerStyles.width
        });

        ns.append(initializer.container, containerParent);
        ns.append(containerParent, this._container);
        
        // the width needed to move a hidden thumb into view; used by moveForward()/moveBack() methods
        this._width = thumbW + thumbMgn;
        
        var initImgs = this.getImageRotator().current();
        // the amount of time in which to perform swutch animation
        this._duration = ns.parseNumber(initializer.duration, 200);
        // the thumbnails used to facilitate slide behavior
        this._leftHidden = null, this._rightHidden = null;
        // the visible thumbnails
        this._displays = ns.array();
        
        for (var i = -1; i <= num; i++) {
            var top = (thumbMgn / 2);
            var left = (i * (thumbW + thumbMgn)) + (thumbMgn / 2);
            ns.log("Top: " + top + ", left: " + left);
            var html = ns.create("<img />");
            if (i == -1) {
                this._leftHidden = html;
            } else if (i == num) {
                this._rightHidden = html;
            } else {
                this._displays.push(html);
                var src = initImgs[i];
                var txt = this.getGalleryManager().getImageText(src);
                setImage(html, src, txt);
                
                var fns = (function(element) {
                    return {
                        on : function() {
                            var src = getImageSrc(element);
                            self.getGalleryManager().showImage(src);
                        },
                        off : function() {
                            self.getGalleryManager().showCurrentImage();
                        },
                        click : function(event) {
                            var src = getImageSrc(element);
                            self.getGalleryManager().setCurrentImage(src);
                            return ns.kill(event);
                        }
                    };
                })(html);
                ns.addHover(html, fns.on, fns.off);
                ns.addClick(html, fns.click);
                thumbInitializer(html);
            }
            ns.append(this._container, html);
            ns.style(html, {
                "position" : "absolute",
                "top" : top + "px",
                "left" : left + "px",
                "height" : thumbH + "px",
                "width" : thumbW + "px"
            });
        }
    }
    _DefaultImageStrip.prototype = new ImageStrip();
    /**
     * Internal helper function used to reset the strip elements after an
     * animation cycle.
     * 
     * @param {integer} width
     * @param {Array} imgs
     */
    _DefaultImageStrip.prototype._reset = function(width, imgs) {
        var self = this;
        var x = ns.position(this._container, true).left;
        ns.style(this._container, "left", (x + width) + "px");
        ns.log("Before resetting.");
        ns.log("Left - src: " + this._leftHidden.src + ", text: " + this._leftHidden.alt);
        ns.log("Right - src: " + this._rightHidden.src + ", text: " + this._rightHidden.alt);
        
        setImage(this._leftHidden, "", "");
        setImage(this._rightHidden, "", "");
        
        this._displays.each(function(item, counter) {
            //ns.log("Before resetting (" + counter + ").");
            //ns.log("Src: " + this.src + ", text: " + this.alt);
            var src = imgs[counter];
            var txt = self.getGalleryManager().getImageText(src);
            setImage(this, src, txt);                        
            ns.log("After resetting (" + counter + ").");
            ns.log("Src: " + "" + ", text: " + this.alt);
        });
    };
    /**
     * Advances the currently displaying thumbnails forward by one through the
     * internal image collection.
     * 
     * @param {function} callback
     */
    _DefaultImageStrip.prototype.moveBack = function(callback) {
        var self = this;
        this.getImageRotator().next();
        var imgs = this.getImageRotator().current();
        var imgSrc = imgs.last();
        ns.log("Calling next, src=" + imgSrc);
        var text = this.getGalleryManager().getImageText(imgSrc);
        setImage(this._rightHidden, imgSrc, text);
        
        var width = this._width;            
        
        var anim = ns.animator(this._container);
        anim.move({
            "x" : -width,
            "y" : 0,
            "duration" : self._duration,
            "callback" : function() {
                self._reset(width, imgs);
                callback();
            }
        });
    };
    /**
     * Advances the currently displaying thumbnails backward by one through the
     * internal image collection.
     * 
     * @param {function} callback
     */
    _DefaultImageStrip.prototype.moveForward = function(callback) { 
        var self = this;
        var imgSrc = this.getImageRotator().back();
        var imgs = this.getImageRotator().current();
        ns.log("Calling back, src=" + imgSrc);
        var text = this.getGalleryManager().getImageText(imgSrc);   
        setImage(this._leftHidden, imgSrc, text);
        var width = this._width;
        
        var anim = ns.animator(this._container);
        anim.move({
            "x" : width,
            "y" : 0,
            "duration" : self._duration,
            "callback" : function() {
                self._reset(-width, imgs);
                callback();
            }
        });     
    };
    
	/*
	============================================================================
	                       'Filmstrip' type gallery
    ============================================================================
	 */
	
	/**
	 * Creates a basic 'filmstrip' like gallery, with forward/backward behavior.
	 * 
	 * @param {Object} initializer, with properties:
	 * 
	 * {
	 *     displayArea
	 *     textArea
	 *     textImages || images
	 *     fwdTrigger
	 *     bkwdTrigger
	 * }
	 */
	function _Filmstrip(initializer) {
        // get reference to this for inner functions
        var self = this;
        // the main image display element
        this._displayArea = ns.element(initializer.displayArea);
        // the main image text display element, if there is one
        this._textArea = (initializer.textArea) ? ns.element(initializer.textArea) : document.createDocumentFragment();
                    
        // array of image sources
        this._images = ns.array();
        this._imageTexts = {};
        if (initializer.textImages) {
            // if textImages provided, pull out 'src' prop from each item
            ns.iterator(initializer.textImages, true).getArray().each(
                function() {
                    self._images.push(this.name);
                    self._imageTexts[this.name] = this.value;
                }
            );
        } else {
            this._images = ns.array(initializer.images);
            // if textImages not provided, set all texts to empty string
            this._images.each(
                function() {
                    self._imageTexts[this] = "";
                }
            );
        }
        // preload the gallery images
        jwyre.loadImgs(this._images);
        
        // the currently displayed image's source
        this._currentImage = this._images[0];
        // initialize current image area
        this.showCurrentImage();		
		
		var rotator = this._images.dualRotator();
		/*
		========================================================================
                  THIS IS WHERE CUSTOMIZABLE DISPLAY BEHAVIOR WILL GO -
                            IT IS NOT CURRENTLY IMPLEMENTED                      
        ========================================================================
		 */
		var fwdTrigger = jwyre.element(initializer.fwdTrigger);
		if (!fwdTrigger) {
			throw new Error("No forward trigger has been configured.");
		}
        var bkwdTrigger = jwyre.element(initializer.bkwdTrigger);
        if (!bkwdTrigger) {
            throw new Error("No forward trigger has been configured.");
        }
		jwyre.addClick(fwdTrigger, 
			function(event) {
				var nxt = rotator.next();
				self.setCurrentImage(nxt);
                return jwyre.kill(event);
			}
        );
        jwyre.addClick(bkwdTrigger, 
            function(event) {
                var lst = rotator.back();
                self.setCurrentImage(lst);
                return jwyre.kill(event);
            }
        );
	}
    _Filmstrip.prototype = {
        /**
         * Obtains the array of image sources.
         */
        getImages : function() {
            return this._images;
        },
        /**
         * Sets the current image.  If a text area has been configured, any
         * text associated with the image will also be displayed.
         * 
         * @param {string} imageSrc
         */
        setCurrentImage : function(imageSrc) {
            this.showImage(imageSrc);
            this._currentImage = imageSrc;
        },
        /**
         * Temporarily displays the provided image in the main image area,
         * but does not update it as the current image.
         * 
         * @param {string} imageSrc
         */
        showImage : function(imageSrc) {
            var text = this._imageTexts[imageSrc];
            ns.text(this._textArea, text);
            setImage(this._displayArea, imageSrc, text);
        },
        /**
         * Displays the current image (if it is not currently showing, such as
         * when an image has been temporarily displayed by showImage()).
         */
        showCurrentImage : function() {
            this.showImage(this._currentImage);                
        },
        /**
         * Obtains the currently displayed image's source.
         */
        getCurrentImage : function() {
            return this._currentImage;
        },
        /**
         * Obtains the HTMLElement that is used as the main image display area.
         */
        getDisplayArea : function() {
            return this._displayArea;
        },
        /**
         * Obtains an Object that contains a mapping of image sources mapped
         * to image texts (contains empty strings if none provided).
         */ 
        getImageTexts : function() {
            return this._imageTexts;
        },
        /**
         * Obtains an Object that contains a mapping of image sources mapped
         * to image texts (contains empty strings if none provided).
         * 
         * @param imageSrc
         */ 
        getImageText : function(imageSrc) {
            var src = this._imageTexts[imageSrc];
            return src || "";
        }
    };      
	
    ns.gallery = function(initializer) { 
		if (initializer && initializer.galleryType == ns.Gallery.Type.TYPE_1) {
            return new _Gallery(initializer);
		} else if (initializer && initializer.galleryType == ns.Gallery.Type.FILMSTRIP) {
            new _Filmstrip(initializer);
		} else {
            new _GalleryManager(initializer);
        }
	};
})(jwyre);
/**
 * @projectDescription
 * Used to create a custom scroll bar around an HTMLElement.
 * 
 * @author Ryan Hardy
 * @version 0.9.4
 */
(function(ns) {
    // check that jwyre is present
    if (!ns) {
        throw new Error("jwyre.js is not present, and is required.");
    }
    
	var defaults = {
		scrollerPosition : "right",
		generateScroller : true,
		scrollerWidth : 20
	};
	
	/**
	 * 
	 * @param {Object} initializer
	 */
	function _Scroller(initializer) {
        // make shortcuts to tools
        var h = ns._INTERNALS;
		var sPos = initializer.scrollerPosition || defaults.scrollerPosition;
		if (sPos != "left" && sPos != "right") {
			throw new Error("scrollerPosition must be 'left' or 'right'.")
		}
		// the element containing the content to be scrolled
	    var content = ns.element(initializer.element);
		if (!h._x(content)) {
			throw new Error("The element is null.");
		}
        // the scroll bar width
        var w = ns.parseNumber(initializer.scrollerWidth, defaults.scrollerWidth);
        // indicates if the HTML for the scroll bar will be provided or created
		var genSc = ns.parseBoolean(initializer.generateScroller, defaults.generateScroller);
		
        // this is the element that will actually move to scroll content into view
        var scrollContent = ns.create("<div></div>");
        ns.cloneStyles(scrollContent, content);
        ns.styles(scrollContent, {
			"position" : "absolute"
		});
		
        // move content into new element
        ns.html(scrollContent, ns.html(content));
        ns.empty(content);
        ns.styles(content, {
            "overflow" : "hidden"
        });
        ns.append(content, scrollContent);
		
		// the scroller (the element that will be clicked/dragged to scroll content)
        var scroller;
		// the scrollbar (the area on which the scroller will move when scrolling content)
        // used to restrict scroll element's movement
        var scrollBar;
		
		// creating default scroller?
		if (genSc) {
			//TODO: implement
		} 
        // this is initialization for non-generated scroll components		
		else {
			// the clickable/draggable element
			scroller = jwyre.element(initializer.scrollElement);
			if (!h._x(scroller)) {
				throw new Error("The scrollElememt is null.");
			}
            // element used to restrict scroll element's movement
			scrollBar = jwyre.parent(scroller);
		}
		
	    // the height of only visible content
	    var visHeight = h._getTotalHeight(content);
	    // the height of all content, visible and invisible
	    var totalHeight = h._getTotalHeight(content, true);
		totalHeight = Math.max(totalHeight, visHeight);
		var speed = ns.parseNumber(initializer.speed, 1);
		speed = Math.max(speed, 1);
		var increment = Math.max(1, Math.abs(visHeight - totalHeight) * .01 * speed);
		// the current scroller's y position
		var currentScroll = 0;
		
		function _scrollUp() {
            if (currentScroll < 0) {
                currentScroll += increment;
                ns.style(scrollContent, "top", currentScroll + "px");
                _setScroller();
            }
		}
		function _scrollDn() {
            if (currentScroll > visHeight - totalHeight) {
                currentScroll -= increment;
                ns.style(scrollContent, "top", currentScroll + "px");
				_setScroller();
            }
		}
		
		function _setScroller() {
            var sDims = ns.dimensions(scroller);
            var dims = ns.dimensions(scrollBar);
            var pctDn = currentScroll / (visHeight - totalHeight);
            var y = pctDn * (dims.height - sDims.height);
			y = Math.min(y, dims.height - sDims.height);
            ns.position(scroller, 0, y);
		}
		
	    // create the jwyre.moveable() object to control scroll behavior
	    var mvbl = ns.moveable({
	        trigger : scroller,
	        element : scroller,
	        container : scrollBar,
	        upCallback : null,
	        downCallback : null,
	        moveCallback : function() {
	            var pt = mvbl.getPoint();
	            if (pt) {
		            var sDims = ns.dimensions(scroller);
		            var dims = ns.dimensions(scrollBar);
					var pctDn = pt.y / (dims.height - sDims.height);
                    currentScroll = (visHeight - totalHeight) * pctDn; 
                    ns.style(scrollContent, "top", currentScroll + "px");
	            }
	            return true;
	        }
	    });
		var id = null;
        ns.addListener(initializer.scrollUpTrigger, "mousedown", 
			function() {
				if (!id) {
					id = window.setInterval(_scrollUp, 10);
				}
			}
		);
        ns.addListener(initializer.scrollDownTrigger, "mousedown", 
			function() {
                if (!id) {
                    id = window.setInterval(_scrollDn, 10);
                }				
			}
		);
        function _clear() {
	        if (id) {
	            window.clearInterval(id);
                id = null;
            }
        }

		if (jwyre.isIE()) {
            ns.addListener(document.body, "mouseup", _clear);            
		} else {
	        ns.addListener(document, "mouseup", _clear);			
		}
	}
	
    // create a generic element to act as the scroller if none other provided
    function mkDefScroll() {
        var sEl = ns.create("<div></div>");
        ns.styles(sEl, {
            "position" : "absolute",
            "top" : "0px",
            "left" : "0px",
            "height" : "20px",
            "width" : "100%",
            "background-color" : "white",
            "border" : "1px black solid"
        });
        return sEl;
    }
	
	jwyre.scroller = function(initializer) { return new _Scroller(initializer); };
})(jwyre);

/**
 * @projectDescription
 * Creates a custom 'Please Wait' type animation box.
 * 
 * Requires jwydget.alert.js and jwydget.waiter.js.
 * 
 * May be provided an initializer object to customize look and behavior.
 * Example (showing defaults):
 *  {
 *     "showAnimation" : true,
 *     "numberOfBars" : 5,
 *     "barHeight" : 15,
 *     "barWidth" : 10,
 *     "barSpacing" : 10,
 *     "jumpDistance" : 20,
 *     "duration" : 500,
 *     "barStyles" : {
 *         "background-color" : "rgb(200, 200, 200)",
 *         "border" : "1px rgb(150, 150, 150) solid"
 *     },
 *     "activeStyles" : {
 *         "background-color" : "rgb(240, 240, 240)",
 *         "border" : "1px rgb(150, 150, 220) solid"
 *     },
 *     "labelText" : "Please wait...",
 *     "labelStyles" : {},
 *     "alert" : jwyre.alert({
 *         "closeOnEnter"  : true,
 *         "disableScroll"  : false,
 *         "backgroundColor"  : "rgb(0, 0, 0)",
 *         "backgroundZ" : "100",
 *         "backgroundOpacity"  : ".20",
 *         "font"  : "Arial",
 *         "fontSize"  : "11px",
 *         "fontColor"  : "rgb(100, 100, 120)",
 *         "boxBorderSize"  : "2px",
 *         "boxBorderColor"  : "rgb(100, 100, 100)",
 *         "boxBackgroundColor"  : "rgb(255, 255, 255)",
 *         "boxOpacity"  : "1.0",
 *         "okText"  : "Ok",
 *         "cancelText"  : "Cancel",
 *         "buttonFont"  : "Arial",
 *         "buttonPadding"  : "5px",
 *         "buttonMargin"  : "10px 0px 0px 0px",
 *         "buttonFontSize"  : "11px",
 *         "buttonFontColor"  : "rgb(0, 0, 0)",
 *         "buttonBorderSize"  : "2px",
 *         "buttonBorderColor"  : "rgb(100, 100, 100)",
 *         "buttonBackgroundColor"  : "rgb(240, 240, 250)"        
 *     })
 *  };
 * 
 * @author Ryan Hardy
 * @version 1.0.4
 */
(function(ns) {
    // check that jwyre is present
    if (!ns) {
        throw new Error("jwyre.js is not present, and is required.");
    }
    // if present, make shortcuts to tools
    var h = ns._INTERNALS;
	
	// default initializer object
    var defaults = {
		"animationType" : "bars",// either 'bars', 'bumpBars', or 'boxes'
		
		// bars/bumpBars config
        "numberOfBars" : 6,
        "barHeight" : 15,
        "barWidth" : 10,
        "barSpacing" : 10,
        "jumpDistance" : 20,
        "barStyles" : {
            "background-color" : "rgb(200, 200, 200)",
            "border" : "1px rgb(150, 150, 150) solid"
        },
        "activeStyles" : {
            "background-color" : "rgb(240, 240, 240)",
            "border" : "1px rgb(150, 150, 220) solid"
        },
        
        // boxes config
        "boxHeight" : 10,
        "boxSpacing" : 3,
        "boxColor" : "blue",
        "boxAnimColor" : "blue",
        
        // common config	
        "duration" : 500,
        "labelText" : "Please wait...",
        "labelStyles" : {},
        "alertProps" : {
            "closeOnEnter"  : true,
            "disableScroll"  : false,
            "backgroundColor"  : "rgb(0, 0, 0)",
            "backgroundZ" : "100",
            "backgroundOpacity"  : ".10",
            "font"  : "Arial",
            "fontSize"  : "11px",
            "fontColor"  : "rgb(100, 100, 120)",
            "boxBorderSize"  : "1px",
            "boxBorderColor"  : "rgb(120, 120, 140)",
            "boxBackgroundColor"  : "rgb(255, 255, 255)",
            "boxOpacity"  : "1.0",
            "okText"  : "Ok",
            "cancelText"  : "Cancel",
            "buttonFont"  : "Arial",
            "buttonPadding"  : "5px",
            "buttonMargin"  : "10px 0px 0px 0px",
            "buttonFontSize"  : "11px",
            "buttonFontColor"  : "rgb(0, 0, 0)",
            "buttonBorderSize"  : "2px",
            "buttonBorderColor"  : "rgb(100, 100, 100)",
            "buttonBackgroundColor"  : "rgb(240, 240, 250)"        
        }
    };
    
    function _Waiter(initializer) {
    	if (!initializer) {
    		initializer = {};
    	}
    	var alertProps = ns.combine(defaults.alertProps, initializer.alertProps);
        initializer = ns.combine(defaults, initializer);
        var type = initializer.animationType;
        // container box for animated elements
        var box = ns.create("<div></div>");
        // the function used to start animation cycle
        var beginAnimation;
        // the total animation time for each 'bump' (up and down)
        var duration = initializer.duration;
        
        if (type == "bars" || type == "bumpBars") {
            // the number of progress bars to display
            var numBars = initializer.numberOfBars;
            // the height and width of the bars
            var bH = initializer.barHeight, bW = initializer.barWidth;
            /// the spacing between each
            var spc = initializer.barSpacing;
            // how far to 'bump' a bar on each iteration
            var dist = initializer.jumpDistance;
            var showAnim = type == "bumpBars";
    		                
            ns.style(box, {
                "position" : "relative",
                "top" : "0px",
                "left" : "0px",
                "height" : (bH * 2) + 10 + "px",
                "width" : ((bW + spc) * numBars) + "px",
                "padding" : "10px"
            });
            // progress bar elements
            var ps = ns.array();
            // mapping of each element's id to its direction (true=up, false=down)
            var pDirs = {};             
            for (var i = 0; i < numBars; i++) {
                var p = ns.create("<div id='_wtr_p" + i + "'></div>");
                // set user styles first; will overwrite any invalid style settings
                ns.style(p, initializer.barStyles);
                ns.style(p, {
                    "position": "absolute",
                    "top": (dist + 10) + "px",
                    "left": spc + (i * (bW + spc)) + "px",
                    "height": bH + "px",
                    "width": bW + "px"
                });
                ns.append(box, p);
                ps.push(p);
                pDirs[p.id] = true;
            }
    		
            var lbl = ns.create("<div>" + initializer.labelText + "</div>");
            // set user styles first; will overwrite any invalid style settings
            ns.style(lbl, initializer.labelStyles);
            ns.style(lbl, {
                "position" : "absolute",
                "top" : (bH + dist + 15) + "px",
                "left" : "10px",
                "font-family" : "Arial",
                "font-size" : "10px"
            });
            ns.append(box, lbl);
            
            // create a rotator for cycling through bars
            var rotator = ps.rotator();
            // flag used to kill animation and remove element
            var isCancelled = false;
            
            beginAnimation = function() {
                var el = rotator.next();
                var id = el.id;
                ns.style(el, initializer.activeStyles);
    			
    			if (!showAnim) {
    				window.setTimeout(
    				    function() {
    	                    ns.style(el, initializer.barStyles);                    
    	                    if (!isCancelled) {
    	                        beginAnimation(); 
    	                    }						
    					},
    					duration
    				);
    			} else {
    	            ns.animator(el).move({
    	                "x" : 0, 
    	                "y" : -20,
    	                "duration" : duration / 2, 
    	                "updater" : function() {},
    	                "callback" : function() {
    	                    ns.animator(el).move({
    	                        "x" : 0, 
    	                        "y" : 20,
    	                        "duration" : duration / 2, 
    	                        "updater" : function() {},
    	                        "callback" : function() {
    	                            ns.style(el, initializer.barStyles);                    
    	                            if (!isCancelled) {
    	                                beginAnimation(); 
    	                            }
    	                        } 
    	                    });             
    	                }
    	            });				
    			}
            };   	
        } else if (type == "boxes") {
        	var bH = initializer.boxHeight;
        	var bSpc = initializer.boxSpacing;
        	var bColor = initializer.boxColor;
        	var animColor = initializer.boxAnimColor;
            ns.style(box, {
                "position" : "relative",
                "top" : "0px",
                "left" : "0px",
                "height" : (bH * 3) + (bSpc * 4) + "px",
                "width"  : (bH * 3) + (bSpc * 4) + "px",
                "padding" : "20px"
            });
            var animBoxes = [];
        	for (var i = 0; i < 9; i++) {
        		var aBox = jwyre.create("<div></div>");
        		var top = ((i < 3) ? 0 : (i < 6) ? 1 : 2) * bH;
        		top += ((i < 3) ? 0 : (i < 6) ? 1 : 2) * bSpc;
        		var left = ((i % 3) * bH) +  ((i % 3) * bSpc);
        		jwyre.style(aBox, {
        			"position" : "absolute",
        			"top" : top + "px",
        			"left" : left + "px",
        			"height" : bH + "px",
        			"width" : bH + "px",
        			"background-color" : bColor
        		});
        		jwyre.append(box, aBox);
    			animBoxes.push(aBox);
        	}
            var lbl = ns.create("<div>" + initializer.labelText + "</div>");
            // set user styles first; will overwrite any invalid style settings
            ns.style(lbl, initializer.labelStyles);
            ns.style(lbl, {
                "position" : "absolute",
                "top" : (bH * 3) + (bSpc * 4) + 15 + "px",
                "left" : "10px",
                "font-family" : "Arial",
                "font-size" : "10px"
            });
            ns.append(box, lbl);
        	
        	// re-order to do proper animation rotation
        	var temp = jwyre.array();
    		// 4 is the middle box; is not an animation box, so will not push
        	temp.push(animBoxes[0], animBoxes[1], animBoxes[2], animBoxes[5], animBoxes[8], animBoxes[7], animBoxes[6], animBoxes[3]);
            // create a rotator for cycling through bars
            var rotator = temp.rotator();
            // flag used to kill animation and remove element
            var isCancelled = false;
            var lastBox = rotator.next();
            beginAnimation = function() {
            	jwyre.style(lastBox, "background-color", bColor);
            	lastBox = rotator.next();
            	jwyre.style(lastBox, "background-color", animColor);
            	if (!isCancelled) {
            		window.setTimeout(function() {
            			beginAnimation();
            		}, duration);
            	}
            };

        }
        
        /* ********************************************************** **
         *                  Public Members and Methods
        ** ********************************************************** */
        // function returned by jwydget.alert...used to close display box
        var closer;
        /**
         * 
         * @param {function} openerCallback (optional)
         */
        this.start = function(openerCallback) {
			if (closer) {				
				return;
			}
			openerCallback = h._getFn(openerCallback);
			var cAlert = ns.alert(alertProps);
            closer = cAlert.display({
                "message" : box,
                "openerCallback" : function() {
                    beginAnimation();
					openerCallback();					
				}
            });
        };
        /**
         * 
         */
        this.cancel = function() {
            isCancelled = true;
            if (closer) {
                closer();
				closer = null; 
            }
        };
    }	
	// add to namespace (jwyre.waiter())
	ns.waiter = function(initializer) { return new _Waiter(initializer); };
})(jwyre);
/**
 * @projectDescription
 * 
 * Creates a magnification area upon an image.
 * 
 * @author Ryan Hardy
 * @version 1.0
 */ 
(function(ns) {
	// check that jwyre is present
	if (!ns) {
	    throw new Error("jwyre.js is not present, and is required.");
	}
	// if present, make shortcuts to tools
	var h = ns._INTERNALS;

	function _Magnifier(imageContainer, viewBox, useIeHack) {
        useIeHack = (useIeHack === undefined) ? false : useIeHack;
        
        /*
         *  init class members
         */
        
        //  imageContainer is the surrounding element of the Image element
        imageContainer = ns.element(imageContainer);
        
        // obtains a node of the specified type by examining the element, or 
        // deep-iterating through all children nodes until an element of the 
        // specified type is found
        // returns null if none found
        //TODO: move this functionality and references to _elements()
        function _getNode(element, type) {
            if (!element || !type) {
                return null;    
            }
            if (element.nodeName == type) {
                return element; 
            } else {
                for (var i in element.childNodes) {
                    var temp = _getNode(element.childNodes[i], type);
                    if (temp != null) {
                        return temp;
                    }
                }
            }
            return null;
        }
        
        //  img is the actual image element
        // TODO: handle this with _elements() function
        var img = _getNode(imageContainer, "IMG");
        if (!img) {
            throw new Error("The image element is null.");
        }

        //  if an Image is passed in, rather than a containing elemenet, 
        //  create a container element and add the Image
        if (imageContainer == img) {
            imageContainer = document.createElement("div");
            img.parentNode.appendChild(imageContainer);
            ns.style(imageContainer, "top", img.offsetTop + "px");
            ns.style(imageContainer, "left", img.offsetLeft + "px");
            ns.style(imageContainer, "height", img.offsetHeight + "px");
            ns.style(imageContainer, "width", img.offsetWidth + "px");
            imageContainer.appendChild(img);
        }

        /*
         *  Returns either the view-box element provided as a constructor arg,
         *  or creates a view-box element dynamically.
         */
        function createViewbox(element) {
            if (element) {
                element = ns.element(viewBox);               
            } else {
                element = document.createElement("div");    
                imageContainer.appendChild(element);
                ns.style(element, "border", "2px black solid");
                ns.style(element, "backgroundColor", "transparent");
            }           
            
            return element;
        }
        
        //  create magnifier viwew-box
        var viewbox = createViewbox(viewBox);
        if (!viewbox) {
            throw new Error("The view box element is null");
        }
        
        // create magnified image
        var backImg = document.createElement("img");
        backImg.src = img.src;                      
        viewbox.appendChild(backImg);
        ns.style(backImg, "position", "absolute");   
        ns.style(backImg, "top", "0px"); 
        ns.style(backImg, "left", "0px");
        
        var mvbl;
        if (ns.isIE()) {
            mvbl = ns.moveable(imageContainer, viewbox);
        } else {
            mvbl = ns.moveable(viewbox, viewbox);
        }
                
        var imgX = ns.left(img);
        var imgY = ns.top(img);
        var imgWidth = img.offsetWidth;
        var imgHeight = img.offsetHeight;
        
        //  the width of the magnification area
        var vBoxWidth = 200;
        //  the height of the magnification area
        var vBoxHeight = 200;
        //  the X offset into the view-box to adjust when clicked
        var clickOffsetX = 5;
        //  the Y offset into the view-box to adjust when clicked
        var clickOffsetY = 5;
        //  the percentage to apply to the image for magnification
        //  (should be a float value greater than 1.0, where 1.0 would be same-sized)
        var magnification = 2.0;
        //  the width of the background magnified image element
        var mImgWidth = 0;
        //  the height of the background magnified image element
        var mImgHeight = 0;
        
        //  bounds checking for view-box
        var vLoX = 0;
        var vLoY = 0;
        var vHiX = 0;
        var vHiY = 0;
        
        //  used to indicate if magnifier is currently active
        var isActive = false;

        var toggleIE = false;
        var ctr = 0;;
        
        /**
         * Moves the view-box and backing image, assuring that a full view spectrum
         * is available.
         */
        function setPos(event) {
            event = ns.event(event);
                        
            //  getPoint() returns the x and y determined by the Moveable object
            //  (contained in a single object 'point')  based on offset and bounds
            var point = mvbl.getPoint(event);
            
            // the following calculations assure that the magnified backing
            // image slides in the background such that all portions of it are
            // viewable at the bounds of the view area          
            var rxV = (imgWidth - vBoxWidth) * 1.0;
            var rxM = (vBoxWidth - backImg.offsetWidth) * 1.0;
            var ryV = (imgHeight - vBoxHeight) * 1.0;
            var ryM = (vBoxHeight - backImg.offsetHeight) * 1.0;
            var mX = (point.x / rxV) * (rxM);
            var mY = (point.y / ryV) * (ryM);
            
            ns.style(backImg, "left", mX + "px");
            ns.style(backImg, "top" , mY + "px");
        }
        
        function resetView() {
            ns.style(viewbox, "position", "absolute");
            ns.style(viewbox, "height", vBoxHeight + "px");
            ns.style(viewbox, "width",  vBoxWidth  + "px");
            ns.style(viewbox, "display", "none");    
            ns.style(viewbox, "zIndex", "100");
            ns.style(viewbox, "overflow", "hidden");
        }
        
        function moveView(event) {
            if (!isActive) {
                return true;    
            }       
            setPos(event);          
            return true;
        }
        
        function active(event) {
            if (isActive) {
                return true;    
            }
            isActive = true;
            ns.style(backImg, "display", "block");                   
            setPos(event);
            return true;
        }               
        
        function deactive(event) {
            isActive = false;
            ns.style(backImg, "display", "none");                    
            return true;
        }

        var isToggleOn = false;
        /**
         * Called to create visual elements, and to reset visual elements if any 
         * setters are called.
         */
        function init() {
            resetView();            
            if (ns.isIE()) {
                if (useIeHack) {
                    function toggle(event) {
                        if (isToggleOn) {
                            isToggleOn = false;
                            deactive(event);
                        } else {
                            isToggleOn = true;
                            active(event);
                        }
                    }
                    ns.addListener(imageContainer, "click", toggle);
                    
                    ns.addListener(imageContainer, "mousemove", moveView);
                    ns.addListener(backImg, "mousemove", moveView);                
                    ns.addListener(img, "mousemove", moveView);                
                    ns.addListener(document.body, "mousemove", moveView);                  
                } else {
                    ns.addListener(imageContainer, "mousedown", active);
                    ns.addListener(backImg, "mousedown", active);
                    ns.addListener(img, "mousedown", active);
                    
                    ns.addListener(imageContainer, "mouseup", deactive);
                    
                    ns.addListener(imageContainer, "mousemove", moveView);
                    ns.addListener(backImg, "mousemove", moveView);                
                    ns.addListener(img, "mousemove", moveView);                
                    ns.addListener(document.body, "mousemove", moveView);                  
                }
            } else {
                ns.disableSelection(viewbox);

                ns.addListener(viewbox, "mousedown", active);
                
                ns.addListener(viewbox, "mouseup", deactive);
                ns.addListener(imageContainer, "mouseup", deactive);
                
                ns.addListener(viewbox, "mousemove", moveView);                
                ns.addListener(imageContainer, "mousemove", moveView);
            }
            
            ns.style(backImg, "height", (imgHeight * magnification) + "px"); 
            ns.style(backImg, "width", (imgWidth * magnification) + "px");   
            ns.style(backImg, "zIndex", "99");
            ns.style(backImg, "display", "none");

            // take view-box borders into account when setting boundry values
            function getVal(val) {
                val = parseInt(val);
                return (isNaN(val)) ? 0 : val;  
            }   
            var borderX = getVal(viewbox.style.borderLeftWidth) + getVal(viewbox.style.borderRightWidth);
            var borderY = getVal(viewbox.style.borderTopWidth) + getVal(viewbox.style.borderBottomWidth);
            
            vLoX = 0;
            vLoY = 0;
            vHiX = vLoX + imgWidth - vBoxWidth - borderX;
            vHiY = vLoY + imgHeight - vBoxHeight - borderY;
            mImgHeight = backImg.offsetHeight;
            mImgWidth = backImg.offsetWidth;
            
            mvbl.setXOffset(imgX + clickOffsetX);
            mvbl.setYOffset(imgY + clickOffsetY);
            mvbl.setXBounds(vLoX, vHiX);
            mvbl.setYBounds(vLoY, vHiY);
        }
        init();

        /**
         * Mouseover event handler for the on-page visible image element.
         */
        function hover(event) {
            ns.style(viewbox, "display", "block");   
            return true;
        }
        
        /**
         * Mouseout event handler for the on-page visible image element.
         */
        function unhover(event) {
            if (isActive) {
                return true;    
            }
            resetView();
            return true;
        }
        ns.addListener(imageContainer, "mouseover", hover);
        ns.addListener(imageContainer, "mouseout", unhover);
        
        /**
         * 
         * @param {Object} width
         * @param {Object} height
         */
        this.setViewboxWidth = function(width, height) {
            _checkNum(width, "Width", 0);
            _checkNum(height, "Height", 0);
            vBoxWidth = width;
            vBoxHeight = height;
            init();
        };
        
        /**
         * 
         * @param {Object} x
         * @param {Object} y
         */
        this.setClickOffset = function(x, y) {
            _checkNum(x, "X offset", 0);
            _checkNum(y, "Y offset", 0);
            clickOffsetX = x;
            clickOffsetY = y;
            init();
        };

        /**
         * 
         * @param {Object} value
         */  
        this.setMagnification = function(value) {
            _checkNum(value, "Magnification", 1.0);
            magnification = value;
            init();
        };
    }
    ns.magnifier = function(imageContainer, viewBox, useIeHack) { return new _Magnifier(imageContainer, viewBox, useIeHack); };         
})(jwyre);
/**
 * @projectDescription
 * Provides animation functionality, such as moving and fading.
 * 
 * @author Ryan Hardy
 * @version 1.0
 */
(function(ns) {
    // check that jwyre is present
    if (!ns) {
        throw new Error("jwyre.js is not present, and is required.");
    }
    // if present, make shortcuts to tools
    var h = ns._INTERNALS;
	
    // create a reference to today's date object
    var _today = ns.date();
    // mapping of date strings mapped to DOM elements
    var monthCache = {};
    // helper function used to create string used as key in monthCache object           
    function _getStr(date, calendar) {
        return calendar.getId() + "_" + date.getMonth() + "_" + date.getYear();
    }
    // helper function used to get total computed width of an element
    function _getWidth(el) {
        var w = 0;                  
        el = ns.element(el);
        w += ns.dimensions(el).width;
        w += ns.parseNumber(ns.style(el, "padding-left"), 0);
        w += ns.parseNumber(ns.style(el, "padding-right"), 0);
        w += ns.parseNumber(ns.style(el, "margin-left"), 0);
        w += ns.parseNumber(ns.style(el, "margin-right"), 0);
        return w;
    }
    // used to set the width of the entire calendar container based on 
    // internal widths
    function _fixWidth(hdrWidth, cId, html) {
        if (hdrWidth == null) {
            hdrWidth = 0;
            hdrWidth += _getWidth("#back_" + cId);
            hdrWidth += _getWidth("#months_" + cId);
            hdrWidth += _getWidth("#years_" + cId);
            hdrWidth += _getWidth("#next_" + cId);
            ns.style(html, "width", hdrWidth + "px");
            hdrWidth = ns.parseNumber(hdrWidth, null);
        }
        
        var tWidth = ns.dimensions("#dayGrid_" + cId).width;
        ns.style("#dayGrid_" + cId, "margin-left", ((hdrWidth - tWidth) / 2) + "px");
        
        return hdrWidth;
    }
    // used to convert or wrap the text into/in HTML, as needed, and applies the given id
    function _wrap(text, id) {
        var html = ns.create(text);
        if (!h._x(html)) {
            html = ns.create("<span>" + text + "</span>");
        }
        ns.attribute(html, "id", id);
        return html;
    }
    
    // defaults used to initialize Calendar objects
    var defaultProps = {
        global : {
            "position" : "relative",
            "background-color" : "white",
            "border" : "1px black solid",
            "padding" : "5px",
            "font-size" : "10px",
            "font-family" : "Arial",
            "cursor" : "pointer"
        },
        header : {
            "position" : "relative",
            "margin-bottom" : "10px",
            "padding-top" : "5px"
        },
        todayStyles : {
            "background-color" : "rgb(240, 240, 255)"
        },
        dayStyles : {
            "border" : "1px black solid", 
            "background-color" : "white", 
            "padding" : "3px",
            "text-align" : "center",
            "text-decoration" : "none"
        },
        offMonthDayStyles : {
            "background-color" : "rgb(220, 220, 220)"
        },
        dayHoverStyles : {
            "background-color" : "rgb(200, 200, 200)",
            "text-decoration" : "underline"
        },
        monthStyles : {
            "position" : "relative",
            "background-color" : "white"
        },
        tableAttrs : {},
        callback : function(date) { alert(date); },
        backHtml : "[back]",
        nextHtml : "[next]",
        closeHtml : "[close]",
        closeStyles : {
        },
        footerStyles : {
            "text-align" : "center"                
        },
        navStyles :  {
            "width" : "100px",
            "font-size" : "10px",
            "margin" : "0 5px 0 5px"
        },
        navHoverStyles : {},
        selectStyles : {
            "width" : "80px",
            "margin" : "0 5px 0 5px"
        },
        minDate : ns.date(),
        maxDate : ns.date().advanceYears(5)
    };
    
    /**
     * 
     * @param {Object} initializer with properties:
     *        {Object} todayStyles (optional) :
     *        {Object} dayStyles (optional) :
     *        {Object} offMonthDayStyles (optional) :
     *        {Object} dayHoverStyles (optional) :
     *        {Object} monthStyles (optional) :
     *        {Object} tableAttrs (optional) :
     *        {Object} callback (optional) :
     *        {Object} backHtml (optional) :
     *        {Object} nextHtml (optional) :
     *        {Object} navStyles (optional) :
     *        {Object} navHoverStyles (optional) :
     *        {Object} selectStyles (optional) :
     *        {Object} minDate (optional) :
     *        {Object} maxDate (optional) :
     */
    function _Calendar(initializer) {
        // the container box for the calendar
        var html;
        // the table holding the days info
        var daysHtml;
        // a date object representing the currently displayed info  
        var currDate = _today;
        // indicates if calendar is presently showing
        var isShowing = false;
        // indicates if the Calendar has already been built and displayed
        var isBuilt = false;
        // the select elements for months and years
        var months, years;
        // the currently selected elements for months and years
        var currMo, currYr;
        // the bounds for dates
        var minDate, maxDate;
        // a unique identifier for this instance
        var cId = ns.id();            
        // cached computer width of calendar header; used to fix width of entire widget
        var hdrWidth = null;
        // reference to self (prevents 'this' issues)
        var self = this;
        
        // ensure proper defaults are set
        initializer.callback = h._getFn(initializer.callback);
        
        // array containing initialization properties that are scalar values
        var scalars = ns.array("minDate", "maxDate", "backHtml", "nextHtml", "closeHtml", "callback");
        
        /**
         * Helper function used to check both provided and default 
         * properties to obtain a value; not for external use.
         * 
         * @param {string} name
         */
        this._get = function(name) {
            if (scalars.contains(name)) {
                return h._x(initializer[name]) ? initializer[name] : defaultProps[name];
            }
            var obj = (initializer.hasOwnProperty(name)) ? initializer[name] : {};
            return h._combine(obj, defaultProps[name]);
        };
        
        /**
         * Obtains the unique id of this instance.
         */
        this.getId = function() {
            return cId;
        };
                
        /**
         * @param {Object} init object with properties:
         *       {string || HTMLElement} parent :
         *       {jwyre.Date} date (optional) :
         *       {number} xOffset (optional) :
         *       {number} yOffset (optional) :
         *       {number} zIndex (optional) :
         */ 
        this.show = function(init) {
            if (isShowing) {
                return;
            }
            // is this just a call to reshow a hidden calendar?
            if (isBuilt && !h._x(init)) {
                ns.show(html);
                isShowing = true;
                return;                 
            }
            if (!isBuilt && !h._x(init)) {
                throw new Error("Must provide an initializer to build Calendar.");
            }
            // if no date is provided, use today
            ns.append(document.body, _getHtml(init.date || ns.date()));
            hdrWidth = _fixWidth(hdrWidth, cId, html);
            // position the element in ref. to parent, w/ offsets if present
            var pos = ns.position(init.parent);
            var dims = ns.dimensions(init.parent);
            var x = ns.parseNumber(init.xOffset, 0) + pos.left + dims.width;
            var y = ns.parseNumber(init.yOffset, 0) + + pos.top;
            var z = ns.parseNumber(init.zIndex, 100) + "";
            ns.styles(html, {"top" : y + "px", "left" : x + "px", "z-index" : z});
            ns.show(html);
            isBuilt = isShowing = true;
        };
        
        /**
         * Hides the Calendar object (may be re-shown in call to show()
         * without an initializer object).
         */
        this.hide = function() {
            if (!isShowing) {
                return;
            }
            ns.hide(html);
            isShowing = false;
        };
        
        /**
         * Removes the Calendar object's HTML from the DOM (may be re-shown 
         * in call to show() only with an initializer object).
         */
        this.remove = function() {
            ns.remove(html);
            isBuilt = isShowing = false;
        };
        
        // gets the HTML for this instance, either by building it first,
        // or updating it based on provided date
        function _getHtml(date) {
            if (h._x(html)) {
                _display(date);
                return html;
            }
            try {
                html = ns.create("<div></div>");
                ns.styles(html, self._get("global"));
                var header = ns.create("<div id='header_" + cId + "'></div>");
                ns.styles(header, self._get("header"));
                ns.append(html, header);
                
                // make back link
                var back = _wrap(self._get("backHtml"), "back_" + cId);
                ns.append(header, back);
                ns.styles(back, self._get("navStyles"));
                ns.addHover(back, self._get("navHoverStyles"));
                ns.addClick(back, function(event) {
                    _display(currDate.advanceMonths(-1));
                    return ns.kill(event);
                });
                ns.disableSelection(back);
                
                // add month drop-down
                months = ns.create("<select id='months_" + cId + "'></select>");
                ns.append(header, months);
                ns.styles(months, self._get("selectStyles"));
                ns.array(ns.Date.MONTHS).each(function(val, ctr) {
                    ns.append(months, ns.create("<option value='" + ctr + "'>" + this.name + "</option>"));
                });
                ns.addListener(months, "change",
                    function(event) {
                        var mo = ns.selected(months);
                        if (_display(currDate.setMonth(mo))) {
                            currMo = mo;
                        } else {
                            _display(currDate);
                        }
                        return ns.kill(event);
                    }
                );
                
                // set values for use in _display() checking
                minDate = self._get("minDate");
                maxDate = self._get("maxDate");
                   
                // add year drop-down
                years = ns.create("<select id='years_" + cId + "'></select");
                ns.append(header, years);
                ns.styles(years, self._get("selectStyles"));
                ns.range(minDate.getYear(), maxDate.getYear()).each(function() {
                    ns.append(years, ns.create("<option value='" + this + "'>" + this + "</option>"));
                });
                ns.addListener(years, "change", 
                    function(event) {
                        var yr = ns.selected(years);
                        if (_display(currDate.setYear(yr))) {
                            currYr = yr;
                        } else {
                            _display(currDate);
                        }
                        return ns.kill(event);
                    }
                );

                // make next link
                var next = _wrap(self._get("nextHtml"), "next_" + cId);
                ns.append(header, next);
                ns.styles(next, self._get("navStyles"));
                ns.addHover(next, self._get("navHoverStyles"));
                ns.addClick(next, function(event) {
                    _display(currDate.advanceMonths(1));
                    return ns.kill(event);
                });
                ns.disableSelection(next);
                
                daysHtml = self._getMonthHtml(date);
                ns.append(html, daysHtml);
                
                var footer = ns.create("<div></div>");
                ns.append(html, footer);
                ns.styles(footer, self._get("footerStyles"));
                var closer = _wrap(self._get("closeHtml"), "close_" + cId);
                ns.append(footer, closer);
                ns.styles(closer, self._get("closeStyles"));
                ns.addClick(closer, function(event) { self.hide(); return ns.kill(event); });
                
                _display(currDate);
                return html;
            } catch (e) {
                hdrWidth = html = null;
                ns.log(e.message);
            }
        };
        
        // sets the current day grid and month/year selection (if date is in bounds)
        // returns false if it is out of bounds, true otherwise
        function _display(date) {
            if (!date) {
                return false;
            }
            if (date.isBefore(minDate) || date.isAfter(maxDate)) {
                return false;
            }
            currDate = date;
            var temp = self._getMonthHtml(date, self);
            ns.replace(daysHtml, temp);
            ns.select(months, currDate.getMonth());
            ns.select(years, currDate.getYear());
            daysHtml = temp;
            try {
                hdrWidth = _fixWidth(hdrWidth, cId, html);
            } catch (e) {
                hdrWidth = null;
                // catch and eat; this will throw ex. on first display in IE
            }
            return true;
        }
    }
    /**
     * Builds HTML for each calendar day; not for external use.
     * 
     * @param {jwyre.Date} refDate
     * @param {jwyre.Date} date
     */
    _Calendar.prototype._getDayHtml = function(refDate, date) {
        var html = ns.create("<td>" + date.getDay() + "</td>");
        // check if this is today
        ns.styles(html, this._get("dayStyles"));            
        if (date.equals(_today)) {
            ns.styles(html, this._get("todayStyles"));                   
        } 
        // check if this date is not in the present month
        else if (date.getMonth() != refDate.getMonth()) {
            ns.styles(html, this._get("offMonthDayStyles"));                 
        }
        ns.addHover(html, this._get("dayHoverStyles"));
        var calendar = this;
        ns.addClick(html,
            function(event) {
                h._getFn(calendar._get("callback")).call(calendar, date);
                return ns.kill(event);
            }
        );
        return html;
    };
    /**
     * Builds HTML for each calendar week; not for external use.
     * 
     * @param {jwyre.Date} refDate
     * @param {jwyre.Date} date
     */
    _Calendar.prototype._getWeekHtml = function(refDate, date) {
        var week = date.getWeek();
        var html = ns.create("<tr></tr>");
        var calendar = this;
        week.each(function() {
            ns.append(html, calendar._getDayHtml(refDate, this, calendar))
        });
        return html;
    };
    /**
     * Builds HTML for each calendar month; not for external use.
     * 
     * @param {jwyre.Date} date
     */
    _Calendar.prototype._getMonthHtml = function(date) {
        // check cache first
        var html = monthCache[_getStr(date, this)];
        if (h._x(html)) {
            return html;
        }
        html = ns.create("<table id='dayGrid_" + this.getId() + "'></table>");
        ns.styles(html, this._get("monthStyles"));
        ns.iterator(this._get("tableAttrs"), true).getArray().each(function() {
            ns.attribute(html, this.name, this.value);
        });
        // this is done for IE compliance
        ns.append(html, ns.create("<thead></thead>"));
        
        var tbody = ns.create("<tbody></tbody>");    
        ns.append(html, tbody);
        ns.append(html, ns.create("<tfoot></tfoot>"));
        var mo = date.getMonth();
        var fDay = date.getFirstDay();
        
        while (true) {
            ns.append(tbody, this._getWeekHtml(date, fDay, this));
            fDay = fDay.advanceDays(7);
            if (fDay.getWeek()[0].getMonth() != mo) {
                break;
            }
        }
        monthCache[_getStr(date, this)] = html;
        
        return html;
    };
    
    // used to track Calendar objects added to page with call to Calendar.add()
    var calendars = ns.array();
    
    // create Calendar namespace and add global functions
    ns.Calendar = {
        /**
         * Adds a standard listener to an input element.  Returns the Calendar
         * object that is created.  If singleton is provided and true, will 
         * ensure that only one Calendar object that has been added to page
         * with Calendar.add() is visible on the page at one time.  A unique name
         * must be provided to enable interaction with the underlying Calendar 
         * object.
         * 
         * @param {Object} initializer with properties (in addition to Calendar
         * constructor properties):
         * 
         *     {string || HTMLElement} input :
         *     {integer} x (optional) :
         *     {integer} y (optional) :
         *     {integer} z (optional) :
         *     {boolean} singleton (optional) :
         */
        add : function(initializer) {
            var input = ns.element(initializer.input);
            // needed for calendar.show()
            initializer.parent = input;
            initializer.x = ns.parseNumber(initializer.x, 0);
            initializer.y = ns.parseNumber(initializer.y, 0);
            initializer.z = ns.parseNumber(initializer.z, 0);
            initializer.singleton = ns.parseBoolean(initializer.singleton, false);
            var fn = h._getFn(initializer.callback);
            initializer.callback = function(date) {
                if (fn.call(this, date) != false) {
                    input.value = "" + date;
                    this.hide();                    
                }
            };
            var calendar = new _Calendar(initializer);
            function _show(event) {
                calendars.each(function() { if (this.isSingle) { this.cal.hide(); } });
                calendar.show(initializer);
                // do not re-initialize on subsequent shows
                initializer = null;
                return ns.kill(event);
            }
            // keep track of cal info so that safe deletion can occur (see purge())
            calendars.push({ "cal" : calendar, "isSingle" : initializer.singleton, "input" : input, "callback" : _show });
            ns.addListener(input, "focus", _show, ns.Event.CAPTURE);
            
            return calendar;
        },
        remove : function(calendar) {
            for (var i = calendars.iterator(); i.hasNext(); ) {
                var obj = i.next();
                if (obj.cal == calendar) {
                    ns.removeListener(obj.input, "focus", obj.callback, ns.Event.CAPTURE); 
                    obj.cal.remove();
                    calendars.remove(obj);
                    break;
                }
            }
        },
        /**
         * Hides all Calendar objects that are showing that have been added
         * with call to Calendar.add().
         */
        hide: function() {
            calendars.each(function() { this.cal.hide(); });
        },
        /**
         * Clears all internal caches; used to manage memory consumption.
         */
        purge : function() {
            this.hide();
            calendars.each(function() { 
                ns.removeListener(this.input, "focus", this.callback, ns.Event.CAPTURE); 
                this.cal.remove();
            });
            delete calendars;
            calendars = ns.array();
            delete monthCache;
            monthCache = {};
        }
    };
    // assign basic constructor function
    ns.calendar = function(initializer) { return new _Calendar(initializer); };
})(jwyre);  

/**
 * @projectDescription
 * Provides animation functionality, such as moving and fading.
 * 
 * @author Ryan Hardy
 * @version 1.0.6
 */
(function(ns) {
    // check that jwyre is present
    if (!ns) {
        throw new Error("jwyre.js is not present, and is required.");
    }
    // if present, make shortcuts to tools
    var h = ns._INTERNALS;
	
    // the number of millis between animation moves
    var INCREMENT = 100;
    // the minimum animation interval
    var MIN_INTERVAL = 20;
    // collection of internal objects
    var internalObjects = ns.array();
    // used to track and destroy internal objects
    var internalObjCounter = 0;
	// mapping of Mover group ids mapped to arrays of still-live Movers in the group
	var internalMovers = {};
    
	function log(msg, override) {
		try {
	        override = ns.parseBoolean(override, true);
	        if (override) {
	            ns.log(msg);
	        }			
		// console is undefined at times in both FireFox and IE	
		} catch (e) {}
	}
	
    // tracks internal objects; begins object's animation cycle
    function add(fnName, newFn, init) {
        var id = internalObjCounter++;
        init.id = id;
        
        var object = new newFn(init);
        internalObjects[id] = object;
        object[fnName]();
    }
	
	// is called by Mover when complete; returns true if it is the last Mover 
	// in the group to complete
	function remove(groupId, moverId) {
		var count = internalMovers[groupId];
		if (count == undefined) {
			return true;
		}
		internalMovers[groupId] = --count;
        log("Removing - GroupId: " + groupId + ", count=" + internalMovers[groupId]);
		return count == 0;
	}
    
    // cleans up object when complete from internal objects
    function cleanUp(id) {
        var _this = internalObjects[id];
        internalObjects[id] = null;
        delete _this;
    }
	
	// returns the distance between the 2 sets of points
	function dist(x, y, x2, y2) {
		return Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2));
	}
	
	// gets the percentage completion of the travel between start and end points
	function getProgress(startX, startY, endX, endY, x, y) {
		return 0.01;
		var dTotal = dist(startX, startY, endX, endY);
		var dCurr =  dist(x, y, endX, endY);
		
		return 1 - (dCurr / dTotal);
	}
    
	// flag used to indicate if mover debugging should be output
	var outputMove = false;
	    
    /**
     * Internal helper object for peforming a 'move' animation.
     * 
     * @param initializer {Object} with properties:
     * 
     *      dX : the -x distance to travel
     *      dY : the -y distance to travel
     *      el : the HTMLElement currently being moved
     *      duration : the time in milliseconds until next move
     *      updater : the function called on each animation iteration
     *      callback : the function to call at termination
     *      ctr : the total number of elements to move (used to
     *            track when to fire callback)
     *      id :  the 'internalObjectCounter' assigned to this instance
     *            (for garbage collection)
     */
    function Mover(initializer) {
        var dX, dY, el, duration, callback, id, groupId, moverId;
        var updater = h._getFn(initializer.updater);
        function _callback() {
            h._getFn(initializer.callback)(initializer._this.isCancelled());
            cleanUp(id);
        }
        
        dX = initializer.dX;
        dY = initializer.dY;
        log("INITIALIZING MOVER....", outputMove);
        log("dX: " + dX + ", dY: " + dY, outputMove);
        
        el = initializer.el;
        duration = initializer.duration;
        id = initializer.id;
        moverId = initializer.moverId;
        groupId = initializer.groupId;
		
        var p = ns.position(el, true);
        var vX = dX / duration;
        var vY = dY / duration;
        log("vX: " + vX + ", vY: " + vY, outputMove);
        var termX = p.left + dX;
        var termY = p.top + dY;         
        log("termX: " + termX + ", termY: " + termY, outputMove);
        var currTime = startTime = jwyre.Date.currentTimeMillis(); 
        var distTotal = dist(p.left, p.top, termX, termY);
        log("currTime: " + currTime, outputMove);
        log("MOVER INITIALIZED.", outputMove);
                    
        this.move = function() {
            if (initializer._this.isCancelled()) {
                _callback();
                return;
            }
            log("this.move() iteration.", outputMove);
            var time = jwyre.Date.currentTimeMillis();
            var elapsed = time - currTime;
            currTime = time;
            log("elapsed: " + elapsed, outputMove);
            var p = ns.position(el, true);
            var x = p.left, y = p.top;
            var incX = vX * elapsed;
            var incY = vY * elapsed;
            log("incX: " + incX + ", incY: " + incY, outputMove);
            
            // check for termination
            log("Term check: distX=" + Math.abs(termX - x) + ", distY=" + Math.abs(termY - y), outputMove);
            if (Math.abs(termX - x) <= Math.abs(incX) && Math.abs(termY - y) <= Math.abs(incY)) {
	            log("this.move() terminating.", outputMove);
                ns.position(el, termX, termY);
                if (remove(groupId, moverId)) {
                    _callback();
                }
            } else {
                x = Math.round(x + incX), y = Math.round(y + incY);
                ns.position(el, x, y);
                distCurr = dist(x, y, termX, termY);
                var INT = 10;
                log("Progress: (" + INT + ") " + (1 - (distCurr / distTotal)), outputMove);
                updater(1 - (distCurr / distTotal));
                window.setTimeout(arguments.callee, INT);
            }
        };
        
        /**
         * Begins the move animation.
         */
        this.start = function() {
            this.move();                    
        };
    }

    /**
     * 
     * @param {Object} with properties:
     * 
     *       fElement : 
     *       duration :
     *       begin (optional) : a whole number between 0 and 100
     *       end (optional; required if begin specified) : a whole number between 0 and 100 
     *       callback (optional) :
     *       id : the 'internalObjectCounter' assigned to this instance (for garbage collection)
     */
    function Fader(initializer) {
        var fElement, duration, begin, end, callback, id;
        fElement = initializer.fElement;
        //    how long, in millis, to fade for
        duration = initializer.duration;
        begin = initializer.begin;
        end = initializer.end;
        callback = initializer.callback;
        id = initializer.id;
        //    the opacity value the fElement is to have set at inception
        var beginOpacity = 0;
        //    the opacity value at which fading will be terminated
        var endOpacity = 100;
        if (h._x(begin)) {
            if (!h._x(end)) {
                throw new Error("If a begin opacity value is specifed, an end must also be specified.");
            }
            beginOpacity = begin;
            endOpacity = end;
            h._checkNum(beginOpacity, "Opacity", 0, 100);
            h._checkNum(endOpacity, "Opacity", 0, 100);
        }
        function _callback() {
            h._getFn(callback)();
            cleanUp(id);
        }
        
        // how often to call the fade() function
        var interval;
        //    the incremental change value of opacity, either +1 or -1
        var fadeDir;
        //  current opacity level
        var current = beginOpacity;
        // the fade unit multiplier
        var units = 1;
           
        /**
         * Function used to begin the fading of the fElement.
         */
        this.start = function() {
            fadeDir = (endOpacity - beginOpacity) / Math.abs(endOpacity - beginOpacity);
            ns.style(fElement, "opacity", beginOpacity);
            ns.show(fElement);
            interval = duration / Math.abs(endOpacity - beginOpacity);
            if (interval < 1) {
                units /= interval;
                interval = 1;
            } 
            units *= fadeDir;
            window.setTimeout(fade, interval);
        };

        /*
         * Internal function that either changes the opacity value of the fElement,
         * or, if end opacity has been reached, terminates fading.
         */
        function fade() {
            current += units;
            //   setOpacity returns true only if fading should continue, i.e.
            //   end opacity has not been reached
            if (setOpacity(current)) {
                window.setTimeout(fade, interval);
            } else {
                _callback();
            }
        }

        /*
         * Sets the fElement's opacity value, first check to see that the value
         * does not go beyond the end opacity.  Returns false if the endOpacity
         * has been reached.
         */
        function setOpacity(value) {
            if ((fadeDir < 0 && value <= endOpacity) || (fadeDir > 0 && value >= endOpacity)) {
                endOpacity /= 100;
                ns.style(fElement, "opacity", endOpacity);
                return false;
            } else {
                value /= 100;
                ns.style(fElement, "opacity", value);
                return true;
            }
        }        
    }

    /**
     * 
     * @param {string || HTMLElement} element
     */	
    function _Animator(element) {
        element = ns.elements(element);
        if (element.size() == 0) {
            throw new Error("Element is null (element=" + e + ")");
        }
		// get reference to this for use in inner functions
		var self = this;
		// is set to true if canceled manually through call to cancel()
        var cancelled = false;

        /**
         * Provides the basic framework for doing a custom animation with the
         * Animator element.  Expects a function, updater, that will be called
         * repeatedly at the specified interval so long as the function
         * returns true.  For the duration of the animation, the function
         * will be a member of the element such that a reference to
         * 'this' within the function will be a reference to the element.
         * At such point that the function returns false, the callback
         * will be called, if provided.
         * 
         * @param {Object} initializer with properties:
         *      updater: a function called on each iteration
         *      interval: an integer indicating how many milliseconds between iterations
         *      callback: an optional function to provide that will be called at termination
         */
        this.custom = function(initializer) {
            if (arguments.length > 1) {
                throw new Error("The arguments have been changed: expects one initializer object (see docs).");
            }
            var updater, interval, callback;
            updater = initializer.updater;
            interval = initializer.interval;
            callback = initializer.callback;
            for (var i = element.iterator(); i.hasNext();) {
                var el = i.next();
                el['_custom'] = updater;
                function _() {
                    if (el['_custom']()) {
                       window.setTimeout(_, interval);  
                    } else {
                        // IE 7 wont allow deletion
                        if (!ns.isIE(7) && !ns.isIE(6)) {
                            delete el['_custom'];                           
                        } 
                        h._getFn(callback)();
                    }
                };
                _();
            }
        };
        
        /**
         * Creates an animation by iterating through a collectiosn of images.
         * 
         * @param {Object} initailizer with properties:
         *       images: an array of image src values OR an array of
         *               objects with properties of 'image' and 'interval'
         *               so that each image may be provided a unique interval value
         *       interval: an optional property (if an array of objects is provided
         *                 for 'images') to specifiy a global interval value,
         *                 to specify how many milliseconds between animation updates
         *       callback: an optional function to be called at termination of
         *                 animation
         *       attrs:  an optional object that contains name/value pairs
         *               of HTML attributes to apply to HTMLImageElements created
         */   
        this.animate = function(initializer) {
            if (arguments.length > 1) {
                throw new Error("The arguments have been changed: expects one initializer object (see docs).");
            }
            var images, interval, callback, attrs;
            images = ns.array(initializer.images);
            if (images.size() < 2) {
                throw new Error("Must provide at least 2 images ot create an animation.");
            }
            interval = (!h._x(initializer.interval)) ? -1 : initializer.interval;
            callback = h._getFn(initializer.callback);
            attrs = (h._x(initializer.attrs)) ? initializer.attrs : {};
            
            //TODO: for now, only use single element
            var el = element[0];
            var pics = ns.array();
            images.each(
                function(item) {
                    var img = new Image();
                    if (typeof item == "string") {
                        img.src = item;
                    } else  {
                        img.src = item.image;
                    }
                    for (var att in attrs) {
                        ns.attribute(img, att, attrs[att]);                      
                    }
                    pics.push(img);
                }
            );
            function getInterval(index) {
                if (index >= images.size()) {
                    return -1;
                }
                if (typeof images[index] == "string") {
                    return interval;
                } else {
                    return images[index].interval;
                }
            }
            function show(index) {
                if (cancelled) {
                    return;
                }
                if (index + 1 == images.size()) {
                    callback();
                    return;
                }
                var newPic = pics[index + 1];
                ns.empty(el);
                ns.append(el, newPic);
                setTimeout(function() { show(index + 1); }, getInterval(index));
            }
            ns.append(el, pics[0]);
            show(0);
        };
        
        /**
         * Moves the animated element(s) by the distance provided in
         * initializer over the specified period.
         * 
         * @param {Object} initializer with properties:
         *       x : the total -x distance to move the animated element
         *       y : the total -y distance to move the animated element 
         *       duration : the total amount of time to perform animation
         *       callback : an optional function to provide that will be 
         *                  called at animation termination
         *       updater : an optional function to provide that will be 
         *                  called on each animation iteration
         */
        this.move = function(initializer) {
            if (arguments.length > 1) {
                throw new Error("The arguments have been changed: expects one initializer object (see docs).");
            }
            var x, y, duration, callback, updater;
            x = initializer.x;
            y = initializer.y;
            duration = ns.parseNumber(initializer.duration, 1);
            duration = Math.max(duration, 1);
            callback = h._getFn(initializer.callback);
            updater = h._getFn(initializer.updater);
            
            // used to track if all elements are done
            var id = jwyre.id(); 
            internalMovers[id] = element.size();
            log("Adding - GroupId: " + id + ", internal count=" + internalMovers[id]);

            //TODO: for now, only want callback fired once; figure out if 
            // the need for callback firing at the end of each is needed
            element.each(function(item) {
                add("start", Mover, {
                    "dX" : x,
                    "dY" : y,
                    "el" : item,
                    "duration" : duration,
                    "callback" : callback,
                    "updater" : updater,
                    "groupId" : id,
                    "moverId" : jwyre.id(),
					"_this" : self
                });
            });
        };
        
        /**
         * Moves the animated element(s) to the given point provided in
         * initializer over the specified period.
         * 
         * @param {Object} initializer with properties:
         *       x : the ending x coordinate
         *       y : the ending y coordinate
         *       isRelative : an optional boolean value to specifiy
         *       whether ending position is a relative position within
         *       the element's parent, or an absoluet position on the page (default is true)
         *       duration : the total amount of time to perform animation
         *       callback : an optional function to provide that will be 
         *                  called at animation termination
         *       updater : an optional function to provide that will be 
         *                  called on each animation iteration
         */
        this.moveTo = function(initializer) {
            if (arguments.length > 1) {
                throw new Error("The arguments have been changed: expects one initializer object (see docs).");
            }
            var x, y, isRelative, duration, callback, updater;
            x = initializer.x;
            y = initializer.y;
            isRelative = ns.parseBoolean(initializer.isRelative, true);
            duration = ns.parseNumber(initializer.duration, 1);
            duration = Math.max(duration, 1);
            callback = h._getFn(initializer.callback);
            updater = h._getFn(initializer.updater);
			
            // used to track if all elements are done
            var id = jwyre.id(); 
            internalMovers[id] = element.size();
            log("Adding - GroupId: " + id + ", internal count=" + internalMovers[id]);
			
            //TODO: for now, only want callback fired once; figure out if 
            // the need for callback firing at the end of each is needed
            element.each(function(item) { 
                var pos = ns.position(item, isRelative);
                var dX = x - pos.left;
				var dY = y - pos.top;
				
                add("start", Mover, {
                    "dX" : dX,
                    "dY" : dY,
                    "el" : item,
                    "duration" : duration,
                    "callback" : callback,
                    "updater" : updater,
                    "groupId" : id,
					"moverId" : jwyre.id(),
                    "_this" : self
                });
            });
        };
     
        /**
         * 
         * @param {integer} duration
         * @param {float} begin (optional)
         * @param {float} end (optional, required if begin specified)
         * @param {Function} callback (optional)
         */
        this.fade = function(duration, begin, end, callback) {
            element.each(function(item) {
                add("start", Fader, {
	                "fElement" : item, 
	                "duration" : duration, 
	                "begin" : begin, 
	                "end" : end, 
	                "callback" : callback,
	                "_this" : self
	            });				
			});
        };
        
        /**
         * 
         * @param {integer} duration
         * @param {Function} callback (optional)
         */
        this.fadeIn = function(duration, callback) {
			element.each(function(item) {
	            add("start", Fader, {
	                "fElement" : item, 
	                "duration" : duration, 
	                "begin" : 0, 
	                "end" : 100, 
	                "callback" : callback,
	                "_this" : self
	            });				
			});
        };

        /**
         * 
         * @param {integer} duration
         * @param {Function} callback (optional)
         */
        this.fadeOut = function(duration, callback) {
			element.each(function(item) {
	            add("start", Fader, {
	                "fElement" : item, 
	                "duration" : duration, 
	                "begin" : 100, 
	                "end" : 0, 
	                "callback" : callback,
	                "_this" : self
	            });				
			});
        };
        
        /**
         * 
         */   
        this.cancel = function() {
            cancelled = true;
        };
		
		/**
		 * 
		 */
		this.isCancelled = function() {
			return cancelled;
		};
        
        /**
         * 
         */   
        this.reset = function() {
            cancelled = false;
        };
    }    
	
	ns.animator = function(element) { return new _Animator(element); };        
})(jwyre);
/**
 * @projectDescription
 * Creates a drop-shadow effect on a given element.
 * 
 * May be customized with the following initializer properties:
 * 
 * {
 *      element {string || HTMLElement} :
 *      color {string} :
 *      vOffset {number} : (optional)
 *      hOffset {number} : (optional)
 *      fontSize {number} : (optional)
 * }
 * 
 * @author Ryan Hardy
 * @version 1.0
 */
(function(ns) {
    // check that jwyre is present
    if (!ns) {
        throw new Error("jwyre.js is not present, and is required.");
    }
    // if present, make shortcuts to tools
    var h = ns._INTERNALS;
	
    function _DropShadow(initializer) {
        initializer.element = ns.element(initializer.element);
        if (!h._x(initializer.element)) {
            throw new Error("Element is null.");
        }
        h._checkType(initializer.color, "string", "Color must be a CSS color specification.");            
        initializer.vOffset = ns.parseNumber(initializer.vOffset, 2);
        initializer.hOffset = ns.parseNumber(initializer.hOffset, 2);
        initializer.shadows = ns.parseNumber(initializer.shadows, 1);
        initializer.reductionFactor = ns.parseNumber(initializer.reductionFactor, 10);
         
        if (h._x(initializer.fontSize)) {
            h._checkType(initializer.fontSize, "number", "Font size must be a number (in pixels).");
            initializer.fontSize += "px";
        } else {
            initializer.fontSize = ns.style(initializer.element, "fontSize");
        }
        var text = ns.text(initializer.element);
        if (!h._x(text) || h._strEq(text, "")) {
            throw new Error("There is no text within element.");
        }
        var forefront = ns.create("<div>" + text + "</div>");
        ns.styles(forefront, {
            "position" :  "absolute",
            "top" :  "0",
            "left" :  "0",
            "zIndex" :  "2"
        });
        ns.empty(initializer.element);
        ns.append(initializer.element, forefront);
        
        function getReducedColor(color, factor) {
            // returns an array
            color = h._convertColor(color, true);
            var hex = "#";
            color.each(function() { 
                var v = 255 - (parseInt(this, 16) * factor);
                hex += Math.round(v).toString(16);
            });
            return hex;
        }
        
        ns.range(1, initializer.shadows).each(
            function() {
                var v, h, color;
                if (initializer.shadows == 1) {
                    v = initializer.vOffset;
                    h = initializer.hOffset;
                    color = initializer.color;
                } else {
                    v = (this * initializer.vOffset);
                    h = (this * initializer.hOffset);
                    var fct = 100 - (this * initializer.reductionFactor);
                    color = getReducedColor(initializer.color, fct / 100);
                }
                var shadow = ns.create("<div>" + text + "</div>");
                ns.styles(shadow, {
                    "position" : "absolute",
                    "top" : v + "px",
                    "left" : h + "px",
                    "zIndex" : "1",
                    "fontSize" : initializer.fontSize,
                    "color" : color
                }); 
                ns.append(initializer.element, shadow);                  
            }
        );
    }
    
    ns.dropShadow = function(initializer) { return new _DropShadow(initializer); };
})(jwyre);

/**
 * @projectDescription
 * Provides animation functionality, such as moving and fading.
 * 
 * @author Ryan Hardy
 * @version 1.0.2
 */
(function(ns) {
    // check that jwyre is present
    if (!ns) {
        throw new Error("jwyre.js is not present, and is required.");
    }
    // if present, make shortcuts to tools
    var h = ns._INTERNALS;
		
	function _ColorPanel() {
	    // the container for the color panel
	    var panel;
	    // the callback function for the currently displayed color panel
	    var cb;
		// get reference to self to avoid 'this' use issues
	    var self = this;
		
	    var basics = jwyre.array(
	       "#000000", "#333333", "#666666", "#999999",
	       "#CCCCCC", "#FFFFFF", "#FF0000", "#00FF00",
	       "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF");
	    
	    // the currently chosen color
	    var current = "#000000";
	        
	    // converts an rgb css value to a hex string
	    function convertToHex(rgb) {
	        rgb = jwyre.trim(rgb);
	        if (rgb.indexOf("#") == 0) {
	            return rgb;
	        }
	        rgb = rgb.replace("rgb(", "");      
	        rgb = rgb.replace(")", "");
	        var cs = rgb.split(/\s*,\s*/);
	        var hex = "#";
	        function pad(n) {
	            if (("" + n).length == 1) {
	                return "0" + n;
	            }
	            return n;
	        }
	        hex += pad(parseInt(cs[0]).toString(16));
	        hex += pad(parseInt(cs[1]).toString(16));
	        hex += pad(parseInt(cs[2]).toString(16));
	        
	        return hex.toUpperCase();
	    }
	    
	    /*
	     * 
	     */
	    function createPanel(callback) {
	        // create the container
	        panel = ns.create("<div></div>");
	        ns.styles(panel, 
	        {
	            "position" : "absolute",
	            "top" : "20px",
	            "left" : "20px",
	            "height" : "260px",
	            "width" : "300px",
	            "border" : "1px black solid",
	            "background-color" : "rgb(200, 200, 200)",
	            "z-index" : "10000"
	        });
	        
	        // create the controls area
	        var header = ns.create("<div></div>");
	        ns.styles(header, 
	        {
	            "position" : "absolute",
	            "top" : "0px",
	            "left" : "0px",
	            "height" : "40px",
	            "width" : "100%",
	            "border-bottom" : "1px black solid",
	            "background-color" : "rgb(180, 180, 180)",
	            "z-index" : "2"
	        });
	        ns.append(panel, header);
	        
	        // the preview box
	        var preview = ns.create("<div></div>");
	        ns.styles(preview, 
	        {
	            "position" : "absolute",
	            "top" : "10px",
	            "left" : "20px",
	            "height" : "20px",
	            "width" : "80px",
	            "border" : "1px black solid",
	            "background-color" : current,
	            "z-index" : "2"
	        });
	        ns.append(header, preview);
	        ns.addHover(preview, 
	            function() {
	                ns.style(preview, "border-color", "white");
	            },
	            function() {
	                ns.style(preview, "border-color", "black");              
	            }
	        );
	        ns.addClick(preview,
	            function(event) {
	                ns.style(preview, "border-color", "black");              
	                cb(current);                
	                return ns.kill(event);
	            }  
	        );
	        
	        // the color value box
	        var cOutput = ns.create("<input type='text' readonly='readonly' value='" + current + "'/>");
	        ns.styles(cOutput, 
	        {
	            "position" : "absolute",
	            "top" : "12px",
	            "left" : "110px",
	            "height" : "12px",
	            "width" : "50px",
	            "padding" : "2px",
	            "border" : "1px black solid",
	            "background-color" : "white",
	            "z-index" : "2",
	            "font-family" : "Arial",
	            "font-size" : "10px"
	        });
	        ns.append(header, cOutput);
	
	        // the 'close' control
	        var close = ns.create("<div>close</div>");
	        ns.styles(close, 
	        {
	            "position" : "absolute",
	            "top" : "12px",
	            "left" : "260px",
	            "height" : "12px",
	            "padding" : "2px",
	            "z-index" : "2",
	            "font-family" : "Arial",
	            "font-size" : "10px",
	            "cursor" : "pointer"
	        });
	        ns.append(header, close);
	        
	        ns.addHover(close,
	            function() {
	                ns.style(close, "text-decoration", "underline");
	            },
	            function() {
	                ns.style(close, "text-decoration", "none");              
	            }
	        );
	        ns.addClick(close, 
	            function(event) { 
	                ns.style(close, "text-decoration", "none"); 
	                self.hide();
	            }
	        );
	        
	        // stack used to keep track of the last selected colors
	        var lastCubes = ns.array();
	        var lastColors = ns.array();
	        
	        function addLastColor(color) {
	            lastColors = ns.array();
	            for (var i = lastCubes.length - 1; i > 0; i--) {
	                var ths = lastCubes[i];
	                var lst = lastCubes[i - 1];
	                var c = ns.attribute(lst, "name");
	                if (!lastColors.contains(c)) {
	                    lastColors.push(c);
	                }
	                ns.attribute(ths, "name", c);
	                ns.style(ths, "background-color", c);
	            }
	            ns.attribute(lastCubes[0], "name", color);
	            ns.style(lastCubes[0], "background-color", color);
	        }
	                                
	        // create hover and click events for color cubes
	        function fn(c) {
	            // performs unhover ops; shared by unhover and click fns
	            function reset() {
	                ns.styles(c, 
	                {
	                    "border-color" : "black",
	                    "z-index" : "1"
	                });
	                ns.style(preview, "background-color", current);
	                ns.attribute(cOutput, "value", current);             
	            }
	            return {
	                hover : function(event) {
	                    ns.styles(c, 
	                    {
	                        "border-color" : "white",
	                        "z-index" : "2"
	                    });
	                    ns.style(preview, "background-color", ns.style(c, "background-color"));
	                    ns.attribute(cOutput, "value", convertToHex(ns.attribute(c, "name")));
	                },
	                unhover : function(event) {
	                    reset();
	                },
	                click : function(event) {
	                    current = convertToHex(ns.attribute(c, "name"));
	                    ns.attribute(cOutput, "value", current);
	                    ns.style(preview, "background-color", current);
	                    
	                    // if we are choosing a color from the 'last colors' palette, dont add to palette again
	                    // also, do nto add if color is already present
	                    var t = ns.event(event).target;
	                    if (ns.attribute(t, "class") != "lastColor" && !lastColors.contains(current)) {
	                        addLastColor(current);
	                    }
	                    reset();
	                    cb(current);
	                    return ns.kill(event);
	                }
	            };
	        }
	
	        // creates a color cube with the gieven top and left positions,
	        // border color (bColor) and background color (bkColor)
	        function mkCube(top, left, bColor, bkColor, height, width) {
	            height = (!height) ? 12 : height;
	            width = (!width) ? 12 : width;
	            var cube = ns.create("<div name='" + bkColor + "'></div>");
	            ns.styles(cube, 
	            {
	                "position" : "absolute",
	                "top" : top + "px",
	                "left" : left + "px",
	                "height" : height + "px",
	                "width" : width + "px",
	                "border" : "1px " + bColor + " solid",
	                "background-color" : bkColor,
	                "z-index" : "1"
	            });         
	            var fns = fn(cube);
	            ns.addHover(cube, fns.hover, fns.unhover);
	            ns.addClick(cube, fns.click);
	            
	            return cube;
	        }
	
	        // create the basic color swatch area
	        var basic = ns.create("<div></div>");
	        ns.styles(basic, 
	        {
	            "position" : "absolute",
	            "top" : "40px",
	            "left" : "0px",
	            "height" : "180px",
	            "width" : "40px",
	            "border-right" : "1px black solid",
	            "background-color" : "rgb(240, 240, 250)",
	            "z-index" : "1"
	        });
	        ns.append(panel, basic);
	
	        // create the last-sed swatch panel
	        var lastSwatches = ns.create("<div></div>");
	        ns.styles(lastSwatches, 
	        {
	            "position" : "absolute",
	            "top" : "219px",
	            "left" : "0px",
	            "height" : "40px",
	            "width" : "100%",
	            "border-top" : "1px black solid",
	            "background-color" : "rgb(180, 180, 180)",
	            "z-index" : "1"
	        });
	        ns.append(panel, lastSwatches);
	        
	        // create last color cubes
	        for (var i = 0; i < 12; i++) {
	            var left = 8 + (24 * i);
	            var cube = mkCube(10, left, "black", "#FFFFFF", 20, 20);
	            ns.attribute(cube, "class", "lastColor");
	            lastCubes.push(cube);
	            ns.append(lastSwatches, cube);
	        }
	                
	        // create basic color cubes
	        for (var i = 0; i < 12; i++) {
	            var top = 10 + (13 * i);
	            var cube = mkCube(top, 13, "black", basics[i]);
	            ns.append(basic, cube);
	        }
	        
	        var swatches = ns.create("<div></div>");
	        ns.styles(swatches, 
	        {
	            "position" : "absolute",
	            "top" : "50px",
	            "left" : "55px",
	            "height" : "160px",
	            "width" : "230px",
	            "z-index" : "1"
	        });
	        ns.append(panel, swatches);
	        
	        // starting red values for each cube        
	        var reds = ns.array(204, 102, 0, 255, 153, 51);
	        // create swatch cubes
	        for (var i = 0; i < 6; i++) {
	            var r = reds[i];
	            var top = 0 + ((i < 3) ? 0 : 78);// + (i * 90);
	            var left = 0 + ((i % 3) * 78);
	            var sBox = ns.create("<div></div>");
	            ns.styles(sBox, {
	                "position" : "absolute",
	                "top" : top + "px",
	                "left" : left + "px"
	            });
	            for (var j = 0; j < 6; j++) {
	                // greens ascend for 1st 3, then descend
	                var g = (i < 3) ? 255 - (j * 51) : 0 + (j * 51);
	                
	                for (var k = 0; k < 6; k++) {
	                    // blues switch between ascending/descending
	                    var b = (i == 1 || i == 4) ? 0 + (k * 51) : 255 - (k * 51);
	                    
	                    var top = 0 + (j * 13);
	                    var left = 0 + (k * 13);
	                    var color = "rgb(" + r + ", " + g + ", " + b + ")";
	                    var cube = mkCube(top, left, "black", color);
	                    ns.append(sBox, cube);
	                }
	            }
	            ns.append(swatches, sBox);
	        }
	    }
	    
		this.display = function(initializer) {
            if (!panel) {
                createPanel();
            }
            cb = h._getFn(initializer.callback);
            ns.append(document.body, panel); 
            
            // the following code is used to determine if the ColorPanel will be
            // rendered off screen, and moves it if so
            
            //TODO: there is a discrepancy in this val between browsers; figure out better solution
            var bDims = ns.dimensions(document.body);
            var scroll = ns.scroll();
            // 200 is a 'magic' number
            var winH = bDims.height - 200 + scroll.top;
            var winW = bDims.width - 0 + scroll.left;
            
            var pos = ns.position(initializer.target);
            var dims = ns.dimensions(initializer.target);
            var cDims = ns.dimensions(panel);
            
			var xOffset = ns.parseNumber(initializer.xOffset, 0);
            var yOffset = ns.parseNumber(initializer.yOffset, 0);
            var top = pos.top + yOffset;
            var left = pos.left + dims.width + xOffset; 
    
            if (top + cDims.height > winH) {
                // add 10 as an addtl. 'buffer'
                var diff = (top + cDims.height) - winH + 10;
                top -= diff;
            }
            if (left + cDims.width > winW) {
                left = left - cDims.width - dims.width;
            }
                    
            ns.position(panel, left, top);
			var z = ns.parseNumber(initializer.zIndex, 10000);
			ns.style(panel, "z-index", z);
		};
		
	    /**
	     * Displays the color panel.
	     */
	    this.show = function(item, top, left, callback) {
			ns.log("Deprecation warning: ColorPanel.show() is deprecated.");
            this.display({
				"target" : item,
				"callback" : callback
			});
	    };
	
	    /**
	     * Hides/removes the color panel.
	     */
	    this.hide = function() {
	        if (!panel) {
	            return;
	        }
	        ns.remove(panel);
	    };
	}
	ns.colorPanel = function(initializer) { return new _ColorPanel(initializer); };
})(jwyre);

/*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */

/*
 * Configurable variables. You may need to tweak these to be compatible with
 * the server-side, but the defaults work in most cases.
 */
var hexcase = 0;   /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad  = "";  /* base-64 pad character. "=" for strict RFC compliance   */

/*
 * These are the functions you'll usually want to call
 * They take string arguments and return either hex or base-64 encoded strings
 */
function hex_md5(s)    { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
function b64_md5(s)    { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
function hex_hmac_md5(k, d)
  { return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function b64_hmac_md5(k, d)
  { return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function any_hmac_md5(k, d, e)
  { return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }

/*
 * Perform a simple self-test to see if the VM is working
 */
function md5_vm_test()
{
  return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72";
}

/*
 * Calculate the MD5 of a raw string
 */
function rstr_md5(s)
{
  return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
}

/*
 * Calculate the HMAC-MD5, of a key and some data (raw strings)
 */
function rstr_hmac_md5(key, data)
{
  var bkey = rstr2binl(key);
  if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);

  var ipad = Array(16), opad = Array(16);
  for(var i = 0; i < 16; i++)
  {
    ipad[i] = bkey[i] ^ 0x36363636;
    opad[i] = bkey[i] ^ 0x5C5C5C5C;
  }

  var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
  return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
}

/*
 * Convert a raw string to a hex string
 */
function rstr2hex(input)
{
  try { hexcase } catch(e) { hexcase=0; }
  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  var output = "";
  var x;
  for(var i = 0; i < input.length; i++)
  {
    x = input.charCodeAt(i);
    output += hex_tab.charAt((x >>> 4) & 0x0F)
           +  hex_tab.charAt( x        & 0x0F);
  }
  return output;
}

/*
 * Convert a raw string to a base-64 string
 */
function rstr2b64(input)
{
  try { b64pad } catch(e) { b64pad=''; }
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var output = "";
  var len = input.length;
  for(var i = 0; i < len; i += 3)
  {
    var triplet = (input.charCodeAt(i) << 16)
                | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
                | (i + 2 < len ? input.charCodeAt(i+2)      : 0);
    for(var j = 0; j < 4; j++)
    {
      if(i * 8 + j * 6 > input.length * 8) output += b64pad;
      else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
    }
  }
  return output;
}

/*
 * Convert a raw string to an arbitrary string encoding
 */
function rstr2any(input, encoding)
{
  var divisor = encoding.length;
  var i, j, q, x, quotient;

  /* Convert to an array of 16-bit big-endian values, forming the dividend */
  var dividend = Array(Math.ceil(input.length / 2));
  for(i = 0; i < dividend.length; i++)
  {
    dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
  }

  /*
   * Repeatedly perform a long division. The binary array forms the dividend,
   * the length of the encoding is the divisor. Once computed, the quotient
   * forms the dividend for the next step. All remainders are stored for later
   * use.
   */
  var full_length = Math.ceil(input.length * 8 /
                                    (Math.log(encoding.length) / Math.log(2)));
  var remainders = Array(full_length);
  for(j = 0; j < full_length; j++)
  {
    quotient = Array();
    x = 0;
    for(i = 0; i < dividend.length; i++)
    {
      x = (x << 16) + dividend[i];
      q = Math.floor(x / divisor);
      x -= q * divisor;
      if(quotient.length > 0 || q > 0)
        quotient[quotient.length] = q;
    }
    remainders[j] = x;
    dividend = quotient;
  }

  /* Convert the remainders to the output string */
  var output = "";
  for(i = remainders.length - 1; i >= 0; i--)
    output += encoding.charAt(remainders[i]);

  return output;
}

/*
 * Encode a string as utf-8.
 * For efficiency, this assumes the input is valid utf-16.
 */
function str2rstr_utf8(input)
{
  var output = "";
  var i = -1;
  var x, y;

  while(++i < input.length)
  {
    /* Decode utf-16 surrogate pairs */
    x = input.charCodeAt(i);
    y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
    if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
    {
      x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
      i++;
    }

    /* Encode output as utf-8 */
    if(x <= 0x7F)
      output += String.fromCharCode(x);
    else if(x <= 0x7FF)
      output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
                                    0x80 | ( x         & 0x3F));
    else if(x <= 0xFFFF)
      output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
                                    0x80 | ((x >>> 6 ) & 0x3F),
                                    0x80 | ( x         & 0x3F));
    else if(x <= 0x1FFFFF)
      output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
                                    0x80 | ((x >>> 12) & 0x3F),
                                    0x80 | ((x >>> 6 ) & 0x3F),
                                    0x80 | ( x         & 0x3F));
  }
  return output;
}

/*
 * Encode a string as utf-16
 */
function str2rstr_utf16le(input)
{
  var output = "";
  for(var i = 0; i < input.length; i++)
    output += String.fromCharCode( input.charCodeAt(i)        & 0xFF,
                                  (input.charCodeAt(i) >>> 8) & 0xFF);
  return output;
}

function str2rstr_utf16be(input)
{
  var output = "";
  for(var i = 0; i < input.length; i++)
    output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
                                   input.charCodeAt(i)        & 0xFF);
  return output;
}

/*
 * Convert a raw string to an array of little-endian words
 * Characters >255 have their high-byte silently ignored.
 */
function rstr2binl(input)
{
  var output = Array(input.length >> 2);
  for(var i = 0; i < output.length; i++)
    output[i] = 0;
  for(var i = 0; i < input.length * 8; i += 8)
    output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
  return output;
}

/*
 * Convert an array of little-endian words to a string
 */
function binl2rstr(input)
{
  var output = "";
  for(var i = 0; i < input.length * 32; i += 8)
    output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
  return output;
}

/*
 * Calculate the MD5 of an array of little-endian words, and a bit length.
 */
function binl_md5(x, len)
{
  /* append padding */
  x[len >> 5] |= 0x80 << ((len) % 32);
  x[(((len + 64) >>> 9) << 4) + 14] = len;

  var a =  1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d =  271733878;

  for(var i = 0; i < x.length; i += 16)
  {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;

    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);

    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
  }
  return Array(a, b, c, d);
}

/*
 * These functions implement the four basic operations the algorithm uses.
 */
function md5_cmn(q, a, b, x, s, t)
{
  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */
function safe_add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left.
 */
function bit_rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}
/*
    http://www.JSON.org/json2.js
    2010-08-25

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html


    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.


    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the value

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.
*/

/*jslint evil: true, strict: false */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/


// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

if (!this.JSON) {
    this.JSON = {};
}

(function () {

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return isFinite(this.valueOf()) ?
                   this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z' : null;
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// Is the value an array?

            if (Object.prototype.toString.apply(value) === '[object Array]') {

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }


// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            text = String(text);
            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
}());
