// SpryHTMLPanel.js - version 0.4 - Spry Pre-Release 1.6

//

// 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);

		}

	}

};

