// SpryHTMLPanel.js - version 0.4 - Spry Pre-Release 1.6.1
//
// Copyright (c) 2006. Adobe Systems Incorporated.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of Adobe Systems Incorporated nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

var Spry; if (!Spry) Spry = {}; if (!Spry.Widget) Spry.Widget = {};

Spry.Widget.HTMLPanel = function(ele, opts)
{
  Spry.Widget.HTMLPanel.Notifier.call(this);

  this.element = Spry.Widget.HTMLPanel.$(ele);

  // evalScripts controls whether or not we execute any script that is within
  // an HTML fragment we load into the panel's container. The default value for
  // this comes from our global flag, but users can override this setting for
  // a specific HTMLPanel instance with an evalScripts constructor option.

  this.evalScripts = Spry.Widget.HTMLPanel.evalScripts;

  // These class names are used to identify content *inside* the panel's container
  // when the panel is first created. If the HTMLPanel finds any elements
  // with these class names, it will remove the elements from the document
  // and tuck away their content. The HTMLPanel will then inject this content
  // back into the its container at the appropriate time.
  //
  // This gives the designer an option for specifying content they want shown
  // when the HTMLPanel is loading content or has encountered an error.

  this.loadingContentClass = "HTMLPanelLoadingContent";
  this.errorContentClass = "HTMLPanelErrorContent";

  this.loadingStateContent = "";
  this.errorStateContent = "";

  // These class names are placed on the panel's container whenever the HTMLPanel
  // loads content, or has encountered an error. This is an alternative to specifying
  // content to use during loading and error states. Instead, the designer would simply
  // define CSS rules that use these class names to alter the appearance of the panel's
  // container.

  this.loadingStateClass = "HTMLPanelLoading";
  this.errorStateClass = "HTMLPanelError";

  // The current request that is pending completion.

  this.pendingRequest = null;

  Spry.Widget.HTMLPanel.setOptions(this, opts);

  // Find any content within the panel's container that is supposed to be
  // used for the loading and error states.

  var elements = this.element.getElementsByTagName("*");
  var numElements = elements.length;

  var errorEle = null;
  var loadingEle = null;

  var d = document.createElement("div");

  for (var i = 0; i < numElements && (!loadingEle || !errorEle); i++)
  {
    var e = elements[i];
    if (Spry.Widget.HTMLPanel.hasClassName(e, this.loadingContentClass))
      loadingEle = e;
    if (Spry.Widget.HTMLPanel.hasClassName(e, this.errorContentClass))
      errorEle = e;
  }

  if (loadingEle)
    this.loadingStateContent = Spry.Widget.HTMLPanel.removeAndExtractContent(loadingEle, this.loadingContentClass);
  if (errorEle)
    this.errorStateContent = Spry.Widget.HTMLPanel.removeAndExtractContent(errorEle, this.errorContentClass);
};

// Global switch that decides whether or not HTMLPanels execute
// script embedded within HTML fragments, after the fragment is inserted
// into the DOM. If false, no HTMLPanel will execute any script embedded
// within an HTML fragment.

Spry.Widget.HTMLPanel.evalScripts = false;

Spry.Widget.HTMLPanel.Notifier = function()
{
  this.observers = [];
  this.suppressNotifications = 0;
};

Spry.Widget.HTMLPanel.Notifier.prototype.addObserver = function(observer)
{
  if (!observer)
    return;

  // Make sure the observer isn't already on the list.

  var len = this.observers.length;
  for (var i = 0; i < len; i++)
  {
    if (this.observers[i] == observer)
      return;
  }
  this.observers[len] = observer;
};

Spry.Widget.HTMLPanel.Notifier.prototype.removeObserver = function(observer)
{
  if (!observer)
    return;

  for (var i = 0; i < this.observers.length; i++)
  {
    if (this.observers[i] == observer)
    {
      this.observers.splice(i, 1);
      break;
    }
  }
};

Spry.Widget.HTMLPanel.Notifier.prototype.notifyObservers = function(methodName, data)
{
  if (!methodName)
    return;

  if (!this.suppressNotifications)
  {
    var len = this.observers.length;
    for (var i = 0; i < len; i++)
    {
      var obs = this.observers[i];
      if (obs)
      {
        if (typeof obs == "function")
          obs(methodName, this, data);
        else if (obs[methodName])
          obs[methodName](this, data);
      }
    }
  }
};

Spry.Widget.HTMLPanel.Notifier.prototype.enableNotifications = function()
{
  if (--this.suppressNotifications < 0)
  {
    this.suppressNotifications = 0;
    Spry.Debug.reportError("Unbalanced enableNotifications() call!\n");
  }
};

Spry.Widget.HTMLPanel.Notifier.prototype.disableNotifications = function()
{
  ++this.suppressNotifications;
};

Spry.Widget.HTMLPanel.prototype = new Spry.Widget.HTMLPanel.Notifier();
Spry.Widget.HTMLPanel.prototype.constructor = Spry.Widget.HTMLPanel;

Spry.Widget.HTMLPanel.$ = function(ele)
{
  if (ele && typeof ele == "string")
    return document.getElementById(ele);
  return ele;
};

Spry.Widget.HTMLPanel.setOptions = function(dstObj, srcObj, ignoreUndefinedProps)
{
  if (srcObj)
  {
    for (var optionName in srcObj)
    {
      if (ignoreUndefinedProps && srcObj[optionName] == undefined)
        continue;
      dstObj[optionName] = srcObj[optionName];
    }
  }
};

Spry.Widget.HTMLPanel.addClassName = function(ele, className)
{
  ele = Spry.Widget.HTMLPanel.$(ele);
  if (!ele || !className || (ele.className && ele.className.search(new RegExp("\\b" + className + "\\b")) != -1))
    return;
  ele.className += (ele.className ? " " : "") + className;
};

Spry.Widget.HTMLPanel.removeClassName = function(ele, className)
{
  ele = Spry.Widget.HTMLPanel.$(ele);
  if (Spry.Widget.HTMLPanel.hasClassName(ele, className))
    ele.className = ele.className.replace(new RegExp("\\s*\\b" + className + "\\b", "g"), "");
};

Spry.Widget.HTMLPanel.hasClassName = function(ele, className)
{
  ele = Spry.Widget.HTMLPanel.$(ele);
  if (!ele || !className || !ele.className || ele.className.search(new RegExp("\\b" + className + "\\b")) == -1)
    return false;
  return true;
};

Spry.Widget.HTMLPanel.removeAndExtractContent = function(ele, className)
{
  var d = document.createElement("div");
  if (ele)
  {
    d.appendChild(ele);
    if (className)
      Spry.Widget.HTMLPanel.removeClassName(ele, className);
  }
  return d.innerHTML;
};

Spry.Widget.HTMLPanel.findNodeById = function(id, node)
{
  if (node && node.nodeType == 1 /* NODE.ELEMENT_NODE */)
  {
    if (node.id == id)
      return node;
    var child = node.firstChild;
    while (child)
    {
      var result = Spry.Widget.HTMLPanel.findNodeById(id, child);
      if (result)
        return result;
      child = child.nextSibling;
    }
  }
  return null;
};

Spry.Widget.HTMLPanel.disableSrcReferences = function (source)
{
  if (source)
    source = source.replace(/<(img|script|link|frame|iframe|input)([^>]+)>/gi, function(a,b,c) {
        // b=tag name, c=tag attributes
        return '<' + b + c.replace(/\b(src|href)\s*=/gi, function(a, b) {
          // b=attribute name
          return 'spry_'+ b + '=';
        }) + '>';
      });
  return source;
};

Spry.Widget.HTMLPanel.enableSrcReferences = function (source)
{
  source = source.replace(/<(img|script|link|frame|iframe|input)([^>]+)>/gi, function(a,b,c) {
      // b=tag name, c=tag attributes
      return '<' + b + c.replace(/\bspry_(src|href)\s*=/gi, function(a, b) {
        // b=attribute name
        return b + '=';
      }) + '>';
    });
  return source;
};

Spry.Widget.HTMLPanel.getFragByID = function(id, contentStr)
{
  var frag = Spry.Widget.HTMLPanel.disableSrcReferences(contentStr);
  var div = document.createElement("div");
  div.innerHTML = frag;

  frag = "";
  var node = Spry.Widget.HTMLPanel.findNodeById(id, div);
  if (node)
    frag = node.innerHTML;

  return Spry.Widget.HTMLPanel.enableSrcReferences(frag);
};

Spry.Widget.HTMLPanel.prototype.setContent = function(contentStr, id)
{
  var data = { content: contentStr, id: id };
  this.notifyObservers("onPreUpdate", data);

  // Observers are allowed to modify the data. Make sure
  // the fragment and id we use are from the data that was
  // past to our observers.

  contentStr = data.content;
  id = data.id;

  // If we have a valid id, extract the markup underneath
  // the element with that id from our html fragment.

  if (typeof id != "undefined")
    contentStr = Spry.Widget.HTMLPanel.getFragByID(id, contentStr);

  // Slam the html fragment into the DOM.

  Spry.Widget.HTMLPanel.setInnerHTML(this.element, contentStr, !this.evalScripts);

  this.removeStateClasses();

  this.notifyObservers("onPostUpdate", data);
};

Spry.Widget.HTMLPanel.prototype.loadContent = function(url, opts)
{
  if (!this.element)
    return;

  this.cancelLoad();

  if (!opts)
    opts = new Object;

  opts.url  = opts.url ? opts.url : url;
  opts.method = opts.method ? opts.method : "GET";
  opts.async  = opts.async ? opts.async : true;
  opts.id  = opts.id ? opts.id : undefined;

  var self = this;
  opts.errorCallback = function(req) { self.onLoadError(req); };

  this.notifyObservers("onPreLoad", opts);

  if (this.loadingStateContent)
    this.setContent(this.loadingStateContent);

  Spry.Widget.HTMLPanel.addClassName(this.element, this.loadingStateClass);
  this.pendingRequest = Spry.Widget.HTMLPanel.loadURL(opts.method, opts.url, opts.async, function(req){ self.onLoadSuccessful(req); }, opts);
};

Spry.Widget.HTMLPanel.prototype.cancelLoad = function()
{
  try
  {
    if (this.pendingRequest && this.pendingRequest.xhRequest)
    {
      var xhr = this.pendingRequest.xhRequest;
      if (xhr.abort)
        xhr.abort();
      xhr.onreadystatechange = null;
      this.notifyObservers("onLoadCancelled", this.pendingRequest);
    }
  }
  catch(e) {}
  this.pendingRequest = null;
};

Spry.Widget.HTMLPanel.prototype.removeStateClasses = function()
{
  Spry.Widget.HTMLPanel.removeClassName(this.element, this.loadingStateClass);
  Spry.Widget.HTMLPanel.removeClassName(this.element, this.errorStateClass);
};

Spry.Widget.HTMLPanel.prototype.onLoadSuccessful = function(req)
{
  this.notifyObservers("onPostLoad", req);
  this.setContent(req.xhRequest.responseText, req.id);
  this.pendingRequest = null;
};

Spry.Widget.HTMLPanel.prototype.onLoadError = function(req)
{
  this.notifyObservers("onLoadError", req);
  if (this.errorStateContent)
    this.setContent(this.errorStateContent);
  Spry.Widget.HTMLPanel.addClassName(this.element, this.errorStateClass);
  this.pendingRequest = null;
};

Spry.Widget.HTMLPanel.msProgIDs = ["MSXML2.XMLHTTP.6.0", "MSXML2.XMLHTTP.3.0"];

Spry.Widget.HTMLPanel.createXMLHttpRequest = function()
{
  var req = null;
  if (window.ActiveXObject)
  {
    while (!req && Spry.Widget.HTMLPanel.msProgIDs.length)
    {
      try { req = new ActiveXObject(Spry.Widget.HTMLPanel.msProgIDs[0]); } catch (e) { req = null; }
      if (!req)
        Spry.Widget.HTMLPanel.msProgIDs.splice(0, 1);
    }
  }
  if (!req && window.XMLHttpRequest) { try { req = new XMLHttpRequest(); } catch (e) { req = null; } }
  return req;
};

Spry.Widget.HTMLPanel.loadURL = function(method, url, async, callback, opts)
{
  var req = new Object;
  req.method = method;
  req.url = url;
  req.async = async;
  req.successCallback = callback;

  Spry.Widget.HTMLPanel.setOptions(req, opts);

  try
  {
    req.xhRequest = Spry.Widget.HTMLPanel.createXMLHttpRequest();
    if (!req.xhRequest)
      return null;

    if (req.async)
      req.xhRequest.onreadystatechange = function() { Spry.Widget.HTMLPanel.loadURL.callback(req); };

    req.xhRequest.open(method, req.url, req.async, req.username, req.password);

    if (req.headers)
    {
      for (var name in req.headers)
        req.xhRequest.setRequestHeader(name, req.headers[name]);
    }

    req.xhRequest.send(req.postData);

    if (!req.async)
      Spry.Widget.HTMLPanel.loadURL.callback(req);
  }
  catch(e) { if (req.errorCallback) req.errorCallback(req); req = null; }

  return req;
};

Spry.Widget.HTMLPanel.loadURL.callback = function(req)
{
  if (!req || req.xhRequest.readyState != 4)
    return;
  if (req.successCallback && (req.xhRequest.status == 200 || req.xhRequest.status == 0))
    req.successCallback(req);
  else if (req.errorCallback)
    req.errorCallback(req);
};

Spry.Widget.HTMLPanel.eval = function(str) { return eval(str); };

Spry.Widget.HTMLPanel.setInnerHTML = function(ele, str, preventScripts)
{
  if (!ele)
    return;
  if (!str) str = "";
  ele = Spry.Widget.HTMLPanel.$(ele);
  var scriptExpr = "<script[^>]*>(.|\s|\n|\r)*?</script>";
  ele.innerHTML = str.replace(new RegExp(scriptExpr, "img"), "");

  if (preventScripts)
    return;

  var matches = str.match(new RegExp(scriptExpr, "img"));
  if (matches)
  {
    var numMatches = matches.length;
    for (var i = 0; i < numMatches; i++)
    {
      var s = matches[i].replace(/<script[^>]*>[\s\r\n]*(<\!--)?|(-->)?[\s\r\n]*<\/script>/img, "");
      Spry.Widget.HTMLPanel.eval(s);
    }
  }
};
