//-----------------------------------------------------------------------
// Copyright (C) Motorwebs Corporation. All rights reserved.
//-----------------------------------------------------------------------
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Permissive License.
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
// All other rights reserved.

Type.registerNamespace('Motorwebs.UI');

Motorwebs.UI.BoxSide = function() {
  /// <summary>
  /// The BoxSide enumeration describes the sides of a DOM element
  /// </summary>
  /// <field name="Top" type="Number" integer="true" static="true" />
  /// <field name="Right" type="Number" integer="true" static="true" />
  /// <field name="Bottom" type="Number" integer="true" static="true" />
  /// <field name="Left" type="Number" integer="true" static="true" />
}

Motorwebs.UI.BoxSide.prototype = {
  Top: 0,
  Right: 1,
  Bottom: 2,
  Left: 3
}

Motorwebs.UI.BoxSide.registerEnum("Motorwebs.UI.BoxSide", false);

Motorwebs.UI._Common = function() { }
Motorwebs.UI._Common.prototype = {

  // The order of these lookup tables is directly linked to the BoxSide enum defined above
  _borderStyleNames: ["borderTopStyle", "borderRightStyle", "borderBottomStyle", "borderLeftStyle"],
  _borderWidthNames: ["borderTopWidth", "borderRightWidth", "borderBottomWidth", "borderLeftWidth"],
  _paddingWidthNames: ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft"],
  _marginWidthNames: ["marginTop", "marginRight", "marginBottom", "marginLeft"],

  getCurrentStyle: function(element, attribute, defaultValue) {
    /// <summary>
    /// getCurrentStyle is used to compute the value of a style attribute on an
    /// element that is currently being displayed.  This is especially useful for scenarios where
    /// several CSS classes and style attributes are merged, or when you need information about the
    /// size of an element (such as its padding or margins) that is not exposed in any other fashion.
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// Live DOM element to check style of
    /// </param>
    /// <param name="attribute" type="String">
    /// The style attribute's name is expected to be in a camel-cased form that you would use when
    /// accessing a JavaScript property instead of the hyphenated form you would use in a CSS
    /// stylesheet (i.e. it should be "backgroundColor" and not "background-color").
    /// </param>
    /// <param name="defaultValue" type="Object" mayBeNull="true" optional="true">
    /// In the event of a problem (i.e. a null element or an attribute that cannot be found) we
    /// return this object (or null if none if not specified).
    /// </param>
    /// <returns type="Object">
    /// Current style of the element's attribute
    /// </returns>

    var currentValue = null;
    if (element) {
      if (element.currentStyle) {
        currentValue = element.currentStyle[attribute];
      } else if (document.defaultView && document.defaultView.getComputedStyle) {
        var style = document.defaultView.getComputedStyle(element, null);
        if (style) {
          currentValue = style[attribute];
        }
      }

      if (!currentValue && element.style.getPropertyValue) {
        currentValue = element.style.getPropertyValue(attribute);
      }
      else if (!currentValue && element.style.getAttribute) {
        currentValue = element.style.getAttribute(attribute);
      }
    }

    if ((!currentValue || currentValue == "" || typeof (currentValue) === 'undefined')) {
      if (typeof (defaultValue) != 'undefined') {
        currentValue = defaultValue;
      }
      else {
        currentValue = null;
      }
    }
    return currentValue;
  },

  getInheritedBackgroundColor: function(element) {
    /// <summary>
    /// CommonScripts.getInheritedBackgroundColor provides the ability to get the displayed
    /// background-color of an element.  In most cases calling CommonScripts.getCurrentStyle
    /// won't do the job because it will return "transparent" unless the element has been given a
    /// specific background color.  This function will walk up the element's parents until it finds
    /// a non-transparent color.  If we get all the way to the top of the document or have any other
    /// problem finding a color, we will return the default value '#FFFFFF'.  This function is
    /// especially important when we're using opacity in IE (because ClearType will make text look
    /// horrendous if you fade it with a transparent background color).
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// Live DOM element to get the background color of
    /// </param>
    /// <returns type="String">
    /// Background color of the element
    /// </returns>

    if (!element) return '#FFFFFF';
    var background = this.getCurrentStyle(element, 'backgroundColor');
    try {
      while (!background || background == '' || background == 'transparent' || background == 'rgba(0, 0, 0, 0)') {
        element = element.parentNode;
        if (!element) {
          background = '#FFFFFF';
        } else {
          background = this.getCurrentStyle(element, 'backgroundColor');
        }
      }
    } catch (ex) {
      background = '#FFFFFF';
    }
    return background;
  },

  getLocation: function(element) {
    /// <summary>Gets the coordinates of a DOM element.</summary>
    /// <param name="element" domElement="true"/>
    /// <returns type="Sys.UI.Point">
    ///   A Point object with two fields, x and y, which contain the pixel coordinates of the element.
    /// </returns>

    // workaround for an issue in getLocation where it will compute the location of the document element.
    // this will return an offset if scrolled.
    //
    if (element === document.documentElement) {
      return new Sys.UI.Point(0, 0);
    }

    // Workaround for IE6 bug in getLocation (also required patching getBounds - remove that fix when this is removed)
    if (Sys.Browser.agent == Sys.Browser.InternetExplorer && Sys.Browser.version < 7) {
      if (element.window === element || element.nodeType === 9 || !element.getClientRects || !element.getBoundingClientRect) return new Sys.UI.Point(0, 0);

      // Get the first bounding rectangle in screen coordinates
      var screenRects = element.getClientRects();
      if (!screenRects || !screenRects.length) {
        return new Sys.UI.Point(0, 0);
      }
      var first = screenRects[0];

      // Delta between client coords and screen coords
      var dLeft = 0;
      var dTop = 0;

      var inFrame = false;
      try {
        inFrame = element.ownerDocument.parentWindow.frameElement;
      } catch (ex) {
        // If accessing the frameElement fails, a frame is probably in a different
        // domain than its parent - and we still want to do the calculation below
        inFrame = true;
      }

      // If we're in a frame, get client coordinates too so we can compute the delta
      if (inFrame) {
        // Get the bounding rectangle in client coords
        var clientRect = element.getBoundingClientRect();
        if (!clientRect) {
          return new Sys.UI.Point(0, 0);
        }

        // Find the minima in screen coords
        var minLeft = first.left;
        var minTop = first.top;
        for (var i = 1; i < screenRects.length; i++) {
          var r = screenRects[i];
          if (r.left < minLeft) {
            minLeft = r.left;
          }
          if (r.top < minTop) {
            minTop = r.top;
          }
        }

        // Compute the delta between screen and client coords
        dLeft = minLeft - clientRect.left;
        dTop = minTop - clientRect.top;
      }

      // Subtract 2px, the border of the viewport (It can be changed in IE6 by applying a border style to the HTML element,
      // but this is not supported by ASP.NET AJAX, and it cannot be changed in IE7.), and also subtract the delta between
      // screen coords and client coords
      var ownerDocument = element.document.documentElement;
      return new Sys.UI.Point(first.left - 2 - dLeft + ownerDocument.scrollLeft, first.top - 2 - dTop + ownerDocument.scrollTop);
    }

    return Sys.UI.DomElement.getLocation(element);
  },

  setLocation: function(element, point) {
    /// <summary>
    /// Sets the current location for an element.
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// DOM element
    /// </param>
    /// <param name="point" type="Object">
    /// Point object (of the form {x,y})
    /// </param>
    /// <remarks>
    /// This method does not attempt to set the positioning mode of an element.
    /// The position is relative from the elements nearest position:relative or
    /// position:absolute element.
    /// </remarks>
    Sys.UI.DomElement.setLocation(element, point.x, point.y);
  },

  getContentSize: function(element) {
    /// <summary>
    /// Gets the "content-box" size of an element.
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// DOM element
    /// </param>
    /// <returns type="Object">
    /// Size of the element (in the form {width,height})
    /// </returns>
    /// <remarks>
    /// The "content-box" is the size of the content area *inside* of the borders and
    /// padding of an element. The "content-box" size does not include the margins around
    /// the element.
    /// </remarks>

    if (!element) {
      throw Error.argumentNull('element');
    }
    var size = this.getSize(element);
    var borderBox = this.getBorderBox(element);
    var paddingBox = this.getPaddingBox(element);
    return {
      width: size.width - borderBox.horizontal - paddingBox.horizontal,
      height: size.height - borderBox.vertical - paddingBox.vertical
    }
  },

  getSize: function(element) {
    /// <summary>
    /// Gets the "border-box" size of an element.
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// DOM element
    /// </param>
    /// <returns type="Object">
    /// Size of the element (in the form {width,height})
    /// </returns>
    /// <remarks>
    /// The "border-box" is the size of the content area *outside* of the borders and
    /// padding of an element.  The "border-box" size does not include the margins around
    /// the element.
    /// </remarks>

    if (!element) {
      throw Error.argumentNull('element');
    }
    return {
      width: element.offsetWidth,
      height: element.offsetHeight
    };
  },

  setContentSize: function(element, size) {
    /// <summary>
    /// Sets the "content-box" size of an element.
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// DOM element
    /// </param>
    /// <param name="size" type="Object">
    /// Size of the element (in the form {width,height})
    /// </param>
    /// <remarks>
    /// The "content-box" is the size of the content area *inside* of the borders and
    /// padding of an element. The "content-box" size does not include the margins around
    /// the element.
    /// </remarks>

    if (!element) {
      throw Error.argumentNull('element');
    }
    if (!size) {
      throw Error.argumentNull('size');
    }
    // FF respects -moz-box-sizing css extension, so adjust the box size for the border-box
    if (this.getCurrentStyle(element, 'MozBoxSizing') == 'border-box' || this.getCurrentStyle(element, 'BoxSizing') == 'border-box') {
      var borderBox = this.getBorderBox(element);
      var paddingBox = this.getPaddingBox(element);
      size = {
        width: size.width + borderBox.horizontal + paddingBox.horizontal,
        height: size.height + borderBox.vertical + paddingBox.vertical
      };
    }
    element.style.width = size.width.toString() + 'px';
    element.style.height = size.height.toString() + 'px';
  },

  setSize: function(element, size) {
    /// <summary>
    /// Sets the "border-box" size of an element.
    /// </summary>
    /// <remarks>
    /// The "border-box" is the size of the content area *outside* of the borders and 
    /// padding of an element.  The "border-box" size does not include the margins around
    /// the element.
    /// </remarks>
    /// <param name="element" type="Sys.UI.DomElement">DOM element</param>
    /// <param name="size" type="Object">Size of the element (in the form {width,height})</param>
    /// <returns />

    if (!element) {
      throw Error.argumentNull('element');
    }
    if (!size) {
      throw Error.argumentNull('size');
    }
    var borderBox = this.getBorderBox(element);
    var paddingBox = this.getPaddingBox(element);
    var contentSize = {
      width: size.width - borderBox.horizontal - paddingBox.horizontal,
      height: size.height - borderBox.vertical - paddingBox.vertical
    };
    this.setContentSize(element, contentSize);
  },

  getBounds: function(element) {
    /// <summary>Gets the coordinates, width and height of an element.</summary>
    /// <param name="element" domElement="true"/>
    /// <returns type="Sys.UI.Bounds">
    ///   A Bounds object with four fields, x, y, width and height, which contain the pixel coordinates,
    ///   width and height of the element.
    /// </returns>
    /// <remarks>
    ///   Use the CommonScripts version of getLocation to handle the workaround for IE6.  We can
    ///   remove the below implementation and just call Sys.UI.DomElement.getBounds when the other bug
    ///   is fixed.
    /// </remarks>

    var offset = $common.getLocation(element);
    return new Sys.UI.Bounds(offset.x, offset.y, element.offsetWidth || 0, element.offsetHeight || 0);
  },

  setBounds: function(element, bounds) {
    /// <summary>
    /// Sets the "border-box" bounds of an element
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// DOM element
    /// </param>
    /// <param name="bounds" type="Object">
    /// Bounds of the element (of the form {x,y,width,height})
    /// </param>
    /// <remarks>
    /// The "border-box" is the size of the content area *outside* of the borders and
    /// padding of an element.  The "border-box" size does not include the margins around
    /// the element.
    /// </remarks>

    if (!element) {
      throw Error.argumentNull('element');
    }
    if (!bounds) {
      throw Error.argumentNull('bounds');
    }
    this.setSize(element, bounds);
    $common.setLocation(element, bounds);
  },

  getClientBounds: function() {
    /// <summary>
    /// Gets the width and height of the browser client window (excluding scrollbars)
    /// </summary>
    /// <returns type="Sys.UI.Bounds">
    /// Browser's client width and height
    /// </returns>

    var clientWidth;
    var clientHeight;
    switch (Sys.Browser.agent) {
      case Sys.Browser.InternetExplorer:
        clientWidth = document.documentElement.clientWidth;
        clientHeight = document.documentElement.clientHeight;
        break;
      case Sys.Browser.Safari:
        clientWidth = window.innerWidth;
        clientHeight = window.innerHeight;
        break;
      case Sys.Browser.Opera:
        clientWidth = Math.min(window.innerWidth, document.body.clientWidth);
        clientHeight = Math.min(window.innerHeight, document.body.clientHeight);
        break;
      default:  // Sys.Browser.Firefox, etc.
        clientWidth = Math.min(window.innerWidth, document.documentElement.clientWidth);
        clientHeight = Math.min(window.innerHeight, document.documentElement.clientHeight);
        break;
    }
    return new Sys.UI.Bounds(0, 0, clientWidth, clientHeight);
  },

  getMarginBox: function(element) {
    /// <summary>
    /// Gets the entire margin box sizes.
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// DOM element
    /// </param>
    /// <returns type="Object">
    /// Element's margin box sizes (of the form {top,left,bottom,right,horizontal,vertical})
    /// </returns>

    if (!element) {
      throw Error.argumentNull('element');
    }
    var box = {
      top: this.getMargin(element, Motorwebs.UI.BoxSide.Top),
      right: this.getMargin(element, Motorwebs.UI.BoxSide.Right),
      bottom: this.getMargin(element, Motorwebs.UI.BoxSide.Bottom),
      left: this.getMargin(element, Motorwebs.UI.BoxSide.Left)
    };
    box.horizontal = box.left + box.right;
    box.vertical = box.top + box.bottom;
    return box;
  },

  getBorderBox: function(element) {
    /// <summary>
    /// Gets the entire border box sizes.
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// DOM element
    /// </param>
    /// <returns type="Object">
    /// Element's border box sizes (of the form {top,left,bottom,right,horizontal,vertical})
    /// </returns>

    if (!element) {
      throw Error.argumentNull('element');
    }
    var box = {
      top: this.getBorderWidth(element, Motorwebs.UI.BoxSide.Top),
      right: this.getBorderWidth(element, Motorwebs.UI.BoxSide.Right),
      bottom: this.getBorderWidth(element, Motorwebs.UI.BoxSide.Bottom),
      left: this.getBorderWidth(element, Motorwebs.UI.BoxSide.Left)
    };
    box.horizontal = box.left + box.right;
    box.vertical = box.top + box.bottom;
    return box;
  },

  getPaddingBox: function(element) {
    /// <summary>
    /// Gets the entire padding box sizes.
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// DOM element
    /// </param>
    /// <returns type="Object">
    /// Element's padding box sizes (of the form {top,left,bottom,right,horizontal,vertical})
    /// </returns>

    if (!element) {
      throw Error.argumentNull('element');
    }
    var box = {
      top: this.getPadding(element, Motorwebs.UI.BoxSide.Top),
      right: this.getPadding(element, Motorwebs.UI.BoxSide.Right),
      bottom: this.getPadding(element, Motorwebs.UI.BoxSide.Bottom),
      left: this.getPadding(element, Motorwebs.UI.BoxSide.Left)
    };
    box.horizontal = box.left + box.right;
    box.vertical = box.top + box.bottom;
    return box;
  },

  isBorderVisible: function(element, boxSide) {
    /// <summary>
    /// Gets whether the current border style for an element on a specific boxSide is not 'none'.
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// DOM element
    /// </param>
    /// <param name="boxSide" type="Motorwebs.UI.BoxSide">
    /// Side of the element
    /// </param>
    /// <returns type="Boolean">
    /// Whether the current border style for an element on a specific boxSide is not 'none'.
    /// </returns>

    if (!element) {
      throw Error.argumentNull('element');
    }
    if (boxSide < Motorwebs.UI.BoxSide.Top || boxSide > Motorwebs.UI.BoxSide.Left) {
      throw Error.argumentOutOfRange(String.format(Sys.Res.enumInvalidValue, boxSide, 'Motorwebs.UI.BoxSide'));
    }
    var styleName = this._borderStyleNames[boxSide];
    var styleValue = this.getCurrentStyle(element, styleName);
    return styleValue != "none";
  },

  getMargin: function(element, boxSide) {
    /// <summary>
    /// Gets the margin thickness of an element on a specific boxSide.
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// DOM element
    /// </param>
    /// <param name="boxSide" type="Motorwebs.UI.BoxSide">
    /// Side of the element
    /// </param>
    /// <returns type="Number" integer="true">
    /// Margin thickness on the element's specified side
    /// </returns>

    if (!element) {
      throw Error.argumentNull('element');
    }
    if (boxSide < Motorwebs.UI.BoxSide.Top || boxSide > Motorwebs.UI.BoxSide.Left) {
      throw Error.argumentOutOfRange(String.format(Sys.Res.enumInvalidValue, boxSide, 'Motorwebs.UI.BoxSide'));
    }
    var styleName = this._marginWidthNames[boxSide];
    var styleValue = this.getCurrentStyle(element, styleName);
    try { return this.parsePadding(styleValue); } catch (ex) { return 0; }
  },

  getBorderWidth: function(element, boxSide) {
    /// <summary>
    /// Gets the border thickness of an element on a specific boxSide.
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// DOM element
    /// </param>
    /// <param name="boxSide" type="Motorwebs.UI.BoxSide">
    /// Side of the element
    /// </param>
    /// <returns type="Number" integer="true">
    /// Border thickness on the element's specified side
    /// </returns>

    if (!element) {
      throw Error.argumentNull('element');
    }
    if (boxSide < Motorwebs.UI.BoxSide.Top || boxSide > Motorwebs.UI.BoxSide.Left) {
      throw Error.argumentOutOfRange(String.format(Sys.Res.enumInvalidValue, boxSide, 'Motorwebs.UI.BoxSide'));
    }
    if (!this.isBorderVisible(element, boxSide)) {
      return 0;
    }
    var styleName = this._borderWidthNames[boxSide];
    var styleValue = this.getCurrentStyle(element, styleName);
    return this.parseBorderWidth(styleValue);
  },

  getPadding: function(element, boxSide) {
    /// <summary>
    /// Gets the padding thickness of an element on a specific boxSide.
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// DOM element
    /// </param>
    /// <param name="boxSide" type="Motorwebs.UI.BoxSide">
    /// Side of the element
    /// </param>
    /// <returns type="Number" integer="true">
    /// Padding on the element's specified side
    /// </returns>

    if (!element) {
      throw Error.argumentNull('element');
    }
    if (boxSide < Motorwebs.UI.BoxSide.Top || boxSide > Motorwebs.UI.BoxSide.Left) {
      throw Error.argumentOutOfRange(String.format(Sys.Res.enumInvalidValue, boxSide, 'Motorwebs.UI.BoxSide'));
    }
    var styleName = this._paddingWidthNames[boxSide];
    var styleValue = this.getCurrentStyle(element, styleName);
    return this.parsePadding(styleValue);
  },

  parseBorderWidth: function(borderWidth) {
    /// <summary>
    /// Parses a border-width string into a pixel size
    /// </summary>
    /// <param name="borderWidth" type="String" mayBeNull="true">
    /// Type of border ('thin','medium','thick','inherit',px unit,null,'')
    /// </param>
    /// <returns type="Number" integer="true">
    /// Number of pixels in the border-width
    /// </returns>
    if (!this._borderThicknesses) {

      // Populate the borderThicknesses lookup table
      var borderThicknesses = {};
      var div0 = document.createElement('div');
      div0.style.visibility = 'hidden';
      div0.style.position = 'absolute';
      div0.style.fontSize = '1px';
      document.body.appendChild(div0)
      var div1 = document.createElement('div');
      div1.style.height = '0px';
      div1.style.overflow = 'hidden';
      div0.appendChild(div1);
      var base = div0.offsetHeight;
      div1.style.borderTop = 'solid black';
      div1.style.borderTopWidth = 'thin';
      borderThicknesses['thin'] = div0.offsetHeight - base;
      div1.style.borderTopWidth = 'medium';
      borderThicknesses['medium'] = div0.offsetHeight - base;
      div1.style.borderTopWidth = 'thick';
      borderThicknesses['thick'] = div0.offsetHeight - base;
      div0.removeChild(div1);
      document.body.removeChild(div0);
      this._borderThicknesses = borderThicknesses;
    }

    if (borderWidth) {
      switch (borderWidth) {
        case 'thin':
        case 'medium':
        case 'thick':
          return this._borderThicknesses[borderWidth];
        case 'inherit':
          return 0;
      }
      var unit = this.parseUnit(borderWidth);
      Sys.Debug.assert(unit.type == 'px', String.format(Motorwebs.UI.Resources.Common_InvalidBorderWidthUnit, unit.type));
      return unit.size;
    }
    return 0;
  },

  parsePadding: function(padding) {
    /// <summary>
    /// Parses a padding string into a pixel size
    /// </summary>
    /// <param name="padding" type="String" mayBeNull="true">
    /// Padding to parse ('inherit',px unit,null,'')
    /// </param>
    /// <returns type="Number" integer="true">
    /// Number of pixels in the padding
    /// </returns>

    if (padding) {
      if (padding == 'inherit') {
        return 0;
      }
      var unit = this.parseUnit(padding);
      Sys.Debug.assert(unit.type == 'px', String.format(Motorwebs.UI.Resources.Common_InvalidPaddingUnit, unit.type));
      return unit.size;
    }
    return 0;
  },

  parseUnit: function(value) {
    /// <summary>
    /// Parses a unit string into a unit object
    /// </summary>
    /// <param name="value" type="String" mayBeNull="true">
    /// Value to parse (of the form px unit,% unit,em unit,...)
    /// </param>
    /// <returns type="Object">
    /// Parsed unit (of the form {size,type})
    /// </returns>

    if (!value) {
      throw Error.argumentNull('value');
    }

    value = value.trim().toLowerCase();
    var l = value.length;
    var s = -1;
    for (var i = 0; i < l; i++) {
      var ch = value.substr(i, 1);
      if ((ch < '0' || ch > '9') && ch != '-' && ch != '.' && ch != ',') {
        break;
      }
      s = i;
    }
    if (s == -1) {
      throw Error.create(Motorwebs.UI.Resources.Common_UnitHasNoDigits);
    }
    var type;
    var size;
    if (s < (l - 1)) {
      type = value.substring(s + 1).trim();
    } else {
      type = 'px';
    }
    size = parseFloat(value.substr(0, s + 1));
    if (type == 'px') {
      size = Math.floor(size);
    }
    return {
      size: size,
      type: type
    };
  },

  getElementOpacity: function(element) {
    /// <summary>
    /// Get the element's opacity
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// Element
    /// </param>
    /// <returns type="Number">
    /// Opacity of the element
    /// </returns>

    if (!element) {
      throw Error.argumentNull('element');
    }

    var hasOpacity = false;
    var opacity;

    if (element.filters) {
      var filters = element.filters;
      if (filters.length !== 0) {
        var alphaFilter = filters['DXImageTransform.Microsoft.Alpha'];
        if (alphaFilter) {
          opacity = alphaFilter.opacity / 100.0;
          hasOpacity = true;
        }
      }
    }
    else {
      opacity = this.getCurrentStyle(element, 'opacity', 1);
      hasOpacity = true;
    }

    if (hasOpacity === false) {
      return 1.0;
    }
    return parseFloat(opacity);
  },

  setElementOpacity: function(element, value) {
    /// <summary>
    /// Set the element's opacity
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// Element
    /// </param>
    /// <param name="value" type="Number">
    /// Opacity of the element
    /// </param>

    if (!element) {
      throw Error.argumentNull('element');
    }

    if (element.filters) {
      var filters = element.filters;
      var createFilter = true;
      if (filters.length !== 0) {
        var alphaFilter = filters['DXImageTransform.Microsoft.Alpha'];
        if (alphaFilter) {
          createFilter = false;
          alphaFilter.opacity = value * 100;
        }
      }
      if (createFilter) {
        element.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(opacity=' + (value * 100) + ')';
      }
    }
    else {
      element.style.opacity = value;
    }
  },

  getVisible: function(element) {
    /// <summary>
    /// Check if an element is visible
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// Element
    /// </param>
    /// <returns type="Boolean" mayBeNull="false">
    /// True if the element is visible, false otherwise
    /// </returns>

    // Note: reference to CommonScripts must be left intact (i.e. don't
    // replace with 'this') because this function will be aliased

    return (element &&
                ("none" != $common.getCurrentStyle(element, "display")) &&
                ("hidden" != $common.getCurrentStyle(element, "visibility")));
  },

  setVisible: function(element, value) {
    /// <summary>
    /// Check if an element is visible
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement" domElement="true">
    /// Element
    /// </param>
    /// <param name="value" type="Boolean" mayBeNull="false">
    /// True to make the element visible, false to hide it
    /// </param>

    // Note: reference to CommonScripts must be left intact (i.e. don't
    // replace with 'this') because this function will be aliased

    if (element && value != $common.getVisible(element)) {
      if (value) {
        if (element.style.removeAttribute) {
          element.style.removeAttribute("display");
        } else {
          element.style.removeProperty("display");
        }
      } else {
        element.style.display = 'none';
      }
      element.style.visibility = value ? 'visible' : 'hidden';
    }
  },

  resolveFunction: function(value) {
    /// <summary>
    /// Returns a function reference that corresponds to the provided value
    /// </summary>
    /// <param name="value" type="Object">
    /// The value can either be a Function, the name of a function (that can be found using window['name']),
    /// or an expression that evaluates to a function.
    /// </param>
    /// <returns type="Function">
    /// Reference to the function, or null if not found
    /// </returns>

    if (value) {
      if (value instanceof Function) {
        return value;
      } else if (String.isInstanceOfType(value) && value.length > 0) {
        var func;
        if ((func = window[value]) instanceof Function) {
          return func;
        } else if ((func = eval(value)) instanceof Function) {
          return func;
        }
      }
    }
    return null;
  },

  addCssClasses: function(element, classNames) {
    /// <summary>
    /// Adds multiple css classes to a DomElement
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement">The element to modify</param>
    /// <param name="classNames" type="Array">The class names to add</param>

    for (var i = 0; i < classNames.length; i++) {
      Sys.UI.DomElement.addCssClass(element, classNames[i]);
    }
  },

  removeCssClasses: function(element, classNames) {
    /// <summary>
    /// Removes multiple css classes to a DomElement
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement">The element to modify</param>
    /// <param name="classNames" type="Array">The class names to remove</param>

    for (var i = 0; i < classNames.length; i++) {
      Sys.UI.DomElement.removeCssClass(element, classNames[i]);
    }
  },

  setStyle: function(element, style) {
    /// <summary>
    /// Sets the style of the element using the supplied style template object
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement">The element to modify</param>
    /// <param name="style" type="Object">The template</param>

    $common.applyProperties(element.style, style);
  },

  removeHandlers: function(element, events) {
    /// <summary>
    /// Removes a set of event handlers from an element
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement">The element to modify</param>
    /// <param name="events" type="Object">The template object that contains event names and delegates</param>
    /// <remarks>
    /// This is NOT the same as $clearHandlers which removes all delegates from a DomElement.  This rather removes select delegates 
    /// from a specified element and has a matching signature as $addHandlers
    /// </remarks>
    for (var name in events) {
      $removeHandler(element, name, events[name]);
    }
  },

  overlaps: function(r1, r2) {
    /// <summary>
    /// Determine if two rectangles overlap
    /// </summary>
    /// <param name="r1" type="Object">
    /// Rectangle
    /// </param>
    /// <param name="r2" type="Object">
    /// Rectangle
    /// </param>
    /// <returns type="Boolean">
    /// True if the rectangles overlap, false otherwise
    /// </returns>

    return r1.x < (r2.x + r2.width)
                && r2.x < (r1.x + r1.width)
                && r1.y < (r2.y + r2.height)
                && r2.y < (r1.y + r1.height);
  },

  containsPoint: function(rect, x, y) {
    /// <summary>
    /// Tests whether a point (x,y) is contained within a rectangle
    /// </summary>
    /// <param name="rect" type="Object">The rectangle</param>
    /// <param name="x" type="Number">The x coordinate of the point</param>
    /// <param name="y" type="Number">The y coordinate of the point</param>

    return x >= rect.x && x < (rect.x + rect.width) && y >= rect.y && y < (rect.y + rect.height);
  },

  isKeyDigit: function(keyCode) {
    /// <summary>
    /// Gets whether the supplied key-code is a digit
    /// </summary>
    /// <param name="keyCode" type="Number" integer="true">The key code of the event (from Sys.UI.DomEvent)</param>
    /// <returns type="Boolean" />

    return (0x30 <= keyCode && keyCode <= 0x39);
  },

  isKeyNavigation: function(keyCode) {
    /// <summary>
    /// Gets whether the supplied key-code is a navigation key
    /// </summary>
    /// <param name="keyCode" type="Number" integer="true">The key code of the event (from Sys.UI.DomEvent)</param>
    /// <returns type="Boolean" />

    return (Sys.UI.Key.left <= keyCode && keyCode <= Sys.UI.Key.down);
  },

  padLeft: function(text, size, ch, truncate) {
    /// <summary>
    /// Pads the left hand side of the supplied text with the specified pad character up to the requested size
    /// </summary>
    /// <param name="text" type="String">The text to pad</param>
    /// <param name="size" type="Number" integer="true" optional="true">The size to pad the text (default is 2)</param>
    /// <param name="ch" type="String" optional="true">The single character to use as the pad character (default is ' ')</param>
    /// <param name="truncate" type="Boolean" optional="true">Whether to truncate the text to size (default is false)</param>

    return $common._pad(text, size || 2, ch || ' ', 'l', truncate || false);
  },

  padRight: function(text, size, ch, truncate) {
    /// <summary>
    /// Pads the right hand side of the supplied text with the specified pad character up to the requested size
    /// </summary>
    /// <param name="text" type="String">The text to pad</param>
    /// <param name="size" type="Number" integer="true" optional="true">The size to pad the text (default is 2)</param>
    /// <param name="ch" type="String" optional="true">The single character to use as the pad character (default is ' ')</param>
    /// <param name="truncate" type="Boolean" optional="true">Whether to truncate the text to size (default is false)</param>

    return $common._pad(text, size || 2, ch || ' ', 'r', truncate || false);
  },

  _pad: function(text, size, ch, side, truncate) {
    /// <summary>
    /// Pads supplied text with the specified pad character up to the requested size
    /// </summary>
    /// <param name="text" type="String">The text to pad</param>
    /// <param name="size" type="Number" integer="true">The size to pad the text</param>
    /// <param name="ch" type="String">The single character to use as the pad character</param>
    /// <param name="side" type="String">Either 'l' or 'r' to siginfy whether to pad the Left or Right side respectively</param>
    /// <param name="truncate" type="Boolean">Whether to truncate the text to size</param>

    text = text.toString();
    var length = text.length;
    var builder = new Sys.StringBuilder();
    if (side == 'r') {
      builder.append(text);
    }
    while (length < size) {
      builder.append(ch);
      length++;
    }
    if (side == 'l') {
      builder.append(text);
    }
    var result = builder.toString();
    if (truncate && result.length > size) {
      if (side == 'l') {
        result = result.substr(result.length - size, size);
      } else {
        result = result.substr(0, size);
      }
    }
    return result;
  },

  __DOMEvents: {
    focusin: { eventGroup: "UIEvents", init: function(e, p) { e.initUIEvent("focusin", true, false, window, 1); } },
    focusout: { eventGroup: "UIEvents", init: function(e, p) { e.initUIEvent("focusout", true, false, window, 1); } },
    activate: { eventGroup: "UIEvents", init: function(e, p) { e.initUIEvent("activate", true, true, window, 1); } },
    focus: { eventGroup: "UIEvents", init: function(e, p) { e.initUIEvent("focus", false, false, window, 1); } },
    blur: { eventGroup: "UIEvents", init: function(e, p) { e.initUIEvent("blur", false, false, window, 1); } },
    click: { eventGroup: "MouseEvents", init: function(e, p) { e.initMouseEvent("click", true, true, window, 1, p.screenX || 0, p.screenY || 0, p.clientX || 0, p.clientY || 0, p.ctrlKey || false, p.altKey || false, p.shiftKey || false, p.metaKey || false, p.button || 0, p.relatedTarget || null); } },
    dblclick: { eventGroup: "MouseEvents", init: function(e, p) { e.initMouseEvent("click", true, true, window, 2, p.screenX || 0, p.screenY || 0, p.clientX || 0, p.clientY || 0, p.ctrlKey || false, p.altKey || false, p.shiftKey || false, p.metaKey || false, p.button || 0, p.relatedTarget || null); } },
    mousedown: { eventGroup: "MouseEvents", init: function(e, p) { e.initMouseEvent("mousedown", true, true, window, 1, p.screenX || 0, p.screenY || 0, p.clientX || 0, p.clientY || 0, p.ctrlKey || false, p.altKey || false, p.shiftKey || false, p.metaKey || false, p.button || 0, p.relatedTarget || null); } },
    mouseup: { eventGroup: "MouseEvents", init: function(e, p) { e.initMouseEvent("mouseup", true, true, window, 1, p.screenX || 0, p.screenY || 0, p.clientX || 0, p.clientY || 0, p.ctrlKey || false, p.altKey || false, p.shiftKey || false, p.metaKey || false, p.button || 0, p.relatedTarget || null); } },
    mouseover: { eventGroup: "MouseEvents", init: function(e, p) { e.initMouseEvent("mouseover", true, true, window, 1, p.screenX || 0, p.screenY || 0, p.clientX || 0, p.clientY || 0, p.ctrlKey || false, p.altKey || false, p.shiftKey || false, p.metaKey || false, p.button || 0, p.relatedTarget || null); } },
    mousemove: { eventGroup: "MouseEvents", init: function(e, p) { e.initMouseEvent("mousemove", true, true, window, 1, p.screenX || 0, p.screenY || 0, p.clientX || 0, p.clientY || 0, p.ctrlKey || false, p.altKey || false, p.shiftKey || false, p.metaKey || false, p.button || 0, p.relatedTarget || null); } },
    mouseout: { eventGroup: "MouseEvents", init: function(e, p) { e.initMouseEvent("mousemove", true, true, window, 1, p.screenX || 0, p.screenY || 0, p.clientX || 0, p.clientY || 0, p.ctrlKey || false, p.altKey || false, p.shiftKey || false, p.metaKey || false, p.button || 0, p.relatedTarget || null); } },
    load: { eventGroup: "HTMLEvents", init: function(e, p) { e.initEvent("load", false, false); } },
    unload: { eventGroup: "HTMLEvents", init: function(e, p) { e.initEvent("unload", false, false); } },
    select: { eventGroup: "HTMLEvents", init: function(e, p) { e.initEvent("select", true, false); } },
    change: { eventGroup: "HTMLEvents", init: function(e, p) { e.initEvent("change", true, false); } },
    submit: { eventGroup: "HTMLEvents", init: function(e, p) { e.initEvent("submit", true, true); } },
    reset: { eventGroup: "HTMLEvents", init: function(e, p) { e.initEvent("reset", true, false); } },
    resize: { eventGroup: "HTMLEvents", init: function(e, p) { e.initEvent("resize", true, false); } },
    scroll: { eventGroup: "HTMLEvents", init: function(e, p) { e.initEvent("scroll", true, false); } }
  },

  tryFireRawEvent: function(element, rawEvent) {
    /// <summary>
    /// Attempts to fire a raw DOM event on an element
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement">The element to fire the event</param>
    /// <param name="rawEvent" type="Object">The raw DOM event object to fire. Must not be Sys.UI.DomEvent</param>
    /// <returns type="Boolean">True if the event was successfully fired, otherwise false</returns>

    try {
      if (element.fireEvent) {
        element.fireEvent("on" + rawEvent.type, rawEvent);
        return true;
      } else if (element.dispatchEvent) {
        element.dispatchEvent(rawEvent);
        return true;
      }
    } catch (e) {
    }
    return false;
  },

  tryFireEvent: function(element, eventName, properties) {
    /// <summary>
    /// Attempts to fire a DOM event on an element
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement">The element to fire the event</param>
    /// <param name="eventName" type="String">The name of the event to fire (without an 'on' prefix)</param>
    /// <param name="properties" type="Object">Properties to add to the event</param>
    /// <returns type="Boolean">True if the event was successfully fired, otherwise false</returns>

    try {
      if (document.createEventObject) {
        var e = document.createEventObject();
        $common.applyProperties(e, properties || {});
        element.fireEvent("on" + eventName, e);
        return true;
      } else if (document.createEvent) {
        var def = $common.__DOMEvents[eventName];
        if (def) {
          var e = document.createEvent(def.eventGroup);
          def.init(e, properties || {});
          element.dispatchEvent(e);
          return true;
        }
      }
    } catch (e) {
    }
    return false;
  },

  wrapElement: function(innerElement, newOuterElement, newInnerParentElement) {
    /// <summary>
    /// Wraps an inner element with a new outer element at the same DOM location as the inner element
    /// </summary>
    /// <param name="innerElement" type="Sys.UI.DomElement">The element to be wrapped</param>
    /// <param name="newOuterElement" type="Sys.UI.DomElement">The new parent for the element</param>
    /// <returns />

    var parent = innerElement.parentNode;
    parent.replaceChild(newOuterElement, innerElement);
    (newInnerParentElement || newOuterElement).appendChild(innerElement);
  },

  unwrapElement: function(innerElement, oldOuterElement) {
    /// <summary>
    /// Unwraps an inner element from an outer element at the same DOM location as the outer element
    /// </summary>
    /// <param name="innerElement" type="Sys.UI.DomElement">The element to be wrapped</param>
    /// <param name="newOuterElement" type="Sys.UI.DomElement">The new parent for the element</param>
    /// <returns />

    var parent = oldOuterElement.parentNode;
    if (parent != null) {
      $common.removeElement(innerElement);
      parent.replaceChild(innerElement, oldOuterElement);
    }
  },

  removeElement: function(element) {
    /// <summary>
    /// Removes an element from the DOM tree
    /// </summary>
    /// <param name="element" type="Sys.UI.DomElement">The element to be removed</param>
    /// <returns />

    var parent = element.parentNode;
    if (parent != null) {
      parent.removeChild(element);
    }
  },

  applyProperties: function(target, properties) {
    /// <summary>
    /// Quick utility method to copy properties from a template object to a target object
    /// </summary>
    /// <param name="target" type="Object">The object to apply to</param>
    /// <param name="properties" type="Object">The template to copy values from</param>

    for (var p in properties) {
      var pv = properties[p];
      if (pv != null && Object.getType(pv) === Object) {
        var tv = target[p];
        $common.applyProperties(tv, pv);
      } else {
        target[p] = pv;
      }
    }
  },

  parseNumber: function(num) {
    num = num.toString().replace(/\$/, '').replace(/\%/, '').replace(/,/, '');
    return isNaN(parseFloat(num)) ? 0 : parseFloat(num);
  },

  isNotEmpty: function(el) {
    var str = el.value;
    var re = /.+/;
    if (!str.match(re) || ((el.title + ':') == el.value)) {
      alert("Please fill in the required " + el.title + " field.");
      setTimeout("$common.focusElement('" + el.id + "')", 0);
      return false;
    } else {
      return true;
    }
  },

  isChosen: function(select) {
    if (select.selectedIndex == 0) {
      alert("Please make a choice from the required " + select.name + " dropdown.");
      select.focus();
      return false;
    } else {
      return true;
    }
  },

  isEmailAddr: function(el) {
    var str = el.value;
    var re = /^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/;
    if (!str.match(re)) {
      alert("Not a valid email address! \nPlease verify the e-mail address format.");
      setTimeout("$common.focusElement('" + el.id + "')", 0);
      return false;
    } else {
      return true;
    }
  },

  focusElement: function(elID) {
    var el = $get(elID);
    el.focus();
    el.select();
  },

  fillSelectOneColumn: function(select, responseText, label, selectedValue) {
    select.options.length = 0;
    select.options[0] = new Option(label, "", true, true);
    var response = responseText.split("||");
    for (var i = 0; i < response.length; i++) {
      if (selectedValue == response[i])
        select.options[select.options.length] = new Option(response[i], response[i], true, true);
      else
        select.options[select.options.length] = new Option(response[i], response[i], false, false);     
    }
    select.disabled = false;
  },

  fillSelectTwoColumn: function(select, responseText, label, selectedValue) {
    select.options.length = 0;
    select.options[0] = new Option(label, "", true, true);
    var response = responseText.split("||");
    for (var i = 0; i < response.length; i++) {
      var style = response[i].split(";;");
      if (selectedValue == style[0])
        select.options[select.options.length] = new Option(style[1], style[0], true, true);
      else
        select.options[select.options.length] = new Option(style[1], style[0], false, false);
    }
    select.disabled = false;
  },

  clearSelect: function(select, label) {
    if (select.options != null) {
      select.options.length = 0;
      select.options[0] = new Option(label, "", true, true);
    }
    select.disabled = true;
  },

  createHistoryObj: function(hash) {
    hash = hash.substring(1, hash.length)
    var urlArr = hash.split('&');

    var s = "{";
    for (var i = 0; i < urlArr.length; i++) {
      prmArr = urlArr[i].split('=');
      if (i > 0) s += ", ";
      s += prmArr[0] + ":\"" + unescape(prmArr[1]) + "\""
    }
    s += "}";

    var ho = eval('(' + s + ')');

    return ho;
  }
}

var $common = Motorwebs.UI.Common = new Motorwebs.UI._Common();