
 //Loader.require("common");

/*
 function _parseInt(v) {
	var c = v.charAt(0);
	if (c>='0' && c<='9')
		return parseInt(v);
	return 0;
 }
 */

// If we're not the top window, attempt to inherit from the top window
if (typeof(DOM) == 'undefined') {

/*
if ((window != top) && (typeof(top.DOM) != 'undefined')) {
	DOM = top.DOM;
} else {
*/
	/**
	 * The DOM namespace provides some basic cross-browser DOM functionality
	 *
	 * Requires common.js be included first
	 *
	 * @namespace
	 */
	DOM = {

		/**
		 * Attempts to return a W3C compatible event object regardless of the browser
		 *
		 * Note: 'currentTarget' property isn't going to be set in non-W3C compliant browsers, try to use 'this' instead...
		 *		It isn't possible to determine the currentTarget reliably in IE
		 */
		getEvent : function(e) {
				if (!e)
					e = window.event /* || window.Event */;	// window.Event should only be for _very_ old Netscape and probably isn't what we want

				// Determine event target
				if (!e.target)
					e.target = e.srcElement ||			// IE has target object in srcElement property
							(e.type == 'mouseover'
								? e.toElement
								: e.fromElement) ||
							window;						// Events handled by window don't have a target in IE

				/* Defeat Safari bug (source www.quirksmode.org)
				 * If an event takes place on an element that contains text, this text node, and not the element, becomes the
				 * target of the event. Therefore we check if the target's nodeType is 3, a text node. If it is we move to its
				 * parent node, the HTML element.
				 */
				if (e.target.nodeType == 3)
					e.target = e.target.parentNode;

				// Ensure that pageX & pageY properties are always set to the correct position relative to the document
				// Source: www.quirksmode.org
				if (e.pageX == null) {
					if (e.clientX || e.clientY) {
						e.pageX = e.clientX + document.body.scrollLeft
									+ document.documentElement.scrollLeft;
						e.pageY = e.clientY + document.body.scrollTop
									+ document.documentElement.scrollTop;
					}		
				}

				// Try to translate the button property
			//	if (e.which && typeof(e.button) != 'undefined'))
			
				// Make sure the event object has a W3C-compliant stopPropagation method
				if (!e.stopPropagation)
					e.stopPropagation = function() { this.cancelBubble = true; };

				if (!e.preventDefault)
					e.preventDefault = function() { this.returnValue = false; };

				// Set relatedTarget property
				if (typeof(e.relatedTarget) == 'undefined')
					e.relatedTarget = (e.type == 'mouseover' ? e.fromElement : e.toElement);	// IE

				// Handle key modifiers
				// modifiers property should be Netscape 4 only
				if (e.modifiers) {
					e.altKey = (e.modifiers & 1) > 0;
					e.ctrlKey = (e.modifiers & 2) > 0;
					e.shiftKey = (e.modifiers & 4) > 0;
					e.metaKey = (e.modifiers & 8) > 0;

					e.keyCode = e.which;
			
					var ch = String.fromCharCode(e.keyCode);
					if ((ch >= 'a') && (ch <= 'z')) {
						e.keyCode -= 'a'.charCodeAt(0) - 'A'.charCodeAt(0);
					}
				}
			
			// TODO: Standard way of determining which button is clicked??
			// Use button property?  or which property?  or what?
			// Perhaps add some non-standard properties like: leftClick, rightClick, middleClick?

				return e;
			}, // DOM.getEvent(e)

		addEventListener : (function() {
				if (document.addEventListener) {
					return function(obj, eventName, listenerFunc) {
							// Assume w3c standard compatibility
							return obj.addEventListener(eventName, listenerFunc, false);
						} // addEventListener(obj, eventName, listenerFunc) - w3c version
				} else if (document.attachEvent) {
					// List of objects with attached event handlers
					var attachedObjects = null;

					// Function to cleanup event attachments when the page is unloaded
					function detachAll() {
						if (attachedObjects) {
							for (var i = 0; i < attachedObjects.length; ++i) {
								var obj = attachedObjects[i];
								try {
									var evtList = obj._DOM_Event_Listeners;
									if (evtList) {
										for (var eventName in evtList) {
											if (evtList.hasOwnProperty(eventName)) {
												var list = evtList[eventName];

												for (var f=0; f<list.length; ++f) {
													// Detatch bound listener and remove reference
													// This should allow the JS garbage collector to clean up the rest
													obj.detachEvent(eventName, list[f][1]);
													list[f][1] = null;
												} // for
											}
										} // for

										obj._DOM_Event_Listeners = null;
									}
								} catch(ex) {
									top.debug("Unable to free event listener at index: "+ i);
								}
							} // for

							attachedObjects = [ ];
						}
					} // detatchAll

					// The function the user calls to attach the event handlers
					return function(obj, eventName, listenerFunc) {
							// Assume IE compatibility
							// IE doesn't send 'this' properly into event handlers, so we need to re-bind the function
							// Because of this, and how the remove works, we are required to keep a cache containing
							// the original function and the newly bound one so we can clean up later

							// Create the re-bound callback
							var bindFunc = function(e) {
												e = DOM.getEvent(e);
												return listenerFunc.call(obj, e);
											};

							// Attach the event handler
							if (!obj.attachEvent('on'+eventName, bindFunc))
								return false;

							if (!obj._DOM_Event_Listeners) {
								obj._DOM_Event_Listeners = { };

								if (attachedObjects == null) {
									// Bind an 'unload' handler to clean up the cache (only do this for the first event we bind)
									window.attachEvent('onunload', detachAll);
									attachedObjects = [ ];
								}

								attachedObjects[attachedObjects.length] = obj;
							}

							// Add the entry for this event to the object's cache
							if (!obj._DOM_Event_Listeners.hasOwnProperty(eventName))
								obj._DOM_Event_Listeners[eventName] = [ ];
							var list = obj._DOM_Event_Listeners[eventName];
							list[list.length] = [ listenerFunc, bindFunc ];

							return true;
						} // addEventListener(obj, eventName, listenerFunc) - IE version
				} else {
					return function(obj, eventName, listenerFunc) {
							throw "Attaching events is not supported by your browser";
						} // addEventListener(obj, eventName, listenerFunc) - unsupported browser
				}
			})(), // DOM.addEventListener(obj, eventName, listenerFunc)


		removeEventListener : (function() {
				if (document.removeEventListener) {
					// Assume w3c standard compatibility
					return function(obj, eventName, listenerFunc) {
							return obj.removeEventListener(eventName, listenerFunc, false);
						} // removeEventListener(obj, eventName, listenerFunc) - w3c version
				} else if (document.detachEvent) {
					// Assume IE compatibility
					return function(obj, eventName, listenerFunc) {
							if (obj._DOM_Event_Listeners && obj._DOM_Event_Listeners.hasOwnProperty(eventName)) {
								// Search for a matching entry
								var evtList = obj._DOM_Event_Listeners[eventName];
								for (var i = 0; i < evtList.length; ++i) {
									if (evtList[i][0] === listenerFunc) {
										listenerFunc = evtList[i][1];	// Use the re-bound listener func
										evtList.splice(i, 1);	// Remove the entry if found
										break;
									}
								} // for
							}

							return obj.detachEvent('on'+ eventName, listenerFunc);
						} // removeEventListener(obj, eventName, listenerFunc) - IE version
				} else {
					return function(obj, eventName, listenerFunc) {
							throw "Attaching events is not supported by your browser";
						} // removeEventListener(obj, eventName, listenerFunc) - unsupported browser
				}
			})(), // DOM.removeEventListener(obj, eventName, listenerFunc)


		getComputedStyle : function(obj, property) {
			var s;
			if (obj.currentStyle) {
				// For IE
				s = obj.currentStyle;
			} else if (document.defaultView && document.defaultView.getComputedStyle) {
				// For Mozilla and other standards-compliant browsers (Defined in DOM level 2 Style Specification)
				s = document.defaultView.getComputedStyle(obj, null);
			}
		
			// If a single property was requested, return that
			if (property && s)
				return s[property];
		
			// If no property was requested, return everything
			return s;
		}, // DOM.getComputedStyle(obj [, property ])


		getBoolAttribute : function(obj, attr, default_val) {
			var v = obj.getAttribute(attr);
			if (v == null)
				return (default_val ? true : false);
			return ((parseInt(v) > 0) || (v == 'true') || (v == 'yes') || (v === true));
		}, // DOM.getBoolAttribute(obj, attr, default)


		getScrollPosition : function(doc) {
			if (!doc)
				doc = document;
			else if (doc.nodeType != 9 /* DOCUMENT_NODE */)
				doc = doc.ownerDocument;

			var win = DOM.getWindow(doc);
			if (win.pageXOffset!=null) {
				// All except Explorer
				return [ win.pageXOffset, win.pageYOffset ];
			} else if (doc.documentElement && doc.documentElement.scrollLeft!=null) {
				// Explorer 6 strict (w/DOCTYPE)
				return [ doc.documentElement.scrollLeft, doc.documentElement.scrollTop ];
			} else if (doc.body && doc.body.scrollLeft!=null) {
				// All other explorers
				return [ doc.body.scrollLeft, doc.body.scrollTop ];
			}
			throw "DOM.getScrollPosition(): Unsupported browser";
		}, // DOM.getScrollPosition(doc)


		getScrollTop : function(doc) {
			if (!doc)
				doc = document;
			else if (doc.nodeType != 9 /* DOCUMENT_NODE */)
				doc = doc.ownerDocument;

			var win = DOM.getWindow(doc);
			if (win.pageYOffset!=null) {
				// All except Explorer
				return win.pageYOffset;
			} else if (doc.documentElement && doc.documentElement.scrollTop!=null) {
				// Explorer 6 strict (w/DOCTYPE)
				return doc.documentElement.scrollTop;
			} else if (doc.body && doc.body.scrollTop!=null) {
				// All other explorers
				return doc.body.scrollTop;
			}
			throw "DOM.getScrollTop(): Unsupported browser";
		}, // DOM.getScrollTop(doc)
		
		
		getScrollLeft : function(doc) {
			if (!doc)
				doc = document;
			else if (doc.nodeType != 9 /* DOCUMENT_NODE */)
				doc = doc.ownerDocument;
		
			var win = DOM.getWindow(doc);
			if (win.pageXOffset!=null) {
				// All except Explorer
				return win.pageXOffset;
			} else if (doc.documentElement && doc.documentElement.scrollLeft!=null) {
				// Explorer 6 strict (w/DOCTYPE)
				return doc.documentElement.scrollLeft;
			} else if (doc.body && doc.body.scrollLeft!=null) {
				// All other explorers
				return doc.body.scrollLeft;
			}
			throw "DOM.getScrollLeft(): Unsupported browser";
		}, // DOM.getScrollLeft(doc)


		setRelativePosition : function(obj, pos) {
			obj.style.position = 'relative';
		
			if (pos[0] != null) {
				if (common.is_numeric(pos[0]))
					pos[0] += 'px';
		
				obj.style.left = pos[0];
			}
		
			if (pos[1] != null) {
				if (common.is_numeric(pos[1]))
					pos[1] += 'px';
		
				obj.style.top = pos[1];
			}
		}, // DOM.setRelativePosition(obj, pos)
		
		
		setPagePosition : function(obj, pos) {

// TODO: This will probably not work as expected in IE...
//		IE actually seems to follow the standard better than Firefox??  Absolutely positioned elements are positioned relative to
//		the parent element's top/left.  In Firefox, they appear to be relative to the parent document...

			obj.style.position = 'absolute';
		
			if (pos[0] != null) {
				if (common.is_numeric(pos[0]))
					pos[0] += 'px';
		
				obj.style.left = pos[0];
			}
		
			if (pos[1] != null) {
				if (common.is_numeric(pos[1]))
					pos[1] += 'px';
		
				obj.style.top = pos[1];
			}
		}, // DOM.setPagePosition(obj, pos)


		/**
		 * Returns the position of an element within the client area of the specified window.
		 *
		 * The code for this is based on the Position.page function from the prototype library
		 *
		 * @param	Element	forElement	The element to retrieve the position for
		 * @param	Window	relativeTo	The window or element to determine the position relative to. 
		 *								If null, defaults to the window containing the forElement.
		 *
		 * @return	Array	Array containing the calculated position of the element within the window
		 *					(always in pixels without the 'px' suffix).
		 */
		getClientPosition : function(forElement, relativeTo) {
			var rv = [ 0, 0 ];

			var element = forElement;
			var win = DOM.getWindow(element.parentNode);
			if (!relativeTo)
				relativeTo = win;

			do {
				if (element === relativeTo) {
					relativeTo = win;	// So we skip test below
					break;
				}

				rv[0] += element.offsetLeft || 0;
				rv[1] += element.offsetTop  || 0;

				// Safari fix
				if (element.offsetParent == element.ownerDocument.body)
					if (element.style.position == 'absolute') break;

				element = element.offsetParent ||
							// For Firefox, <FRAME> doesn't seem to have an offsetParent...
							(element.nodeName == 'FRAME'
								? win.frameElement
								: null
							);
			} while (element) // while

			if (relativeTo !== win) {
				if ( win === top ) {
					// Calculate global position of relativeTo object and adjust return value
					var t = DOM.getClientPosition(
										(relativeTo.frameElement
											? relativeTo.frameElement
											: relativeTo),
										top
									);
					rv[0] = rv[0] - t[0];
					rv[1] = rv[0] - t[1];
				} else if (win.frameElement && win.frameElement.tagName) {
					var t = DOM.getClientPosition(win.frameElement, relativeTo);
					rv[0] += t[0];
					rv[1] += t[1];
				}
			}

			return rv;
		}, // DOM.getClientPosition(forElement [, relativeTo ])


		/**
		 * Returns the position of an element within the page/document that it is contained within (ie. it's ownerDocument)
		 *
		 * @param	Element	forElement	The element to retrieve the position for
		 *
		 * @return	Array	Array containing the calculated position of the element within the page (always in pixels without the 'px' suffix)
		 */
		getPagePosition : function(forElement) {
			var rv = DOM.getClientPosition(forElement);

			// Adjust for document scroll position
			rv[0] += DOM.getScrollLeft(forElement);
			rv[1] += DOM.getScrollTop(forElement);

			return rv;
		}, // DOM.getPagePosition		


		/**
		 * Translates coordinates from one document coordinate system to another
		 *
		 * Notes:
		 *   fromRef and toRef must both be contained within the same top window
		 *
		 * @param	Array	pos		Array containing the position to translate (in page units (ie. pageX, pageY))
		 * @param	Node		fromRef	Document node or subnode of the document the given position is from
		 * @param	Node		toRef	Document node or subnode of the document to translate to
		 *
		 * @return	Array	Array containing the translated coordinates.
		 */
		translatePosition : function(pos, fromRef, toRef) {
			if (fromRef.nodeType != 9 /* DOCUMENT_NODE */) {
		// TODO: should probably adjust for element position in the document here (assume the coordinates are relative to the given node)
				fromRef = fromRef.ownerDocument;
			}
		
			if (toRef.nodeType != 9 /* DOCUMENT_NODE */) {
		// TODO: should probably adjust for element position in the document here (assume the coordinates should be relative to the given node)
				toRef = toRef.ownerDocument;
			}

			// Convert from local (fromRef) to TOP
			//////////////////////////////////////////

			// Adjust for scrolling
			var win	= DOM.getWindow(fromRef);

			if (win != top) {
				var p = DOM.getClientPosition(win.frameElement);
				pos[0] += p[0];
				pos[1] += p[1];

				while (win != top) {
					pos[0] -= DOM.getScrollLeft(win.document);
					pos[1] -= DOM.getScrollTop(win.document);

					win = win.parent;
				}
			}

			// Convert from TOP to local (toRef)
			//////////////////////////////////////////
		
			// Adjust for scrolling
			var win	= DOM.getWindow(toRef);
			if (win != top) {
				var p = DOM.getClientPosition(win.frameElement);
				pos[0] -= p[0];
				pos[1] -= p[1];

				while (win != top) {
					pos[0] += DOM.getScrollLeft(win.document);
					pos[1] += DOM.getScrollTop(win.document);
			
					win = win.parent;
				} // while
			}

			// Return the recomputed position
			return pos;
		}, // DOM.translatePosition(pos, fromRef, toRef)
		
		
		/**
		 * Sets the given node's z-index to be larger than all of it's siblings
		 *
		 * @param	Node		node		The Node to bring to the front
		 *
		 * @return	int		The new z-index of the node
		 */
		bringToFront : function(node) {
			var zIndex = parseInt(node.style.zIndex);
			if (!zIndex)
				zIndex = 1;
		
			var sib = node.parentNode.firstChild;
			while (sib) {
				if (sib.style && (sib != node)) {
					var z = parseInt(sib.style.zIndex);
					if (!z) z = 1;
					if (z > zIndex)
						zIndex = z;
					else if (z == zIndex)
						++zIndex;
				}
				sib = sib.nextSibling;
			}

			node.style.zIndex = zIndex;
			return zIndex;
		}, // DOM.bringToFront(node)
		
		
		/**
		 * Retrieves the window for the document containing the given node
		 *
		 * @param	Node		node		The node we want to retrieve the window for
		 *
		 * @return	Window	The window that was retrieved
		 */
		getWindow : function(node) {
			if (!node)
				node = document;
			else if (node.nodeType != 9 /* DOCUMENT_NODE */)
				node = node.ownerDocument;
		
		//defaultView	-- The window object associated with a document object in the w3C standards
		//parentWindow -- The window object associated with a document object according to Microsoft
		//contentWindow -- The window object containing the content for an IFRAME
		
			return (node.defaultView		// W3C compliant browser
					|| node.parentWindow	// IE
					|| node.contentWindow	// Iframe?
					);
		}, // DOM.getWindow(node)
		
		
		/**
		 * Returns the height of the client window in pixels
		 *
		 * @param	Node		doc		The document node (or other Node whose document should be used) to retrieve the height of
		 *							If not given, the document for the current context will be used.
		 *
		 * @return	int	The height of the document in pixels
		 */
		getClientHeight : function(doc) {
			return DOM.getClientSize(doc)[1];
		}, // DOM.getClientHeight()


		/**
		 * Returns the width of the client window in pixels
		 *
		 * @param	Node		doc		The document node (or other Node whose document should be used) to retrieve the width of
		 *							If not given, the document for the current context will be used.
		 *
		 * @return	int	The visible client width of the document window in pixels (this is _not_ the same as the document size)
		 */
		getClientWidth : function(doc) {
			return DOM.getClientSize(doc)[0];
		}, // DOM.getClientWidth()

		/**
		 * Returns the size of the document in pixels
		 *
		 * @param	Node		doc		The document node (or other Node whose document should be used) to retrieve the size of
		 *							If not given, the document for the current context will be used.
		 *
		 * @return	Array	The size of the document in pixels
		 */
		getClientSize : function(doc) {
			if (!doc)
				doc = document;
			else if (doc.nodeType != 9 /* DOCUMENT_NODE */)
				doc = doc.ownerDocument;

			var win = DOM.getWindow(doc);

/*
alert("winsize:\n"+
	"  winEl="+ win +"\n"+
	"    Wid="+ win.innerWidth +"\n"+
	"    Hgt="+ win.innerHeight +"\n"+
	"  docEl="+ doc.documentElement +"\n"+
	"    Wid="+ (doc.documentElement ? doc.documentElement.clientWidth : null) +"\n"+
	"    Hgt="+ (doc.documentElement ? doc.documentElement.clientHeight : null) +"\n"+
	"  docBod="+ doc.body +"\n"+
	"    Wid="+ (doc.body ? doc.body.clientWidth : null) +"\n"+
	"    Hgt="+ (doc.body ? doc.body.clientHeight : null) +"\n"+
	""
	);
*/

			var x, y;
			if (win.innerWidth) {
				x = win.innerWidth;
				y = win.innerHeight;
			} else if (doc.documentElement && doc.documentElement.clientWidth) {
				x = doc.documentElement.clientWidth;
				y = doc.documentElement.clientHeight;
			} else if (doc.body) {
				x = doc.body.clientWidth;
				y = doc.body.clientHeight;
			} else
				throw "DOM.getClientSize(): Unsupported browser";

			return [ x, y ];
		}, // DOM.getClientSize()


        /**
         * Returns the height of the document.
		* Source: Based on code from the Yahoo Web Toolkit
		*
		* @param	{DOMDocument}	doc				The document to get the height of (if null, uses the
		*										current document)
		* @param	{boolean}	restrictToWindow	Whether the returned size should be limited to the size
		*										of the containing window (true), or if the entire size
		*										of the document should be returned (false).  The
		*										default is false.
		*
         * @return	int	The height of the actual document (which includes the body and its margin).
         */
        getDocumentHeight : function(doc, restrictToWindow) {
			return DOM.getDocumentSize(doc, restrictToWindow)[1];
        }, // DOM.getDocumentHeight([ doc [ , restrictToWindow = false ]])


        /**
         * Returns the width of the document.
		* Source: Based on code from the Yahoo Web Toolkit
		*
		* @param	{DOMDocument}	doc				The document to get the width of (if null, uses the
		*										current document)
		* @param	{boolean}	restrictToWindow	Whether the returned size should be limited to the size
		*										of the containing window (true), or if the entire size
		*										of the document should be returned (false).  The
		*										default is false.
		*
         * @return	int			The width of the actual document (which includes the body and its margin).
         */
        getDocumentWidth : function(doc, restrictToWindow) {
			return DOM.getDocumentSize(doc, restrictToWindow)[0];
        }, // DOM.getDocumentWidth([ doc [, restrictToWindow = false ]])


        /**
         * Returns the size of the document.
		*
		* @param	{DOMDocument}	doc				The document to get the size of (if null, uses the
		*										current document)
		* @param	{boolean}	restrictToWindow	Whether the returned size should be limited to the size
		*										of the containing window (true), or if the entire size
		*										of the document should be returned (false).  The
		*										default is false.
		*
         * @return	Array	The size of the actual document (which includes the body and its margin).
         */
        getDocumentSize : function(doc, restrictToWindow) {
			if (!doc)
				doc = document;
			else if (doc.nodeType != 9 /* DOCUMENT_NODE */)
				doc = doc.ownerDocument;

/*
alert("getDocSize:\n"+
	"	 compat="+ doc.compatMode +"\n"+
	"\n"+
	"    bod.sw="+ doc.body.scrollWidth +"\n"+
	"    bod.sh="+ doc.body.scrollHeight +"\n"+
	"    bod.mg="+ DOM.getComputedStyle(doc.body).marginTop+"\n"+
	"\n"+
	"    del.sw="+ doc.documentElement.scrollWidth +"\n"+
	"    del.sh="+ doc.documentElement.scrollHeight +"\n"+
	"    del.mg="+ DOM.getComputedStyle(doc.documentElement).marginTop+"\n"+
	"");
*/

			var compat = (doc.compatMode != 'CSS1Compat');
            var h = compat ? doc.body.scrollHeight : doc.documentElement.scrollHeight;
            var w = compat ? doc.body.scrollWidth : doc.documentElement.scrollWidth;
			if (restrictToWindow) {
				var cs = DOM.getClientSize(doc);
	            w = Math.max(w, cs[0]);
	            h = Math.max(h, cs[1]);
			}

            return [ w, h ];
        }, // DOM.getDocumentSize([ doc [ , restrictToWindow = false ]])


		getDefaultScrollbarSize : function() {
			if (!arguments.callee._sbSize) {
				var doc = document;

				// Scrolling div
				var scr = doc.createElement('div');
				scr.style.position = 'absolute';
				scr.style.top = '-1000px';
				scr.style.left = '-1000px';
				scr.style.width = '100px';
				scr.style.height = '100px';
				scr.style.margin = '0px 0px 0px 0px';
				scr.style.padding = '0px 0px 0px 0px';
				scr.style.border = '0px none';
				scr.style.overflow = 'scroll';

				// Append the scrolling div to the doc
				doc.body.appendChild(scr);

				// Cache results for later...
				arguments.callee._sbSize = [
						scr.offsetWidth - scr.clientWidth,
						scr.offsetHeight - scr.clientHeight
						];

				// Remove the scrolling div from the doc
				doc.body.removeChild(scr);
			}

			// Return cached result
			return arguments.callee._sbSize;
		}, // getDefaultScrollbarSize()
		
		/**
		 * Returns the visible sizes of the scrollbars in pixels
		 *
		 * @param	{DOMNode}	node		The node to determine the scrollbar sizes for
		 *
		 * @return	Array		[ verticalWidth, horizontalHeight ]  The calculated sizes of the visible
		 *						scrollbars (0 if the scrollbar is not visible).
		 */
		getScrollBarSize : function(node) {
			if (node.tagName == 'IFRAME') {
				var doc = DOM.getIframeContentDocument(node);
				var body = doc.body;
				var html = doc.documentElement;

				// Retain the original border & margin, then set them to 0px so the offsetWidth/Height works for us...
				var s = DOM.getComputedStyle(body);

				// For IE
				var borderStyle = s.borderStyle;
				body.style.borderStyle = "none";
/*
alert(
		"iframe:\n"+
		"  ofsWid="+ node.offsetWidth +"\n"+
		"  ofsHgt="+ node.offsetHeight +"\n"+
		"  cliWid="+ node.clientWidth +"\n"+
		"  cliHgt="+ node.clientHeight +"\n"+
		"body:\n"+
		"  ofsWid="+ body.offsetWidth +"\n"+
		"  ofsHgt="+ body.offsetHeight +"\n"+
		"  cliWid="+ body.clientWidth +"\n"+
		"  cliHgt="+ body.clientHeight +"\n"+
		"html:\n"+
		"  ofsWid="+ html.offsetWidth +"\n"+
		"  ofsHgt="+ html.offsetHeight +"\n"+
		"  cliWid="+ html.clientWidth +"\n"+
		"  cliHgt="+ html.clientHeight +"\n"+
		""
	);
*/
				w = node.clientWidth - body.clientWidth;
				h = node.clientHeight - body.clientHeight;

				// Reset the margin and border to the original values
				body.style.borderStyle = borderStyle;
			} else {
				// Retain the original border & margin, then set them to 0px so the offsetWidth/Height works for us...
				var s = node.style;
				var border = s.border;
				var margin = s.margin;
				s.margin = "0px 0px 0px 0px";
				s.border = "0px none";

				// Calculate the scrollbar sizes
				w = node.offsetWidth - node.clientWidth;
				h = node.offsetHeight - node.clientHeight;

				// Reset the margin and border to the original values
				s.border = border;
				s.margin = margin;
			}

			return [ w, h ];
		}, // DOM.getScrollBarSize(node)


		/**
		 * Returns the visible height of the horizontal scrollbar in pixels
		 *
		 * @param	DOMNode	node		The node to determine the scrollbar height for
		 *
		 * @return	int		The calculated height of the scrollbar.  If no vertical scrollbar is visible, will return 0.
		 */
		getScrollBarHeight : function(node) {
			var s = DOM.getScrollBarSize(node);
			return s[1];
		}, // DOM.getScrollBarHeight(node)


		/**
		 * Returns the visible width of a vertical scrollbar in pixels
		 *
		 * @param	DOMNode	node		The node to determine the scrollbar width for
		 *
		 * @return	int		The calculated width of the scrollbar.  If no horizontal scrollbar is visible, will return 0.
		 */
		getScrollBarWidth : function(node) {
			var s = DOM.getScrollBarSize(node);
			return s[0];
		}, // DOM.getScrollBarWidth(node)


		/**
		 * Retrieves the textContent of this node and its descendants
		 *
		 * @param	Element	obj		The DOM Element to retrieve the textContent for
		 *
		 * @return	String	The textContent of the given Element
		 */
		textContent : function(obj) {
			if (typeof(obj.textContent) != 'undefined') {
				// W3C DOM level 3 attribute
				return obj.textContent;
			} else {
				// Doesn't support textContent, recursively descend the objects children and build it manually
				if (obj.nodeType == 3) {
					return obj.nodeValue;
				} else if (obj.nodeType == 9 /* DOCUMENT_NODE */ || obj.nodeType == 10 /* DOCUMENT_TYPE_NODE */ || obj.nodeType == 12 /* NOTATION_NODE */) {
					return null;
				} else if (obj.nodeType != 8 /* COMMENT_NODE */ && obj.nodeType != 7 /* PROCESSING_INSTRUCTION_NODE */) {
					var rv = '';
					var child = obj.firstChild;
					while (child) {
						rv += DOM.textContent(child);
						child = child.nextSibling;
					}
					return rv;
				}
			}
		}, // DOM.textContent(obj)
		
		
		/**
		 * Retrieves the current opacity of a DOM Element
		 *
		 * @param	Element	obj		The DOM Element to set the opacity for
		 *
		 * @return	float	The opacity of the element ( 0 = transparent ... 1 = solid )
		 */
		getOpacity : function(obj) {
			var opacity = 1;
		
			if (typeof(obj.filters) != 'undefined') {
				opacity = (obj.filters && obj.filters.alpha &&
						(typeof(obj.filters.alpha.opacity) == "number")
					? obj.filters.alpha.opacity : 100) / 100;
			} else if ((obj.style.opacity != undefined) && (obj.style.opacity.length > 0)) {
				opacity = obj.style.opacity;
			} else if ((obj.style.MozOpacity != undefined) && (obj.style.MozOpacity.length > 0)) {
				opacity = obj.style.MozOpacity;
			} else if ((obj.style.KhtmlOpacity != undefined) && (obj.style.KhtmlOpacity.length > 0)) {
				opacity = obj.style.KhtmlOpacity;
			}
		
			if (opacity > 1) {
				opacity = 1;
			}
		
			return new Number(opacity);
		}, // DOM.getOpacity(obj)
		
		
		/**
		 * Sets the opacity of a DOM Element
		 *
		 * @param	DOMElement	obj		The DOM Element to set the opacity for
		 * @param	float		opacity	The opacity to set for the element (0 = transparent ... 1 = solid)
		 */
		setOpacity : function(obj, opacity) {
			if (opacity > 1)
				opacity = 1;
			else if (opacity < 0)
				opacity = 0;
		
			obj.style.opacity = opacity;							// W3C
			obj.style.MozOpacity = opacity;							// Mozilla
			obj.style.KhtmlOpacity = opacity;						// Konquerer/Safari
			obj.style.filter = "Alpha(Opacity="+opacity*100+")";	// IE
		}, // DOM.setOpacity(obj)
		
		
		/**
		 * Sets an element's position to be "fixed" (ie. it doesn't scroll when the page scrolls)
		 *
		 * @param	Node		obj		The object to set a fixed position for
		 * @param	Array	pos		The new position for the element (Array of [ x, y ])
		 *
		 * @return	??
		 */
		setFixedPosition : function(obj, pos) {	
			if (window.execScript) { // IE only
				// IE before version 7 doesn't support 'fixed', so implement it via the non-standard 'expression'
				// TODO: This shouldn't be neccessary in IE 7, so we may be able to use the correct method in that browser
				obj.style.position = 'absolute';
				obj.style.setExpression('left', '(x='+ pos[0] +'+document.documentElement.scrollLeft) + "px"');
				obj.style.setExpression('top', '(x='+ pos[1] +'+document.documentElement.scrollTop) + "px"');
			} else { // All other (hopefully compliant) browsers
				obj.style.position = 'fixed';
				obj.style.left = pos[0];
				obj.style.top = pos[1];
			}
		}, // DOM.setFixedPosition(obj, pos)
		
		/**
		 * Retrieves the content document element for the given IFRAME node
		 *
		 * @param	HTMLIFrameElement	iframe	The IFrame element to retrieve the contentDocument for
		 *
		 * @return	Document		The contentDocument associated with the given IFrame
		 */
		getIframeContentDocument : function(iframe) {
			if (iframe.contentDocument) {		// This is the W3C standard property (defined in DOM level 2 HTML - http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-50708718)
				return iframe.contentDocument;
			} else if (iframe.contentWindow) {	// The window object the contentDocument is in
				return iframe.contentWindow.document;
			}

			// This is IE-specific??
			return iframe.Document;
		}, // DOM.getIframeContentDocument(iframe)

		
		/**
		 * Creates a new element ala document.createElement, but sets the given attributes during the create
		 *
		 * This is particulary useful for IE compatibility when creating form elements, since is allows you to set the
		 * type and name attributes appropriately.  In IE, setting the type of an input tag is only possible if done immediately
		 * after creating the element, and setting the name is only fully supported if created via the method used below.
		 * Failure to set the name attribute as below will result in the form element not being submitted with the form.
		 *
		 * This may work differently in IE 7 - it has not yet been tested.
		 *
		 * @param	{String}	type		The tag name / type of the node to create (ie. 'input', 'textarea', etc)
		 * @param	{String}	attrs	Object/assoc. array containing the attributes to assign to the new node
		 *
		 * @return	{DOMElement}	The new node
		 */
		createElement : (
				(window.execScript)	// Property that should only be in IE (document.all doesn't really work here)
				?	// IE version, need to create element
					function(type, attrs) {
						if ((attrs instanceof Object) && !(attrs instanceof String)) {
							var a = '';
							for (var i in attrs) {
								a += ' ' + common.htmlentities(i) +'="'+ common.htmlentities((attrs[i] == null ? '' : attrs[i])) +'"';
							}
							attrs = a;
						} else
							attrs = ' '+ attrs;
						return document.createElement('<'+ type + attrs +'>');
					}
				:	// Version for firefox and all other browsers
					function (type, attrs) {
						var o = document.createElement(type);
						if ((attrs instanceof String) || !(attrs instanceof Object)) {
							var a = { };
							// Need to parse the given attributes if they passed in a string
							// Supports the following three forms:
							//	name="some value"
							//	name='some value'
							//	name=someValue
							var re = /\s*([^\s]+)\s*=\s*((?:"[^"]*?")|(?:'[^']*?')|(?:[^\s]*))\s*/g;

							var m = re.exec(attrs);
							while (m != null) {
								var v = m[2].substr(0, 1);
								if ((v == "'") || (v == '"'))
									v = m[2].substr(1, m[2].length - 2);
								else
									v = m[2];
								a[m[1]] = common.html_entity_decode(v);

								m = re.exec(attrs);
							}

							attrs = a;
						}
						if (attrs instanceof Object) {
							for (var i in attrs) {
								o.setAttribute(i, attrs[i]);
							}
						}
						return o;
					}
			), // DOM.createElement(type, attrs)


		/**
		 * Finds the next sibling to the given node with the specified tagName and returns it
		 *
		 * @param	{DOMElement}		node		The reference node
		 * @param	{String|Array}	tagName	The tag name(s) to match (ie. "TABLE", "DIV", etc.)
		 *
		 * @return	{DOMElement|null}		The matching element or null if not found
		 */
		nextSiblingByTagName : function(node, tagName) {
				if (!(tagName instanceof Array))
					tagName = [ tagName ];
				node = node.nextSibling;
				while (node && (tagName.indexOf(node.tagName) == -1))
					node = node.nextSibling;
				return node;
			}, // DOM.nextSiblingByTagName(node, tagName)


		/**
		 * Finds the previous sibling to the given node with the specified tagName and returns it
		 *
		 * @param	{DOMElement}		node		The reference node
		 * @param	{String|Array}	tagName	The tag name(s) to match (ie. "TABLE", "DIV", etc.)
		 *
		 * @return	{DOMElement|null}		The matching element or null if not found
		 */
		previousSiblingByTagName : function(node, tagName) {
				if (!(tagName instanceof Array))
					tagName = [ tagName ];
				node = node.previousSibling;
				while (node && (tagName.indexOf(node.tagName) == -1))
					node = node.previousSibling;
				return node;
			}, // DOM.previousSiblingByTagName(node, tagName)


		/**
		 * Finds the first child of the given node with the specified tagName and returns it
		 *
		 * @param	{DOMElement}		node		The reference node
		 * @param	{String|Array}	tagName	The tag name(s) to match (ie. "TABLE", "DIV", etc.)
		 *
		 * @return	{DOMElement|null}		The matching element or null if not found
		 */
		firstChildByTagName : function(node, tagName) {
				if (!(tagName instanceof Array))
					tagName = [ tagName ];
				node = node.firstChild;
				while (node && (tagName.indexOf(node.tagName) == -1))
					node = node.nextSibling;
				return node;
			}, // DOM.firstChildByTagName(node, tagName)


		/**
		 * Finds the last child of the given node with the specified tagName and returns it
		 *
		 * @param	{DOMElement}		node		The reference node
		 * @param	{String|Array}	tagName	The tag name(s) to match (ie. "TABLE", "DIV", etc.)
		 *
		 * @return	{DOMElement|null}		The matching element or null if not found
		 */
		lastChildByTagName : function(node, tagName) {
				if (!(tagName instanceof Array))
					tagName = [ tagName ];
				node = node.lastChild;
				while (node && (tagName.indexOf(node.tagName) == -1))
					node = node.previousSibling;
				return node;
			}, // DOM.lastChildByTagName(node, tagName)


		/**
		 * Finds the first ancestor of the given node with the specified tagName and returns it
		 *
		 * @param	{DOMElement}		node		The reference node
		 * @param	{String|Array}	tagName	The tag name(s) to match (ie. "TABLE", "DIV", etc.)
		 *
		 * @return	{DOMElement|null}		The matching element or null if not found
		 */
		parentNodeByTagName : function(node, tagName) {
				if (!(tagName instanceof Array))
					tagName = [ tagName ];
				node = node.parentNode;
				while (node && (tagName.indexOf(node.tagName) == -1)) {
					if (node.nodeType == 9)	// Document
						node = null;
					else
						node = node.parentNode;
				}
				return node;
			}, // parentNodeByTagName(node, tagName)
	
		/**
		 * Removes all the children of given node
		 *
		 * @param	DomNode	node	The node to remove all children of
		 *
		 * @return	???
		 */
		removeAllChildren : function(node) {
				while (node.hasChildNodes())
					node.removeChild(node.firstChild);       
			}, //removeAllChildren(node)
		
		/**
		 * Appends text to given node (can remove all children if specified)
		 *
		 * @param	DomNode		node		The node to append text to
		 * @param	string		text		Text to append to node
		 * @param	boolean		remChildren	Wheather or not to remove all the node's children before appending text
		 *
		 * @return	???
		 */
		appendText : function(node,text,remChildren){
				if (remChildren) DOM.removeAllChildren(node);
				node.appendChild(document.createTextNode(text));
			}, //appendText(node,text,remChildren)
	
		/**
		 * Works the way up the nodelist to get the offset position of a node
		 *
		 * @param	DomNode		node		The node to get the position of
		 * @param	DomNode		stopNode	The node which to stop at. (Optional)
		 *
		 * @return	object of x/y position
		 */
		getPosition : function(node,stopNode){
				var pos = {};
				pos.x = node.offsetLeft;
				pos.y = node.offsetTop;
				while (node.offsetParent) {
					pos.x += node.offsetParent.offsetLeft;
					pos.y += node.offsetParent.offsetTop;
					node = node.offsetParent;
					if(stopNode && node.offsetParent)
						if (stopNode == node.offsetParent) break;
				}
				return pos;
			}, //getPosition(node,stopNode)
	
		// Static property used for counting the number of drag divs added (not really used at the moment, so could be removed if needed)
		_dragDivNum : 0,
		
		/**
		 * Create a new invisible DIV over all other elements in this document
		 *
		 * The purpose for the DIV is to capture all mouse events so they bubble down to the root document instead of
		 * getting captured by iframes along the way
		 *
		 * This can be called multiple times to create multiple layers (mostly so you can have modal support and dragging)
		 *
		 * @param	Node		doc		The document node (or a subnode whose document should be used) that the dragging is being performed on
		 *
		 * @return	Node		The newly constructed DIV node
		 */
		beginDrag : function(doc) {
		
		// TODO: There is one issue with this method: In IE, select controls are not covered by this div (due to infinite z-index bug). We would need to use an iframe instead of a div, and attach event listeners for the events we are interested in
		
			if (!doc)
				doc = document;
			else if (doc.nodeType != 9 /* DOCUMENT_NODE */)
				doc = doc.ownerDocument;
		
			var clientSize = DOM.getClientSize(doc);
		
			// Create a new DIV element to capture the events
			var d = doc.createElement('div');
			d.id = 'DOM.dragDiv'+ (DOM._dragDivNum++);
			d.style.width = clientSize[0] +'px';
			d.style.height = clientSize[1] +'px';
			d.style.backgroundColor = '#ffffff';
			DOM.setOpacity(d, 0.75);
		
			DOM.setFixedPosition(d, [ 0, 0 ]);
		
			// Add the DIV to the document and ensure it is on top of all other elements
		//	d = doc.body.appendChild(d);
			d = doc.body.appendChild(d);
			var z = DOM.bringToFront(d);
		
			// Add the DIV to the list of active drag DIVs
			if (!doc.DOM_dragDivs)
				doc.DOM_dragDivs = [ d ];
			else
				doc.DOM_dragDivs.push( d );
		
			return d;
		}, // DOM.beginDrag(doc)
		
		
		/**
		 * Remove the last invisible DIV created by DOM.beginDrag
		 *
		 * @param	{Node}	doc		The document node (or a subnode whose document should be used) that the dragging is being performed on
		 */
		endDrag : function(doc) {
			if (!doc)
				doc = document;
			else if (doc.nodeType != 9 /* DOCUMENT_NODE */)
				doc = doc.ownerDocument;
		
			// Remove the last drag DIV from the document & current list
			if (doc.DOM_dragDivs && (doc.DOM_dragDivs.length > 0))
				doc.body.removeChild(doc.DOM_dragDivs.pop());
		}, // DOM.endDrag(doc)
		

		_memberOfClass : function(obj_class, test_class) {
			var re = new RegExp('(?:^|\\s+)'+ common.quotemeta(test_class) +'(?:\\s+|$)');
			return re.test(obj_class);
		}, // DOM._memberOfClass(obj_class, test_class)


		/**
		 * Determines whether the classes in testClsList match the classes in classList
		 *
		 * @param	{String}		classList	The class list to check
		 * @param	{String}		testClsList	The class(es) to check for membership in
		 * @param	{Boolean}	matchAny		Whether all of the given classes must match (false - DEFAULT)
		 *									or a match of any of the classes are sufficient (true)
		 *
		 * @return	boolean		True if testClsList matches the given list of CSS classes in classList, False if not
		 */
		s_memberOfClass : function(classList, testClsList, matchAny) {
			if (/\s/.test(testClsList)) {
				var classes = testClsList.split(/\s+/);
				for (var i = 0; i < classes.length; ++i) {
					if (matchAny) {
						if (DOM._memberOfClass(classList, classes[i]))
							return true;
					} else if (!DOM._memberOfClass(classList, classes[i]))
							return false;
				}
				return true;
			}
			return DOM._memberOfClass(classList, testClsList);
		}, // DOM.s_memberOfClass(classList, testClsList [, matchAny = false ])


		memberOfClass : function(obj, clsList, matchAny) {
			return DOM.s_memberOfClass(obj.className, clsList, matchAny);
		}, // DOM.memberOfClass(obj, clsList [, matchAny = false ])


		/**
		 * Removes CSS classes from the given class list string
		 *
		 * @param	{String}	classList		The list of CSS classes
		 * @param	{String}	removeClsList		The name of the class(es) to remove
		 *
		 * @return	{String}		The modified class list
		 */
		s_removeClass : function(classList, removeClsList) {
			if (classList == null)
				classList = '';

			// Remove any existing entries for the classes
			var classes = removeClsList.split(/\s+/);
			for (var i=0; i<classes.length; ++i) {
				var re = new RegExp('(^|(?:\\s+))'+ common.quotemeta(classes[i]) +'((?:\\s+)|$)', 'g');

				classList = classList.replace(re, 
						function (match, p1, p2, offset, s) {
							if ((p1 == '') || (p2 == ''))
								return '';	// At beginning or end of string
							else
								return ' ';	// In middle of string
						});
			}
			return classList;
		}, // DOM.s_removeClass(classList, removeClsList)


		/**
		 * Removes CSS classes from the given DOM element
		 *
		 * @param	{Element}	obj				The DOM element to remove the class(es) from
		 * @param	{String}		removeClsList		The name of the class(es) to remove
		 *
		 * @return	{String}		The modified class list
		 */
		removeClass : function(obj, removeClsList) {
			obj.className = DOM.s_removeClass(obj.className, removeClsList);
		}, // DOM.removeClass(obj, removeClsList)


		/**
		 * Adds CSS class(es) to a list of classes if they are not already in the list
		 *
		 * @param	{String}	classList	The list of CSS classes
		 * @param	{String}	addClsList	The name of the class(es) to add
		 *
		 * @return	{String}		The modified class list
		 */
		s_addClass : function(classList, addClsList) {
			if (classList == null)
				classList = '';

			// Add any classes that are not already in the list
			var classes = addClsList.split(/\s+/);
			for (var i = 0; i < classes.length; ++i) {
				var re = new RegExp('(^|(?:\\s+))'+ common.quotemeta(classes[i]) +'((?:\\s+)|$)', 'g');
				if (!re.test(classList))
					classList += (classList ? ' ' : '') + classes[i];
			} // for

			return classList;
		}, // DOM.s_addClasss(classList, addClsList)


		/**
		 * Adds CSS class(es) to a list of classes for the given DOM element if they are not already assigned
		 *
		 * @param	Element	obj			The DOM element to add the class to
		 * @param	String	addClsList	The name of the class(es) to add
		 *
		 * @return	{String}		The modified class list
		 */
		addClass : function(obj, addClsList) {
			obj.className = DOM.s_addClass(obj.className, addClsList);
		}, // DOM.addClass(obj, addClsList)


		/**
		 * Replaces class(es) in classList with classes in addClassList
		 *
		 * If classList matches replaceClassList respective to the match flags (append & matchAny), then any
		 * classes in replaceClassList will be removed from classList regardless of whether the classes in
		 * addClassList were already in classList previously or not.
		 *
		 * If none of the classes in replaceClassList were found, then any classes in addClassList no already
		 * in classList will be added if append=true, or not added if append=false.
		 *
		 * @param	{String}		classList		The class list to modify
		 * @param	{String}		replaceClassList	List of existing classes to replace
		 * @param	{String}		addClassList		List of new classes to add
		 * @param	{Boolean}	append			Whether to append all classes in addClassList if _NONE_ of
		 *										the classes in replaceClassList are found (true - DEFAULT),
		 *										or leave the classList unmodified if none of the
		 *										replaceClassList match (false).
		 * @param	{Boolean}	matchAny			Whether all classes in replaceClassList must be found to
		 *										perform the replacement (false - DEFAULT), or if any
		 *										matched class is sufficient (true)
		 *
		 * @return	{String}		The modified class list
		 */
		s_replaceClass : function(classList, replaceClassList, addClassList, append, matchAny) {
			append = append || (append == null);

			if (classList == null)
				classList = '';

			var replaceClasses = replaceClassList.split(/\s+/);
			var numReplaced = 0;
			var newClassList = classList;
			for (var i = 0; i < replaceClasses.length; ++i) {
				var re = new RegExp('(^|(?:\\s+))'+ common.quotemeta(replaceClasses[i]) +'((?:\\s+)|$)', 'g');

				var r = false;
				newClassList = newClassList.replace(re, 
						function (match, p1, p2, offset, s) {
							r = true;
							if ((p1 == '') || (p2 == ''))
								return '';	// At beginning or end of string
							else
								return ' ';	// In middle of string
						});

				if (r) ++numReplaced;
			} // for

			if ( ((numReplaced > 0) && (matchAny || (numReplaced == replaceClasses.length))) ||
				 append
				)
			{
				return DOM.s_addClass(newClassList, addClassList);
			}

			// Return unmodified list
			return classList;
		}, // DOM.s_replaceClass(classList, removeClassList, addClassList [, append = true [, matchAny = false ]])


		/**
		 * Replaces class(es) in the given DOM element's classList with classes in addClassList
		 *
		 * @param	{Element}	obj				The DOM element to update the class list for
		 * @param	{String}		replaceClassList	List of existing classes to replace
		 * @param	{String}		addClassList		List of new classes to add
		 * @param	{Boolean}	append			Whether to append all classes in addClassList if _NONE_ of
		 *										the classes in replaceClassList are found (true - DEFAULT),
		 *										or leave the classList unmodified if none of the
		 *										replaceClassList match (false).
		 * @param	{Boolean}	matchAny			Whether all classes in replaceClassList must be found to
		 *										perform the replacement (false - DEFAULT), or if any
		 *										matched class is sufficient (true)
		 *
		 * @return	{String}		The modified class list
		 */
		replaceClass : function(obj, replaceClassList, addClassList, append, matchAny) {
			obj.className = DOM.s_replaceClass(obj.className, replaceClassList, addClassList, append, matchAny);
		}, // DOM.replaceClass(obj, replaceClassList, addClassList [, append = true [, matchAny = false ]])


		/**
		 * Strip comment tags from the given string
		 *
		 * @param	{String}	str	The string to stip comments from
		 *
		 * @return	{String}	The string with the comments stripped
		 */
		stripComments : function(str) {
			var re = new RegExp("<"+"!--(?:\n|\r|.)*?-->", "img");
			return str.replace(re, '');
		}, // DOM.stripComments(str)


		/**
		 * Strip script tags from the given string
		 *
		 * The concept comes from the prototype library where similar functionality is
		 * added as a method of the String object
		 *
		 * For some reason, the prototype method of one regex to match the script content seems to lock up IE
		 * when the script content got even moderately large (ie. at least at 3k-4k).  Strangely, it isn't
		 * a problem with the stripComments function above?????
		 * Because of that, this has been implemented as a more traditional parser with simpler regexes.
		 *
		 * @param	String	str	The string to stip scripts from
		 *
		 * @return	String	The string with the scripts stripped
		 */
		stripScripts : function(str) {
			if (str == null) return "";
			str = str + "";

			var s_re = /<script(?:\s+(?:\n|\r|.)*?)?>/ig;
			var e_re = /<\/script(?:\s+(?:\n|\r|.)*?)?>/ig;
		
			var rv = '';
			var p = 0;
			var p1 = 0;
			var s_res;
			var e_res;
			
			while ((s_res = s_re.exec(str)) != null) {
				p1 = s_res.index + s_res[0].length;
				e_re.lastIndex = p1;
		
				if ((e_res = e_re.exec(str)) != null) {
		//			var content = str.substr(p1, e_res.index - p1);
					rv += str.substr(p, s_res.index - p);
					p = e_res.index + e_res[0].length;
				} else
					p = p1;
			} // while
		
			if (p < str.length)
				rv += str.substr(p);
		
			return rv;
		}, // DOM.stripScripts(str)
		
		
		/**
		 * Evaluate scripts embedded in a string
		 *
		 * The concept comes from the prototype library where similar functionality is
		 * added as a method of the String object
		 *
		 * For some reason, the prototype method of one regex to match the script content seems to lock up IE
		 * when the script content got even moderately large (ie. at least at 3k-4k).  Strangely, it isn't
		 * a problem with the stripComments function above?????
		 * Because of that, this has been implemented as a more traditional parser with simpler regexes.
		 *
		 * @param	String	str	The string to strip scripts from
		 * @param	Window	wnd	The window object to execute in the context of
		 *
		 * @return	String	The string with the scripts stripped
		 */
		evalScripts : function(str, wnd) {
			if (!wnd)
				wnd = this;

			var s_re = /<script(?:\s+(?:\n|\r|.)*?)?>/ig;
			var e_re = /<\/script(?:\s+(?:\n|\r|.)*?)?>/ig;
		
			var rv = '';
			var p = 0;
			var p1 = 0;
			var s_res;
			var e_res;
			
			while ((s_res = s_re.exec(str)) != null) {
				p1 = s_res.index + s_res[0].length;
				e_re.lastIndex = p1;
		
				if ((e_res = e_re.exec(str)) != null) {
					var content = str.substr(p1, e_res.index - p1);

// TODO: Is "wnd.eval(content)" better to use here?

					if (wnd.execScript) {
						wnd.execScript(content);		// Should work in IE
					} else
						wnd.setTimeout(content, 0);		// Should work in Gecko,Opera,Safari,Konqueror but not IE
//						eval.call(wnd, content);		// Works in FF, and should work in other compliant browsers
														//   If this doesn't work in all browsers, we can also use:
														//		window.setTimeout(content, 0);
														//   which should execute the code in the context of the window
														//   (and seems to in all browsers _except_ IE)
		
					rv += str.substr(p, s_res.index - p);
					p = e_res.index + e_res[0].length;
				} else
					p = p1;
			} // while
		
		/*
			var re = /<script(?:\n|\r|.)*?>(?:\n|\r|.)*<\/script>/img;
		//	var re = /<script(?:\s+(.*?))?>((?:\n|\r|.)*?)<\/script(?:\s+.*?)?>/img;
			var res;
		
			while ((res = re.exec(str)) != null) {
				// res[1] = script tag attributes (ignored for now)
				// res[2] = script content
				eval.call(window, res[2]);		// Functions declared directly in the script are not attached to the window properly
			} // while
		*/
		}, // DOM.evalScripts(str)
		
		
		parseXML : function(xmlStr) {
			if (window.execScript) {
				// code for IE
				DOM.parseXML = function(xmlStr) {
					var doc = new ActiveXObject("Microsoft.XMLDOM");
					doc.async = "false";
					doc.loadXML(xmlStr);
					return doc;
				};
			} else {
				// code for Mozilla, Firefox, Opera, etc.
				DOM.parseXML = function(xmlStr) {
					var parser = new DOMParser();
					return parser.parseFromString(xmlStr, "text/xml");
				};
			}
		
			return DOM.parseXML(xmlStr);
		}, // DOM.parseXML(xmlStr)


		serializeXML : function(XMLNode) {
			if (!XMLNode) return null;
			if (window.XMLSerializer) {
				// Firefox
				DOM.serializeXML = function(XMLNode) {
							if (!XMLNode) return null;
							var s = new XMLSerializer();
							return s.serializeToString(XMLNode);
						};
				
			} else if (XMLNode.xml) {
				// MSXML Parser
				DOM.serializeXML = function(XMLNode) {
							if (!XMLNode) return null;
							return XMLNode.xml;
						};
			} else if (XMLNode.OuterXml) {
				// MSXML Parser?
				DOM.serializeXML = function(XMLNode) {
							if (!XMLNode) return null;
							return XMLNode.OuterXml;
						};
			} else {
				// Worst-case... Some other browsers?
				// NOTE: This won't handle namespaces properly
				DOM.serializeXML = function(XMLNode) {
						if (!XMLNode) return null;

						// Handle text node
						if (XMLNode.nodeType == 3)
							return XMLNode.nodeValue;

						// Handle all other node types
						var rv;
						var isDoc = ((XMLNode.nodeType == 9) || (XMLNode.nodeType == 11));
						if (!isDoc) {
							rv = '<'+ XMLNode.nodeName;

							// process attributes
							for (var i = 0; i < XMLNode.attributes.length; ++i) {
								var a = XMLNode.attributes[i];
								rv += ' '+ common.xmlentities(a.nodeName) +'="'+ common.xmlentities(a.nodeValue) +'"';
							}

							rv += (XMLNode.firstChild ? '>' : ' />');
						}

						if (XMLNode.firstChild) {
							rv += DOM.getInnerXML(XMLNode);
							if (!isDoc)
								rv += '</'+ XMLNode.nodeName +'>';
						}

						return rv;
					};
			}
			DOM.getOuterXML = DOM.serializeXML;
			return DOM.serializeXML(XMLNode);
		}, // DOM.serializeXML(XMLNode)


		getInnerXML : function(XMLNode) {
			if (XMLNode.innerXML) {
				// IE/Microsoft XML Parser
				DOM.getInnerXML = function(XMLNode) {
						return XMLNode.innerXML;
					};
			} else {
				// Firefox/etc.
				DOM.getInnerXML = function(XMLNode) {
						var rv = '';

						if (XMLNode.firstChild) {
							var c = XMLNode.firstChild;
							while (c) {
								rv += DOM.getOuterXML(c);
								c = c.nextSibling;
							} // while
						}

						return rv;
					};
			}
			return DOM.getInnerXML(XMLNode);
		} // MenuItem.getInnerXML(XMLNode)


		/*
		// TODO: The following could be useful in conjunction with innerHTML... For implementing MS insertAdjacentHTML in Mozilla/Firefox.
		//   The createContextualFragment may be Mozilla-specific, so probably won't work in other browsers.
		//	Could possibly research ways to implement this to work in other browsers.
		// Source: http://webfx.eae.net/
		HTMLElement.prototype.insertAdjacentHTML = function (sWhere, sHTML) {
		   var df;   // : DocumentFragment
		   var r = this.ownerDocument.createRange();
		
		   switch (String(sWhere).toLowerCase()) {  // convert to string and unify case
			  case "beforebegin":
				 r.setStartBefore(this);
				 df = r.createContextualFragment(sHTML);
				 this.parentNode.insertBefore(df, this);
				 break;
				
			  case "afterbegin":
				 r.selectNodeContents(this);
				 r.collapse(true);
				 df = r.createContextualFragment(sHTML);
				 this.insertBefore(df, this.firstChild);
				 break;
				
			  case "beforeend":
				 r.selectNodeContents(this);
				 r.collapse(false);
				 df = r.createContextualFragment(sHTML);
				 this.appendChild(df);
				 break;
				
			  case "afterend":
				 r.setStartAfter(this);
				 df = r.createContextualFragment(sHTML);
				 this.parentNode.insertBefore(df, this.nextSibling);
				 break;
		   }
		};
		*/
		
	} // END OF DOM NAMESPACE

	// The following are just aliases so the same mechanism can be used for implementing modal popups as for drag-n-drop
	DOM.beginModal = DOM.beginDrag;
	DOM.endModal = DOM.endDrag;
	DOM.getOuterXML = DOM.serializeXML;

}

(function(){
	var handleKey = function(e) {
							DOM.altKey = e.altKey;
							DOM.ctrlKey = e.ctrlKey;
							DOM.shiftKey = e.shiftKey;
							DOM.metaKey = e.metaKey;
//							debug("alt="+ DOM.altKey +" ctrl="+ DOM.ctrlKey +" shift="+ DOM.shiftKey +" meta="+ DOM.metaKey);
						};
	DOM.addEventListener(document, 'keydown', handleKey);
	DOM.addEventListener(document, 'keyup', handleKey);
})();

//} // if (!DOM)
