JSLIB.namespace("JSLIB.dom");

/**
 * @class Provides helper methods for DOM elements.
 */
JSLIB.dom = function() {
   var ua = navigator.userAgent.toLowerCase();
   var isOpera = (ua.indexOf('opera') > -1);
   var isSafari = (ua.indexOf('safari') > -1);
   var isIE = (window.ActiveXObject);

   var id_counter = 0;
   var util = JSLIB.util;
   
   return {
		// Get HTML element reference
		// elem: a string (id of element), an element, or an array of IDs
		// return HTML element reference or array of references if found, null if not found.
		get: function(elem) {
			// return if elem contains nothing
			if (!elem) {
				return null;
			}
			// is elem a string? if so, look up the element by ID
			if (util.isString(elem)) {
				return document.all ? document.all[elem] : document.getElementById(elem);
			}
			// is elem an array? if so, look up each array element
			else if (util.isArray(elem)) {
				var arr = [];
				for (var i in elem) {
					var obj = this.get(elem[i]);
					if (obj) {
						arr[arr.length] = obj;
					}
				}
				return arr;
			}
			// nothing else? assuming it is an element reference
			else {
				return elem;
			}
			return null;
		},

        // get HTML elements that match criteria
        // testfunc - an element will be passed to testfunc. the element will be included in result iff testfunc return true.
        // tag (optional) - return elements with tagName 'tag'
        // root (optional) - root element to start searching from
        // return - array of elements
        // note: for performance reason, specifies tag and root if possible.
        getElementsBy : function(testfunc, tag, root) {
            var result = [];
            var elements;
            if (tag && root && root.getElementsByTagName) {
                elements = root.getElementsByTagName(tag);
            }
            else if (tag) {
                elements = document.getElementsByTagName(tag);
            }
            else {
                elements = document.getElementsByTagName("*");
            }
            
            for (var i = 0; i < elements.length; i ++) {
                if (testfunc(elements[i])) {
                    result[result.length] = elements[i];
                }
            }
            
            return result;
        },
        
        // get HTML elements that has class 'className'
        // className - return element that has this className specified by this argument
        // tag (optional) - return elements with tagName 'tag'
        // root (optional) - root element to start searching from
        // return - array of elements
        // note: for performance reason, specifies tag and root if possible.
        getElementsByClassName : function(className, tag, root) {
            return this.getElementsBy(
                new Function("return JSLIB.dom.hasClass(arguments[0], '" + className + "')"), 
                tag, root);
        },

		// get the style property (not the computed one) of an elemnt
        // elem - element or name of element
        // property - css property to retrieve
        // return string
		getStyle: function(elem, property) {
			var obj = this.get(elem);
			if (!obj) {
				return null;
			}
			return obj.style[property];
		},
		
		// get the computed style property of an elemnt
		getComputedStyle: function(elem, property) {
			var dv = document.defaultView;
			var obj = this.get(elem);
			var value;
			if (!obj) {
				return null;
			}
            if (isIE && obj.currentStyle && obj.currentStyle[property]) { // isIE to workaround broken Opera 9 currentStyle
               value = obj.currentStyle[property];
            }
            else if (dv && dv.getComputedStyle) {
               var computed = dv.getComputedStyle(obj, '');
               if (computed && computed[property]) {
                    value = computed[property];
               }
            }
            else if (obj.style[property]) {
               value = obj.style[property];
            }
			return value;
		},
   
		// set the style of an element
		setStyle: function(elem, property, val) {
			var obj = this.get(elem)
			switch(property) {
			// provides a standard way to update the opacity property
			case 'opacity' :
				if (isIE && typeof obj.style.filter == 'string') { // in case not appended
					obj.style.filter = 'alpha(opacity=' + val * 100 + ')';
					if (!obj.currentStyle || !obj.currentStyle.hasLayout) {
						el.style.zoom = 1; // when no layout or cant tell
					}
				} 
				else {
					obj.style.opacity = val;
					obj.style['-moz-opacity'] = val;
					obj.style['-khtml-opacity'] = val;
				}
				break;
			default :
				obj.style[property] = val;
				break;
			}
		},

		// get the position of an element in page coordinates. this method does not take into account any scrolling layer.
		// elem: string ID or element reference
		// return: array [x,y]
		getXY: function(elem) {
			var elm = this.get(elem);
			if (!elm) {
				return null;
			}
			var mOL=elm.offsetLeft;
			var mOT=elm.offsetTop;
			var mOP=elm.offsetParent;
			while(mOP){
				mOL+=mOP.offsetLeft
				mOT+=mOP.offsetTop;
				mOP=mOP.offsetParent
			}
			return [mOL,mOT];
		},
      
		// get the x-coordinate of an element in page coordinates
		getX: function(el) {
			return this.getXY(el)[0];
		},

		// get the y-coordinate of an element in page coordinates
		getY: function(el) {
			return this.getXY(el)[1];
		},

		// set the xy coordinate of an abs positioned element
		setXY: function(elem, pos) {
			var elm = this.get(elem);
			if (!elm) {
				return null;
			}
			if (!util.isNull(pos[0])) {
				this.setStyle(elm, "left", pos[0]+"px");
			}
			if (!util.isNull(pos[1])) {
				this.setStyle(elm, "top", pos[1]+"px");
			}
		},

		// set the x coordinate of an abs positioned element
		setX: function(el, x) {
			this.setXY(el, [x, null]);
		},
      
		// set the y coordinate of an abs positioned element
		setY: function(el, y) {
			this.setXY(el, [null, y]);
		},

		// returns whether element has particular class
		// elem: string ID or element reference
		// return: boolean (true - hasClass, false otherwise)
		hasClass: function(elem, className) {
			var obj = this.get(elem);
			if (!obj) {
				return false;
			}
			var re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
			return re.test(obj['className']);
		},
		
		addClass: function(elem, className) {
			var obj = this.get(elem);
			if (!obj) {
				return;
			}
			// has the class already. just return.
			if (this.hasClass(elem, className)) {
				return;
			}
			// concatenate className if there is something set.
			if (obj.className && obj.className.length > 0) {
				obj.className += " " + className;
			}
			else {
				obj.className = className;
			}
		},
		
		removeClass: function(elem, className) {
			var obj = this.get(elem);
			if (!obj) {
				return;
			}
			while (this.hasClass(elem, className)) {
	            var tmpClass = obj['className'];
    	        obj['className'] = tmpClass.replace(className, ' ');
			}
		},
		
		replaceClass: function(elem, oldClassName, newClassName) {
			this.removeClass(elem, oldClassName);
			this.addClass(elem, newClassName);
		},

		// set an ID to the specified element if none exists
		generateId: function(elem, prefix) {
			prefix = prefix || 'jslib-gen-';
			var obj = this.get(elem);
			if (obj) {
				if (!obj.id) {
					obj.id = prefix + this.id_counter;
					this.id_counter ++;
				}
				return obj.id
			}
			return null;
		},
		
		isAncestor: function(haystack, needle) {
			haystack = this.get(haystack);
			needle = this.get(needle);
			
			if (!haystack || !needle) {
				return false;
			}
			
			if (this.isIE && haystack.contains) {
				return haystack.contains(needle);
			}
			else {
               var parent = needle.parentNode;
               while (parent) {
                  if (parent == haystack) {
                     return true;
                  }
                  parent = parent.parentNode;
               }
               return false;			
			}
		},
		
		inDocument: function(elem) {
			return this.isAncestor(document.documentElement, elem);
		},
		
      /**
       * Returns the height of the document.
       * @return {Int} The height of the actual document (which includes the body and its margin).
       */
      getDocumentHeight: function() {
         var scrollHeight=-1,windowHeight=-1,bodyHeight=-1;
         var marginTop = parseInt(util.Dom.getStyle(document.body, 'marginTop'), 10);
         var marginBottom = parseInt(util.Dom.getStyle(document.body, 'marginBottom'), 10);
         
         var mode = document.compatMode;
         
         if ( (mode || isIE) && !isOpera ) { // (IE, Gecko)
            switch (mode) {
               case 'CSS1Compat': // Standards mode
                  scrollHeight = ((window.innerHeight && window.scrollMaxY) ?  window.innerHeight+window.scrollMaxY : -1);
                  windowHeight = [document.documentElement.clientHeight,self.innerHeight||-1].sort(function(a, b){return(a-b);})[1];
                  bodyHeight = document.body.offsetHeight + marginTop + marginBottom;
                  break;
               
               default: // Quirks
                  scrollHeight = document.body.scrollHeight;
                  bodyHeight = document.body.clientHeight;
            }
         } else { // Safari & Opera
            scrollHeight = document.documentElement.scrollHeight;
            windowHeight = self.innerHeight;
            bodyHeight = document.documentElement.clientHeight;
         }
      
         var h = [scrollHeight,windowHeight,bodyHeight].sort(function(a, b){return(a-b);});
         return h[2];
      },
      
      /**
       * Returns the width of the document.
       * @return {Int} The width of the actual document (which includes the body and its margin).
       */
      getDocumentWidth: function() {
         var docWidth=-1,bodyWidth=-1,winWidth=-1;
         var marginRight = parseInt(util.Dom.getStyle(document.body, 'marginRight'), 10);
         var marginLeft = parseInt(util.Dom.getStyle(document.body, 'marginLeft'), 10);
         
         var mode = document.compatMode;
         
         if (mode || isIE) { // (IE, Gecko, Opera)
            switch (mode) {
               case 'CSS1Compat': // Standards mode
                  docWidth = document.documentElement.clientWidth;
                  bodyWidth = document.body.offsetWidth + marginLeft + marginRight;
                  winWidth = self.innerWidth || -1;
                  break;
                  
               default: // Quirks
                  bodyWidth = document.body.clientWidth;
                  winWidth = document.body.scrollWidth;
                  break;
            }
         } else { // Safari
            docWidth = document.documentElement.clientWidth;
            bodyWidth = document.body.offsetWidth + marginLeft + marginRight;
            winWidth = self.innerWidth;
         }
      
         var w = [docWidth,bodyWidth,winWidth].sort(function(a, b){return(a-b);});
         return w[2];
      },

      /**
       * Returns the current height of the viewport.
       * @return {Int} The height of the viewable area of the page (excludes scrollbars).
       */
      getViewportHeight: function() {
         var height = -1;
         var mode = document.compatMode;
      
         if ( (mode || isIE) && !isOpera ) {
            switch (mode) { // (IE, Gecko)
               case 'CSS1Compat': // Standards mode
                  height = document.documentElement.clientHeight;
                  break;
      
               default: // Quirks
                  height = document.body.clientHeight;
            }
         } else { // Safari, Opera
            height = self.innerHeight;
         }
      
         return height;
      },
      
      /**
       * Returns the current width of the viewport.
       * @return {Int} The width of the viewable area of the page (excludes scrollbars).
       */
      
      getViewportWidth: function() {
         var width = -1;
         var mode = document.compatMode;
         
         if (mode || isIE) { // (IE, Gecko, Opera)
            switch (mode) {
            case 'CSS1Compat': // Standards mode 
               width = document.documentElement.clientWidth;
               break;
               
            default: // Quirks
               width = document.body.clientWidth;
            }
         } else { // Safari
            width = self.innerWidth;
         }
         return width;
      }

   };
}();


