/*
*************************************************************************
"This Trailfire(tm) extension is protected by Copyright laws of the
United States and International Conventions.  (c) 2006 Trailfire, Inc.
All rights reserved.  You are permitted to use this extension solely in
connection with your activities as a registered user of the
Trailfire(sm) service.  Without limitation, you are not licensed to and
you agree not to develop, reverse engineer, modify, create derivative
works or take any actions with respect to the Trailfire extension source
code or program other than as permitted in connection with the Trailfire
service.  By accessing this directory and/or extension code, you agree
to these terms and conditions, and to the terms of the Trailfire End
User License Agreement available at www.trailfire.com.  If you do not
agree to these terms and conditions, you may not access this directory
or any Trailfire code or use the Trailfire extension, and you must
immediately and permanently delete all copies in your possession or
control.
*************************************************************************
*/

var PIPESTONE = "http://trailfire.com/";
var PIPESTONE_VERSION = "12492";
    


// /var/www/trailfire.com-release/content/client/lib/assert.js 

/**
 *  Logger singleton class for output debug, errors, and alerts 
 */
var Log = {

	uniqueId : (new Date()).getTime() + "" + Math.floor(Math.random()*10000),

	silent	: false, // disable all logging

	memory	: false,  // check memory leaks

	ajax  	: false,  // log ajax data (turns on/off all ajax* variables)
	ajaxRequests: false, //log ajax requests
	ajaxData: false, // log ajax request data
	ajaxFailures: false,  // log ajax failures

	trace 	: false,  // log trace information (turns on/off all trace* variables)
	traceWait: false, // log a trace of wait calls
	traceModel: false, // trace model firing
	traceClass: false, // trace class calls
	traceParam: false, // trace parameters and return values of public function calls

	depth    : 0,

	debug    : function() { },
	error    : function() { },
	alert    : function() { },
	profile  : function() { },	
	assert	 : function() { },
	halt	 : function() { }	
}

// /var/www/trailfire.com-release/content/client/lib/utils.js 


//this is a dummy profiler class (so that we can keep the profiler
// calls in the code, but only include profiler.js when 
// we want to record the data
var profiler = new function()
{
	this.start = function() {};
	this.stop = function() {};
	this.record = function() {};
	this.push = function() {};
	this.pop = function() {};
}

var waitCallBackoff = 1;
var waitCallStreak = 0;
var waitCallQueue = new Object();

function wait(func,waitTime,profileCategory,trace)
{
	var callId = null;
	var callTime = new Date();

	if (typeof(profileCategory) == "undefined")
		profileCategory = "other";

	if (typeof(trace) == "undefined")
		trace = true;

	function checkLogDepth()
	{
		if (Log.depth > 0)
		{
			if (Log && Log.trace && Log.traceWait && trace)
			{
				Log.debug("*ERROR* Silent exception detected @"+Log.depth);
				Log.halt();
			}
			Log.depth =0;
		}
	}

	function wait_wrapper()
	{
		var start = new Date();

		profiler.push(profileCategory);

		checkLogDepth();
		try
		{
			if ( waitCallQueue[callId] && waitCallQueue[callId] instanceof Array )
			{
				if (Log && Log.trace && Log.traceWait && trace)
				{
					Log.debug("wait ("+profileCategory+")")
					Log.debug("{");
					Log.depth++;
				}

				if ( typeof(func) === "function" )
				{
					func();
				} else {
					eval(func);
				}
			}
		} catch (ex) {
			if (Log && Log.trace && Log.traceWait && trace)
			{
				if (confirm("Debug? "+ex.message+"\n"+snippet))
				{
					debugger;
					func();
				}
			}

			// display a little code
			var snippet = Utils.functionToString(func);
			Log.debug("Exception caught in wait("+snippet+")");
			Log.error(ex);
		} finally {
			delete waitCallQueue[callId];
		}
		
		if (Log && Log.trace && Log.traceWait && trace)
		{
			Log.depth--;
			Log.debug("} //wait ("+profileCategory+")")
		}

		checkLogDepth();

		//profiling
		profiler.record(profileCategory ,(new Date()).getTime() - start.getTime());
		profiler.pop();
	}

	callId = setTimeout(wait_wrapper,waitTime);
	waitCallQueue[callId] = [wait_wrapper,callTime.getTime()+waitTime];
	return callId;
}

// calling setTimeout clears the calling stack, so exceptions bubble up to the browser
//  and are displayed to the user - this function is a wrapper that will prevent that
//  called the same as setTimeout, it is a drop in replacement
// Uncomment profiling code to identify chunks of the software that take too much time
//  without yielding to the browser.
function kickStartWaitQueue(sync)
{
	var now = (new Date()).getTime();

	for (var id in waitCallQueue)
	{
		if ( waitCallQueue[id] && waitCallQueue[id] instanceof Array )
		{
			var func = function()
			{
				if ( waitCallQueue[this.id] && waitCallQueue[this.id] instanceof Array )
				{
					if (now > waitCallQueue[this.id][1])
					{
						waitCallQueue[this.id][0]() // calls function wrapper() from wait() below 
					}
					else
					{
						setTimeout(waitCallQueue[this.id][0],waitCallQueue[this.id][1]-now);
					}
				}
			}
			func.id = id;

			if (sync)
				bind(func,func)()
			else
				setTimeout(bind(func,func),1);
		}
	}
}

function clearWaitQueue()
{
	for (var id in waitCallQueue)
	{
		clearWait(id);
	}
}

function clearWait(callId) 
{	
	try{
		clearTimeout(callId);
	} catch (e) {
		// not an error... callid might be already called.
	}finally {
		delete waitCallQueue[callId];				
	}
}

function bind(func, self) {
    var im_func = null;

	im_func = func.im_func || func;

    func = function() 
    {
        return func.im_func.apply(func.im_self, arguments);
    }

    func.im_func = im_func;
    func.im_self = self;

	return func;
}

var Utils = {
        
    arrayToString: function (arr,sep,keys,recurse) {
        if (typeof(sep)=="undefined") { sep = ",\n"; }
        if (typeof(keys)=="undefined") { keys = true; }
        if (typeof(recurse)=="undefined") { recurse = true; }

        var outStr = "";

		function append(value)
		{
        	if (outStr) 
        		outStr += sep;   
        		         		
			if (keys)
                outStr += key + ": ";

			try
			{
	            if (value instanceof Array)
	            {
					if (recurse)
	                    outStr+= Utils.arrayToString(value,sep,keys,recurse);
	                else
	                	outStr+= "["+arr[key.length]+"]";
	            } else if (typeof(value) == "string")
	                outStr+= '"'+value+'"';
	            else if (typeof(value) == "boolean")
	                outStr+= '"'+value?'true':'false'+'"';
	            else if (typeof(value) == "object") {
	                if (typeof(value.toString) == "function")
	                    outStr+= '['+value.toString()+']';
	                if (typeof(value.document) == "object")
	                    outStr+= '[DOMElement]';
					else if (recurse)
	                    outStr+= Utils.arrayToString(value,sep,keys,recurse);
	                else
	                	outStr+= "[object]";
	            } else if (typeof(value) == "number")
	            	outStr+= value;
	            else
	                outStr+= '<'+typeof(value)+'>';
			} catch(ex) { outStr +='<...>'; }
        }

		if (typeof(arr.length) == "number")
		{
			for (var key = 0; key < arr.length; key++)
				append(arr[key]);
		} else {
	        for (var key in arr) 
				append(arr[key]);			
		}

		if (recurse)
	        return "{"+outStr+"}";
	    else
	    	return outStr;
    },
    
    functionToString: function(func) {
		var snippet = "";
		if (func.im_func) //bound function
		{
			if (func.im_func.t && func.im_func.t.queue) //fired function
			{
				snippet = func.im_func.t.queue[func.im_func.index].toString();
			} else {
				snippet = func.im_func.toString();
			}
		} else {
			snippet = func.toString();				
		}
		return snippet;
    },
    
    errorToString: function(error)
    {
		var errorMsg ="Unknown error.";
		
		if (error.response) //response from the server
		{
			errorMsg = error.response;
		} else if (error.message)
		{
			errorMsg = error.message;
			if (errorMsg.indexOf("timeout") > 0)
				errorMsg += ".  The server may be unreachable.";
			else if (error.line && error.file)
				{
					errorMsg += "\nLine :"+error.line;
					errorMsg += "\fFile :"+error.file;
				}

		} else if (error.rpc && error.rpc.why) 
			errorMsg = error.rpc.why;
		return errorMsg;
    },
        
	camelize: function(str) {
		var oStringList = str.split('-');
		if (oStringList.length === 1) return oStringList[0];
		
		var camelizedString = str.indexOf('-') === 0
		  ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
		  : oStringList[0];
		
		for (var i = 1, len = oStringList.length; i < len; i++) {
		  var s = oStringList[i];
		  camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
		}
		
		return camelizedString;
	},
	
	trim:function(aStr)
	{
		return aStr.replace( /(^\s*)|(\s*$)/g, '' ) ;
	},
	
	assert : function (condition, getError) { return Log.assert(condition, getError); }
}

Utils.newArray = function()
{
	//since so many sites use prototype overloading, we
	// need to be able to create arrays without any of that
	var arr = [];

	for (var i in arr)
	{
		delete arr[i];
		delete Array.prototype[i];
	}

	return arr;
}

Utils.isChildOf = function(child,parentElem)
{
	if (child 		=== null || child 		=== undefined || 
		parentElem  === null || parentElem  === undefined)
		return false;

	var nodeName = child.nodeName;
	for (var i = 0; (elem = parentElem.getElementsByTagName(nodeName)[i]); i++)
	{
		if (elem === child)
			return true;
	}
	return false;
}

/* getBodyCoordinate - travel up the DOM to find the absolute position of an element
 *
 * @elem: the html element
 * 
 * @returns : the absolute position on the page [x,y]
 */

Utils.getBodyCoordinates = function Utils_getBodyCoordinates(element)
{
    var x = 0, y = 0;
    
    if (element.nodeName === 'BODY' || element.nodeName === 'HTML') {
        return [ 0, 0 ];
    }

	Controls.blockWindowResize = true;

    var node = element;
    while (node != null && node.nodeName != 'BODY') {
        x += node.offsetLeft;
        y += node.offsetTop;
        node = node.offsetParent;
    }

	Controls.blockWindowResize = false;
    return [x, y];  
}

Utils.getOffsetCoordinates = function(element) {
    return [element.offsetLeft, element.offsetTop];
}

Utils.getWindowSize = function(win) 
{
	Controls.blockWindowResize = true;
	var myWidth = 0, myHeight = 0;
	var Document = win.document;
	if( typeof( win.innerWidth ) === 'number' ) {
	//Non-IE
		myWidth = win.innerWidth;
		myHeight = win.innerHeight;
	} else if( Document.documentElement &&
	      ( Document.documentElement.clientWidth || Document.documentElement.clientHeight ) ) {
		//IE 6+ in 'standards compliant mode'
		myWidth = Document.documentElement.clientWidth;
		myHeight = Document.documentElement.clientHeight;
	} else if( Document.body && ( Document.body.clientWidth || Document.body.clientHeight ) ) {
		//IE 4 compatible
		myWidth = Document.body.clientWidth;
		myHeight = Document.body.clientHeight;
	}
	Controls.blockWindowResize = false;
	return [myWidth, myHeight];
}

/* getBrowserWindowSize - find the size of the window using the document element, 
		rather than the window (for use with classes that do not have references to the window)
 */
Utils.getBrowserWindowSize = function(doc) 
{
	Controls.blockWindowResize = true;
	var x=0;
	var y=0;

	if (doc)
	{
		if (Controls.detectIE())
		{
			if (doc.documentElement 
				&& doc.documentElement.clientHeight )
			{
				x = doc.documentElement.clientWidth;
				y = doc.documentElement.clientHeight;
			} else if ( doc.documentElement 
						&& doc.documentElement.innerWidth )
			{
				x = doc.documentElement.innerWidth;
				y = doc.documentElement.innerHeight;
			} else {	
				if (doc.body.clientWidth)
				{
					x=doc.body.clientWidth;
					y=doc.body.clientHeight;
				} else {
					x=doc.body.innerWidth;
					y=doc.body.innerHeight;
				} 
			}
		} else {
			if (
				doc.documentElement 
				&& doc.compatMode != "BackCompat"
				&& doc.documentElement.clientHeight )
			{
				x=doc.documentElement.clientWidth;
				y=doc.documentElement.clientHeight;
			} else if (doc.body.innerWidth)
			{
				x=doc.body.innerWidth;
				y=doc.body.innerHeight;
			} else {
				x=doc.body.clientWidth;
				y=doc.body.clientHeight;
			} 
		}
	}
	
	Controls.blockWindowResize = false;
	return [x,y];
}

Utils.getBodyOffset = function(w)
{
	var body = w.document.body;
	if (body.offsetLeft)
		return [body.offsetLeft, body.offsetTop];	
		
	var ws = Utils.getWindowSize(w);
	
	if (body.scrollWidth < ws[0])
	{
		return [Math.round((ws[0] - body.scrollWidth) /2) , Math.round((ws[1] - body.scrollHeight) /2)];
	}
	
	return [0,0];
}

//resolve an event to document coordinates
Utils.eventToBodyCoordinates = function(evt) {
    var e = {x:0,y:0};
    if (evt.pageX) 
    {
        e.x=evt.pageX;
        e.y=evt.pageY;
    }
    else if (evt.clientX)
    {
	   var doc=evt.target.ownerDocument;
       var scrollOffsets = Utils.scrollOffsets(doc);       
       e.x = evt.clientX + (scrollOffsets[0]);
       e.y = evt.clientY + (scrollOffsets[1]);
    } else {
		Log.debug("event not found");
    }
    return e;
}

Utils.readCookie = function(doc,cookieName)
{
    var nameEQ = cookieName + "=";
    if (!doc.cookie) { return null; }
    var cookieArray = doc.cookie.split(';');
    for(var i=0;i < cookieArray.length;i++)
    {
        var cookie = cookieArray[i];
        while (cookie.charAt(0)===' ') cookie = cookie.substring(1,cookie.length);
        if (cookie.indexOf(nameEQ) === 0) return cookie.substring(nameEQ.length,cookie.length);
    }
    return null;
}

Utils.setCookie = function(cookieName,cookieValue,lifespan /*milliseconds*/,doc) {
 if (!doc) { doc = document; }
 var today = new Date();
 var expire = new Date();
 if (lifespan===null || lifespan===0) lifespan=1000*60*60; //one hour
 expire.setTime(today.getTime() + lifespan);
 doc.cookie = cookieName+"="+escape(cookieValue)
                 + ";expires="+expire.toGMTString();
}

Utils.windowSize = function(doc)
{
    var x,y;
	Controls.blockWindowResize = true;
    if (self.innerHeight) // all except Explorer
    {
        x = self.innerWidth;
        y = self.innerHeight;
    }
    else if (doc.documentElement && doc.documentElement.clientHeight)
        // Explorer 6 Strict Mode
    {
        x = doc.documentElement.clientWidth;
        y = doc.documentElement.clientHeight;
    }
    else if (doc.body) // other Explorers
    {
        x = doc.body.clientWidth;
        y = doc.body.clientHeight;
    }
	Controls.blockWindowResize = false;
    return [x,y];
}

Utils.scrollTo = function(doc,x,y)
{
	doc.documentElement.scrollLeft = x;
	doc.body.scrollLeft = x; 

	doc.documentElement.scrollTop = y;
	doc.body.scrollTop = y; 
}

Utils.scrollBy = function(doc,deltaX,deltaY)
{
	doc.documentElement.scrollLeft += deltaX;
	doc.body.scrollLeft += deltaX; 

	doc.documentElement.scrollTop += deltaY;
	doc.body.scrollTop += deltaY; 
}

Utils.autoScroll = function(doc,event)
{
	Utils.cancelAutoScroll();

	// enable auto-scrolling since the user can't touch the scrollbar without
	// making the dropdown go away
	var autoScrollMargin = 10;
	var autoScrollAmount = 10;
	var autoScrollDelay  = 50;

	var screenSize = Utils.getBrowserWindowSize(doc);
	
	var autoScrollDelta = 0;

	var doAutoScroll = function()
	{
		Utils.scrollBy(doc,0,autoScrollDelta);
		Controls.autoScrollTimer = wait(doAutoScroll,autoScrollDelay);
	}
	
	if (event.clientY < autoScrollMargin)
	{
		autoScrollDelta = 0 - autoScrollAmount;
		doAutoScroll();
	} else if (event.clientY > (screenSize[1] - autoScrollMargin)) {
		autoScrollDelta = autoScrollAmount;
		doAutoScroll();
	}
}

Utils.cancelAutoScroll = function()
{
	if (Controls.autoScrollTimer > 0)
		clearWait(Controls.autoScrollTimer);
	Controls.autoScrollTimer = 0;
}

Utils.scrollOffsets = function(doc)
{
	// throw out null values and 0, use the greatest of the remaining values
	function bestFit(w,d,b)
	{
		var r = w ? w : 0;
		if (d && (!r || (r > d)))
			r = d;
		return b && (!r || (r > b)) ? b : r;
	}

	if (!doc) { return [0,0]; }

	try
	{
		var win = doc.defaultView  || doc.parentWindow;
	} catch (ex) {
		;
	}
	if (!win) { return [0,0]; }
	
	x = bestFit (
		win.pageXOffset ? win.pageXOffset : 0,
		doc.documentElement ? doc.documentElement.scrollLeft : 0,
		doc.body ? doc.body.scrollLeft : 0
	);

	y = bestFit (
		win.pageYOffset ? win.pageYOffset : 0,
		doc.documentElement ? doc.documentElement.scrollTop : 0,
		doc.body ? doc.body.scrollTop : 0
	);

    return [x,y];
}

Utils.getCharSet = function(doc)
{
	try
	{
		if (doc.charset)
			{ return doc.charset; }
		else
			{ return doc.characterSet; }
	} catch (ex) {
		return 'UTF-8';
	}
}

/**
 * getSelection(): get the currently hilighted text on the page
 */
Utils.getSelection = function (win) 
{
	if (!win)
		return "";
	try
	{
		var selectedText = "";
		var doc = win.document;

		if (win.getSelection) {
			var kids = win.getSelection().getRangeAt(0).cloneContents().childNodes;
			for (var i = 0; i < kids.length; ++i)
				selectedText += Utils.serializeElement(kids[i]);
		} else if (doc.getSelection) {
			selectedText = doc.getSelection()+'';
		} else if (doc.selection) {
			selectedText = doc.selection.createRange().htmlText;
		}

		return selectedText;
	} catch (ex) { return ""; }
}

Utils.getSelectionLocation = function (win) 
{
	if (!win)
		return null;
	try
	{
		var doc = win.document;
		
		if (doc.selection)
		{
			var range = doc.selection.createRange();
			var scrollOffset = Utils.scrollOffsets(doc);
			return { x:range.offsetLeft+scrollOffset[0], y:range.offsetTop+scrollOffset[1]}
		} else {			
			var selection = win.getSelection();
			var node = selection.focusNode;
			var pos = Utils.getBodyCoordinates(node.parentNode);
			return { x: pos[0], y:pos[1] }; 
	}
	} catch (ex) { return null; }
}

Utils.getSelectionRange = function(doc)
{
	if (doc.selection)
		return doc.selection.createRange();
	return null;
}

Utils.anchorsTargetTop = function(parentElem)
{
	var elem = null;
	for (var i=0; elem=parentElem.getElementsByTagName('A')[i]; i++)
	{
		if (!elem.target)
			elem.target = "_top";
	}		
}

Utils.amap =	function(_array, _func, _cont, profileCategory) 
{
	var index = 0; 
	var iterate = function() {
		if (0 <= index && _array.length > index)
		{ 
			wait(function() //sidebar trail _amap
			{
				if (false != _func(_array[index])) {
					++index;
					wait(iterate, 1,"Utils.amap "+profileCategory);
				}
			},1,"Utils.amap "+profileCategory);
		} else {
			// The array is exhausted 
			if (undefined != _cont && "function" === typeof(_cont))
				wait(_cont, 1,"Utils.amap "+profileCategory);
		}
	};
	wait(iterate, 1,"Utils.amap "+profileCategory);
	return true;
}

Utils.map = function(array, visitor, cont) 
{
    for (var i = 0; i < array.length; ++i)
    {
        if (false === visitor(array[i])) 
        {
            visitor.last = array[i];
			if (undefined != cont && "function" === typeof(cont))
				cont();
			return false;
		}
	}
	if (undefined != cont && "function" === typeof(cont))
		cont();
    return true;
};

/* Utils.public - declare private methods of a class as public
	- the function declared inside of the object should be the same as the public
	  name, preceded by and underscore

	here is an example:

		function myClass(id)
		{		
			this._say = function (what)
			{
				alert(id.":".what);
			}
		}
				
		Utils.public("myClass", { say:1 });
		
		var myObject = new myClass("foo");
		myObject.say("bar");	// alert says foo:bar

		var myOtherObject = new myClass("bar");
		myOtherObject.say("foo"); // alert says bar:foo
		
		className - string that is the name of the class
		functions - an object whose properties are function names with parameter counts
					{ say:1, do:2, tell:3 }
					
		*** for easier debugging, now returns a string that is evaled in the context
			that the functions are being prototyped ****

*/
Utils.declarePublic = function (className, declareFunctions)
{
	var declareString= "";

	for (var functionName in declareFunctions)
	{
		//assemble the parameters
		var declareParams = "";

		for (var i=0; i< declareFunctions[functionName]; i++)
			declareParams += (i > 0) ? (",p"+i) : ("p"+i);

		//assemble the declaration
		if (Log && Log.trace && (className != "ClassBase") && (className != "ModelBase"))
		{
			declareString += className+".prototype."+functionName+ " = function  " +
			       className+"_"		  +functionName+ "("+declareParams+")   {" +

				   //Log the call and parameters
				   "try { if (this.log) { " +
				   "Log.debug('" + className + "." + functionName + " (' + (arguments.length?(Log.traceParam?Utils.arrayToString(arguments,',',false,false):'...'):'') +') ['+this.getId()+']');	 " +
			 	   "Log.debug('{' ); " +
				   "Log.depth++; " +
				   "} " +

			 	   "var r = this._"		  +functionName+ "("+declareParams+");   	" +	

				   "if (this.log) { " +
				   "Log.depth--; " +
			 	   "Log.debug('} // " + className + "." + functionName + " (' + (arguments.length?'...':'') +') ['+(this.getId?this.getId():'##')+']'+(Log.traceParam?(' = '+Utils.arrayToString([r],',',false,false)):''));	" +
				   "} } catch (ex) { Log.halt(); }; " +
				  			 	   
   				   "return r; }\n";
		} else {
			declareString += className+".prototype."+functionName+ " = function  "+
			       className+"_"		  +functionName+ "("+declareParams+")   {"+
			 	   "return this._"		  +functionName+ "("+declareParams+");}\n";
		}
	}

	if (Log && Log.trace)
		Log.debug("/* class "+className+" */\n"+declareString);

	return declareString;
}

/* Utils.implements - copy all of the public function definitinos from the prototype of the 
	superclass onto the subclass */
Utils.implement = function(subClass,superClass)
{
	for (var func in superClass.prototype)
	{
		subClass.prototype[func] = superClass.prototype[func];
	}
}

/*
 * Utils.declare - declare private variables, initialize to default, and declare getter and setter
 * 	takes and array of variableName:defaultValue pairs, returns a string to be eval() in
 *  context of the object
 *
 * 		eval(Utils.declare({id:0, name:""}); 
 */
Utils.declare = function(className,properties)
{
	var out = "";
	for (var prop in properties)
	{
		//set the default value
		var val=properties[prop];

		if (typeof(val)==="string")
			val = '"'+val+'"'

		if (val === null)
			val = "null"

		var cProp = prop.charAt(0).toUpperCase() + prop.substring(1);

		//declare the variable;
		out += "var _"+prop+" = "+val+";\n";
		
		//declare private get and set functions
		out += "this._get"+cProp+" = function _"+className+"_get"+cProp+"() { return _"+prop+"; }\n";
		out += "this._set"+cProp+" = function _"+className+"_set"+cProp+"(val) { return _"+prop+" = val; }\n\n";
	}
	return out;
}

Utils.escapeHTML = function(doc,html)
{
    var div = Controls.createElement(doc,'div');
    var text = doc.createTextNode(html);
    div.appendChild(text);
    return div.innerHTML;
}

Utils.serializeElement = function(element)
{
	var output = "";
	if (element.nodeType === 3)
	{
		return element.data;
	} else {
		if (element.getAttribute("_moz-userdefined")!=null)
			return "";

		output = "<"+ element.tagName 

		//attributes
		var defaultElement = Controls.createElement(element.ownerDocument,element.tagName);
		for (var i = 0; i< element.attributes.length; i++)
		{
			//don't add default attribute values
			var skip = false;
			var name = element.attributes[i].nodeName;
			var val = element.attributes[i].nodeValue;
			if (defaultElement.getAttribute(name)===val) skip=true; 
			
			if (!skip && val)
				output += " "+name.toUpperCase()+"=\""+val+"\"";
		}		

		if (element.childNodes.length > 0)
		{
			output += ">";
	
			for (var i = 0; i< element.childNodes.length; i++)
			{
				output += Utils.serializeElement(element.childNodes[i]);
			}
			output += "</"+element.tagName+">";
		} else 
			output += " />";

	}
	return output;
}

Utils.unescapeHTML = function(doc,encodedHTML)
{
	var output = encodedHTML;

    var div = Controls.createElement(doc,'div');
    div.innerHTML = encodedHTML;

	output = "";
	for (var i =0 ; i<div.childNodes.length; i++)
	{
		output += Utils.serializeElement(div.childNodes[i]);
	}
	    
	return output;
}
/* returns a query string parameter of a url. Returns null if param is not present */
Utils.getUrlParameter = function(url, param)
{
	param = param.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); 
	var regexS = "[\\?&]"+param+"=([^&#]*)";  
	var regex = new RegExp( regexS );  
	var results = regex.exec( url );  
	if( results == null )    
		return ""; 
	 else    
		return results[1];
}

Utils.parseUrl = function(url)
{
	//parse the url into parts
	var values = new Array();
	var fields = {'url':0, 'scheme':2, 'user' : 4, 'pass' : 5, 'host' : 6, 'port' : 7, 'path' : 8, 'query' : 9, 'hash' : 10};
	var regex = /^((\w+):\/\/)?((\w+):?(\w+)?@)?([^\/\?:]+):?(\d+)?(\/?[^\?#]+)?\??([^#]+)?#?(\w*)/;

	var r = regex.exec(url);
	if (r)
		for(var f in fields) 
			if (typeof r[fields[f]] != 'undefined')
				values[f] = r[fields[f]];

	return values;
}

Utils.urlEncode = function(unencoded,convertPlus)
{
	var encoded = encodeURIComponent(unencoded);
	encoded = encoded.replace(/\'/g,'%27');
	encoded = encoded.replace(/\!/g,'%21');
	encoded = encoded.replace(/\*/g,'%2A');
	encoded = encoded.replace(/\(/g,'%28');
	encoded = encoded.replace(/\)/g,'%29');
	encoded = encoded.replace(/\~/g,'%7E');

	if (!convertPlus)
		encoded = encoded.replace(/%2B/g,'+');

	return encoded;
}

Utils.urlDecode = function(encoded,convertPlus)
{
	if (convertPlus)
		encoded = encoded.replace(/\+/g,' ');
	var decoded = decodeURIComponent(encoded);
	return decoded;
}

Utils.standardizeUrl = function(url)
{
	if (!url) { return ""; }

	// Amazon Hack
	// amazon.com adds a unique identifier to the path of URL's to identify the visitor - 
	//  that interferes with our url matching scheme, so we need to remove the visitor id
	if (url.indexOf('amazon.com/')>0)
	{
		url = url.replace(/\/\d{3}-\d{7}-\d{7}/,'/000-0000000-0000000');
	}

	var p = Utils.parseUrl(url);

	var sUrl;

	sUrl = p['scheme']+"://" + p['host'];
	if (p['port'] && (p['port'] != '80'))
		sUrl += ":"+p['port'];

	if (p['path'])
		sUrl += p['path'];
	else
		sUrl += "/";

	if (p['query'])
	{
		var q = "";
		var sep = "&";
		vars = p['query'].split('&');
		if (vars.length === 1)
		{
			sep = ";";
			vars = p['query'].split(';');
		}

		for (var i = 0; i < vars.length; i++)
		{
			var v = vars[i];
			if (v.length > 0)
			{
				var eq = v.indexOf("=");
				if (eq === -1)
				{
					if (i > 0)
						q += "&"+Utils.urlEncode(Utils.urlDecode(v,false),false);
					else			
						q += Utils.urlEncode(Utils.urlDecode(v,false),false);
				} else {
					var key = Utils.urlDecode(v.substring(0,eq),false);
					var val = Utils.urlDecode(v.substring(eq+1,v.length),true);
					if (q)
						q+=sep;
	
					var uriparts = val.split("/");
					for (var j = 0; j<uriparts.length; j++)
					{
						var cparts = uriparts[j].split(":");
						for (var k=0; k<cparts.length; k++)
						{
							cparts[k] = Utils.urlEncode(cparts[k],true);
						}
						uriparts[j] = cparts.join(':');
					}
					val = uriparts.join('/');
					q+=Utils.urlEncode(key,false)+"="+val;
				}
			}
		}

		sUrl += "?"+q;
	}


	return sUrl;
}



// /var/www/trailfire.com-release/content/client/lib/utils-site.js 

//this file is to overload utility functions that need to run differently on the website

// our agressive Utils.newArray function could interfere with Google Analytics
Utils.newArray = function()
{
	return new Array();
}

// /var/www/trailfire.com-release/content/client/lib/class.js 

//for debugging
Utils.classDump = function()
{
	var msg = "Memory Dump\n----------\n";
	msg += "Total Created : "+classBase_objectsCreated+"\n";
	msg += "Total Destroyed : "+classBase_objectsDestroyed+"\n";
	msg += "In Memory : "+classBase_objectCount+"\n---------------\n";
	for (var c in classBase_classCount)
	{
		if (classBase_classCount[c] != 0)
			msg += c + " : " + classBase_classCount[c]+"\n";
	}
	return msg;
}

/* 
 * classBase for keeping track of global objects
 */

var classBase_objectCount = 0;
var classBase_objectsCreated = 0;
var classBase_objectsDestroyed = 0;
var classBase_classCount = Utils.newArray();

var classBase_nextId = 0;
//var classBase_objects = Utils.newArray()

function ClassBase(type)
{
	var destroyed = false;
	var id = classBase_nextId++;

	this.log = Log.trace && Log.traceClass;

//	classBase_objects[id]=this;

	if (typeof(classBase_classCount[type]) === "undefined")
		classBase_classCount[type] = 0;

	classBase_objectCount++;	
	classBase_objectsCreated++;
	classBase_classCount[type]++;

	this._cleanup = function()
	{
		if (destroyed)
		{
			throw new Error(type+" instance already destroyed");
		}

		destroyed = true;
		classBase_objectCount--;
		classBase_objectsDestroyed++;
		classBase_classCount[type]--;

//		delete classBase_objects[id];
		for (var i in this)
		{
			if (i != 'log')
				this[i] = null;
		}
	}
	
	this._destroy = function()
	{
		this._cleanup();
	}
		
	this._getType = function() { return type; }
	this._getId = function() { return type+'-#'+id; }
}

eval(Utils.declarePublic("ClassBase",
	{
		destroy:0,
		getType:0,
		getId:0
	}
));

// /var/www/trailfire.com-release/content/client/model/model-base.js 

function ModelBase(type)
{
	ClassBase.call(this,type);

	this.log = Log.trace && Log.traceModel;

	/**
	 * EventQueue : to fire events
	 */
    var _queue = new Array();

    this._listen = function(listener) {
    	Utils.assert(typeof(listener) === "function",function()
    	{
    		return "invalid listener";
    	})

        for (var i = 0; i < _queue.length; i++) //prevent duplicate listeners
            Utils.assert(_queue[i] != listener, function()
            {
	            return "already listening to listener";
			});

        _queue.push(listener);

		if (this.log)
		{
			var l = "???"
			if (listener.im_self && listener.im_self.getType)
				l = listener.im_self.getType() + "[" + listener.im_self.getId() + "]";
			Log.debug("(+)Listen  : "+this.getType()+"["+this.getId()+"] += "+l+" (@"+_queue.length+")");
		}
    },

    this._ignore= function(listener) { 
    	var ignored=false;

        for (var i = 0; i < _queue.length; i++)
            if (_queue[i] === listener)
            {
            	ignored = true;
                _queue.splice(i, 1);
                if (_queue.length >0)
                	// Notify remaining listeners that a listener was removed
                	this._fire({name:"listener-removed",model:this});
            }
            
		Utils.assert(ignored,bind(function()
		{
        	return this.getType()+"["+this.getId()+"] : ignore() can't find listener";
		},this))

		if (this.log)
		{
			var l = "???"
			if (listener.im_self && listener.im_self.getType)
				l = listener.im_self.getType() + "[" + listener.im_self.getId() + "]";
			Log.debug("(-)Ignore  : "+this.getType()+"["+this.getId()+"] -= "+l+" (@"+_queue.length+")");
		}
    }

    this._fire= function(event) {
    
		Utils.assert(_queue != null,bind(function()
		{
    		return "Firing on destroyed "+this.getType()+"["+this.getId()+"]";
		},this));

		var profilerId = profiler.start(type + "[" + event.name +"]");

    	//copy callbacks so any ignores or listens won't interfere
    	var fireQ = new Array;
        for (var i=0; i < _queue.length; i++) { fireQ.push(_queue[i]);  }

		var callback = null;
		var i = 0;
		while (callback = fireQ.pop())
		{
			if (this.log)
			{
				var l = "???"
				if (callback.im_self && callback.im_self.getType)
					l = callback.im_self.getType() + "[" + callback.im_self.getId() + "]";
				Log.debug("-->Fire    : "+this.getType()+"["+this.getId()+"] -> '"+event.name+"' to "+l+" ("+(++i)+"/"+_queue.length+")");
				Log.debug("{");
				Log.depth++;
			}

			try
			{
				callback(event);
			} catch (ex) { 
				Log.error("Exception : "+this.getType()+"["+this.getId()+"] fire("+event.name+") \n "+ex.name + " : "+ex.message+"\n @:"+Utils.functionToString(callback));
				Log.debug(ex.toString());
			}

			if (this.log)
			{
				Log.depth--;				
				Log.debug("} // "+event.name+"-->"+l);
			}
		}

		profiler.stop(profilerId);
     }

	this._destroyModel = function() {
			
		Utils.assert(_queue.length === 0,bind(function()
		{
			var errorMsg = "Destroying "+this.getType()+"["+this.getId()+"] with "+ _queue.length +" listener(s)"
			for (var i=0; i<_queue.length; i++)
			{
				var type = "???"
				var callback = _queue[i];
				if (callback.im_self && callback.im_self.getType)
					type = callback.im_self.getType() + "[" + callback.im_self.getId() + "]";

				errorMsg += ", "+type;
			}
			return errorMsg;
		},this))

		if (this.log)
			Log.debug("Destroy    : "+this.getType()+"["+this.getId()+"]");

		_queue = null;
		this._cleanup();
	}
	
	this._destroy = function() { 
		this._destroyModel();
	}
	this._getListenerCount = function(){
		return _queue.length;
	}
	this._containsListener = function(listener){
        for (var i = 0; i < _queue.length; i++)
            if (_queue[i] === listener)
            {
            	return true;
            }
        return false;
	}
}

Utils.implement(ModelBase,ClassBase);
eval(Utils.declarePublic("ModelBase",{listen:1, 
									  ignore:1, 
									  fire:1, 
									  destroy:0,
									  getListenerCount:0,
									  containsListener:1
									  }));



// /var/www/trailfire.com-release/content/client/lib/ajax.js 

// $Id: serialize.js,v 1.5 2004/11/21 11:14:05 harryf Exp $
// Notes:
// - Watch out for recursive references - call inside a try/catch block if uncertain
// - Objects are serialized to PHP class name JPSpan_Object by default
// - Errors are serialized to PHP class name JPSpan_Error by default
//
// See discussion below for notes on Javascript reflection
// http://www.webreference.com/dhtml/column68/
var JPSpan_RequestTimeout = 20000;

function JPSpan_Serialize(Encoder) {
    this.Encoder = Encoder;
    this.typeMap = new Object();
};

JPSpan_Serialize.prototype = {

    typeMap: null,
    
    addType: function(cname, callback) {
        this.typeMap[cname] = callback;
    },
    
    serialize: function(v) {
    
        switch(typeof v) {
            //-------------------------------------------------------------------
            case 'object':
            
                // It's a null value
                if ( v === null ) {
                    return this.Encoder.encodeNull();
                }
                
                // Get the constructor
                var c = v.constructor;
                
                if (c != null ) {
                
                    // It's an array
                    if ( c == Array ) {
                        return this.Encoder.encodeArray(v,this);
                    } else {
                    
                        // Get the class name
                        var match = c.toString().match( /\s*function (.*)\(/ );

                        if ( match == null ) {
                            return this.Encoder.encodeObject(v,this,'JPSpan_Object');
                        }
                        
                        // Strip space for IE
                        var cname = match[1].replace(/\s/,'');
                        
                        // Has the user registers a callback for serializing this class?
                        if ( this.typeMap[cname] ) {
                            return this.typeMap[cname](v, this, cname);
                            
                        } else {
                            // Check for error objects
                            var match = cname.match(/Error/);
                        
                            if ( match == null ) {
                                return this.Encoder.encodeObject(v,this,'JPSpan_Object');
                            } else {
                                return this.Encoder.encodeError(v,this,'JPSpan_Error');
                            }

                        }
                    }
                } else {
                    // Return null if constructor is null
                    return this.Encoder.encodeNull();
                }
            break;
            
            //-------------------------------------------------------------------
            case 'string':
                return this.Encoder.encodeString(v);
            break;
            
            //-------------------------------------------------------------------
            case 'number':
                if (Math.round(v) == v) {
                    return this.Encoder.encodeInteger(v);
                } else {
                    return this.Encoder.encodeDouble(v);
                };
            break;
            
            //-------------------------------------------------------------------
            case 'boolean':
                if (v == true) {
                    return this.Encoder.encodeTrue();
                } else {
                    return this.Encoder.encodeFalse();
                };
            break;
            
            //-------------------------------------------------------------------
            default:
                return this.Encoder.encodeNull();
            break;
        }
    }
}

// $Id: xml.js,v 1.7 2004/11/19 21:56:47 harryf Exp $
// See: http://jpspan.sourceforge.net/wiki/doku.php?id=encoding
function JPSpan_Encode_Xml() {
    this.Serialize = new JPSpan_Serialize(this);
};

JPSpan_Encode_Xml.prototype = {

    // Used by rawpost request objects
    contentType: 'text/xml; charset=UTF-8',

    encode: function(data) {
        return '<?xml version="1.0" encoding="UTF-8"?><r>'+this.Serialize.serialize(data)+'</r>';
    },
    
    encodeInteger: function(v) {
        return '<i v="'+v+'"/>';
    },
    
    encodeDouble: function(v) {
        return '<d v="'+v+'"/>';
    },
    
    // Need UFT-8 encoding?
    encodeString: function(v) {
        return '<s>'+v.replace(/&/g, '&amp;').replace(/</g, '&lt;')+'</s>';
    },
    
    encodeNull: function() {
        return '<n/>';
    },
    
    encodeTrue: function() {
        return '<b v="1"/>';
    },
    
    encodeFalse: function() {
        return '<b v="0"/>';
    },
    
    // Arrays being with indexed values - properties added second
    encodeArray: function(v, Serializer) {
        var indexed = new Array();
        var a = '';
        for (var i=0; i<v.length; i++) {
            indexed[i] = true;
            a += '<e k="'+i+'">'+Serializer.serialize(v[i])+'</e>';
        };

        for ( var prop in v ) {
            if ( indexed[prop] || (typeof(prop) == "function")) {
                continue;
            };
            // Assumes prop obeys Javascript naming rules
            a += '<e k="'+prop+'">'+Serializer.serialize(v[prop])+'</e>';
        };
        return '<a>'+a+'</a>';
    },
    
    encodeObject: function(v, Serializer, cname) {
        var o='';
        for (var prop in v) {
            o += '<e k="'+prop+'">'+Serializer.serialize(v[prop])+'</e>';
        };
        return '<o c="'+cname.toLowerCase()+'">'+o+'</o>';
    },
    
    encodeError: function(v, Serializer, cname) {
        var e = new Object();
        if ( !v.name ) {
            e.name = cname;
            e.message = v.description;
        } else {
            e.name = v.name;
            e.message = v.message;
        };
        return this.encodeObject(e,Serializer,cname);
    },
    
    cleanup: function()
    {
    	this.Serialize = null;
    }
}
//---------------------------------------------------------------------------------
// Based loosely on nsXmlRpcClient: http://mozblog.mozdev.org/nsXmlRpcClient.js
// @version $Id: httpclient.js,v 1.6 2004/11/22 22:07:49 harryf Exp $
//---------------------------------------------------------------------------------

// Decorates a normal JS exception for client side errors
// @param Error
// @param string error code
function JPSpan_Client_Error(e, code) {
    e.name = 'Client_Error';
    e.code = code;
    return e;
};

//---------------------------------------------------------------------------------

function JPSpan_HttpClient() {};
JPSpan_HttpClient.prototype = {
    xmlhttp: null,
    userhandler: null,
    timeout_id: null,
    
    abort: function()
    {
    	if (this.xmlhttp)
    	{
	    	this.xmlhttp.abort();
	    	this.cleanup();
	    }
    },

    cleanup: function()
    {		
		if (this.wait_id)
		{
	    	clearWait(this.wait_id);
	    	this.wait_id = 0;
	    }
		
		if (this.timeout_id)
		{
	    	clearWait(this.timeout_id);
	    	this.timeout_id = 0;
	    }

		if (this.xmlhttp)
		{		
	    	this.xmlhttp.abort();
	       	this.xmlhttp = null;
	    }
	    
	    if (this.userhandler)
	    {
	    	this.userhandler.context = null;
	    	this.userhandler = null;
	    }
    },

    // @throws Error code 1000
    init: function() {
        try {
            // Mozilla / Safari / IE7
            this.xmlhttp = new XMLHttpRequest();
        } catch (e) {
            // IE
            var MSXML_XMLHTTP_PROGIDS = new Array(
                'MSXML2.XMLHTTP.5.0',
                'MSXML2.XMLHTTP.4.0',
                'MSXML2.XMLHTTP.3.0',
                'MSXML2.XMLHTTP',
                'Microsoft.XMLHTTP'
            );
            var success = false;
            for (var i=0;i < MSXML_XMLHTTP_PROGIDS.length && !success; i++) {
                try {
                    this.xmlhttp = new ActiveXObject(MSXML_XMLHTTP_PROGIDS[i]);
                    success = true;
                } catch (e) {}
            }
            if ( !success ) {
                throw JPSpan_Client_Error(
                        new Error('Unable to create XMLHttpRequest.'),
                        1000
                    );
            }
        }
    },
    
    // Place an synchronous call (results returned directly)
    // @param object request object for params and HTTP method
    // @return string response text
    // @throws Error codes 1001 and 1002
    call: function (request) {

        if ( !this.xmlhttp ) {
            this.init();
        }

        if (this.callInProgress()) {
            throw JPSpan_Client_Error(
                    new Error('Call in progress'),
                    1001
                );
        };
        
        request.type = 'sync';
        request.prepare(this.xmlhttp);
        this.xmlhttp.setRequestHeader('Accept-Charset','UTF-8');
        request.send(this.xmlhttp);
        
        if ( this.xmlhttp.status == 200 ) {
            return this.xmlhttp.responseText;
        } else {
            var errorMsg = '['+this.xmlhttp.status
                            +'] '+this.xmlhttp.statusText;
            var err = new Error(errorMsg);
            err.headers = this.xmlhttp.getAllResponseHeaders();
            throw JPSpan_Client_Error(err,1002);
        }
    },

    // Place an asynchronous call (results sent to handler)
    // @param object request object for params and HTTP method
    // @param object handler: user defined object to be called
    // @throws Error code 1001
    asyncCall: function (request,handler) {

        var callName = null;
        if ( arguments[2] ) {
            callName = arguments[2];
        }
        
        if ( !this.xmlhttp ) {
            this.init();
        }

        if (this.callInProgress()) {
            throw JPSpan_Client_Error(
                    new Error('Call in progress'),
                    1001
                );
        };

        this.userhandler = handler;
        
        if ( this.userhandler.onInit ) {
            try {
                this.userhandler.onInit(callName);
            } catch(e) {
                this.displayHandlerError(e);
            }
        }
        
        request.type = 'async';
        request.prepare(this.xmlhttp);
 
        var self = this;

        this.timeout_id = wait(function() {
            self.onTimeout(self, callName);
        },request.timeout,"ajaxTimeout");

		// in IE - setting the onreadystatechange property creates a circular reference 
		//  that can not be broken (setting xmlhttp.onreadystatechange = null causes an exception)
//        this.xmlhttp.onreadystatechange = new JPSpan_stateChangeCallback(this, callName);

		//instead, us a wait loop to watch the ready state
		this.wait_id = wait(new JPSpan_stateChangeCallback(this,callName),250,"ajaxListener",false);

        request.send(this.xmlhttp);
    },

    
    // Checks to see if XmlHttpRequest is busy
    // @return boolean TRUE if busy
    callInProgress: function() {
		try
		{
	        switch ( this.xmlhttp.readyState ) {
	            case 1:
	            case 2:
	            case 3:
	                return true;
	            break;
	            default:
	                return false;
	            break;
	        }
	   	} catch (ex) {
			return false;
	   	}
    },
    
    // Callback for timeouts: aborts the request
    // @access private
    onTimeout: function (client, callName) {
		try
		{
	        if ( client ) 
	        {
	            var errorMsg = 'Operation timed out';
	
	            if ( callName ) {
	                errorMsg += ': '+callName;
	            }
	            
	            if ( client.userhandler.onError ) {
	                var ex = JPSpan_Client_Error(new Error(errorMsg), 1003);
	                try {
	                    client.userhandler.onError(ex, callName);
	                } catch (e) {
	                    client.displayHandlerError(e);
	                }
	            }            
		        client.abort();
	        }
		} catch (ex) { Log.error(ex); }
    },
    
    // Called from stateChangeCallback if an error occurs in
    // in handler object
    // @access private
    displayHandlerError: function(e) {
        var errorMsg = "Error in Handler\n";
        if ( e.name ) {
            errorMsg += 'Name: '+e.name+"\n";
        };
        if ( e.message ) {
            errorMsg += 'Message: '+e.message+"\n";
        } else if ( e.description ) {
            errorMsg += 'Description: '+e.description+"\n";
        };
        if ( e.fileName ) {
            errorMsg += 'File: '+e.fileName+"\n";
        };
        if ( e.lineNumber ) {
            errorMsg += 'Line: '+e.lineNumber+"\n";
        };
        Log.error(errorMsg);
    },
    
    getReadyState: function() {
		try
		{
			return this.xmlhttp.readyState;
		} catch (ex) {
			return 5; //error accessing xmlhttp object, connection gone
		}
    }
}

// Callback for asyncCalls
// @access private
function JPSpan_stateChangeCallback(client, callName) 
{
	var stateChangeCallback = function()
	{
		var state = client.getReadyState();
	    switch (state) {
	
	        // XMLHTTPRequest.open() has just been called
	        case 1:
	            if ( client.userhandler.onOpen ) {
	                try {
	                    client.userhandler.onOpen(callName);
	                } catch(e) {
	                    client.displayHandlerError(e);
	                }
	            }
	        break;
	
	        // XMLHTTPRequest.send() has just been called
	        case 2:
	            if ( client.userhandler.onSend ) {
	                try {
	                    client.userhandler.onSend(callName);
	                } catch(e) {
	                    client.displayHandlerError(e);
	                }
	            }
	        break;
	        
	        // Fetching response from server in progress
	        case 3:
	            if ( client.userhandler.onProgress ) {
	                try {
	                    client.userhandler.onProgress(callName);
	                } catch(e) {
	                    client.displayHandlerError(e);
	                }
	            }
	        break;
	        
	        // Download complete
	        case 4:
	            clearWait(client.timeout_id);
	           try {
	                switch ( client.xmlhttp.status ) {
	                    case 200:
	                        if ( client.userhandler.onLoad ) {
	                            try {
	                                client.userhandler.onLoad(client.xmlhttp.responseText, callName);
	                            } catch (e) {
	                                client.displayHandlerError(e);
	                            }
	                        }
	                        break;
	                    
	                    // Special case for IE on aborted requests
	                    case 0:
	                        // Do nothing
	                        break;
	                        
	                    default:
	                        if ( client.userhandler.onError ) {
	                            try {
	                            var errorMsg = '['+client.xmlhttp.status
	                                +'] '+client.xmlhttp.statusText;
	                            var err = new Error(errorMsg);
	                            err.headers = this.xmlhttp.getAllResponseHeaders();
	                            client.userhandler.onError(JPSpan_Client_Error(err,1002), callName);
	                            } catch(e) {
	                                client.displayHandlerError(e);
	                            }
	                        }
	                        break;
	                }
	            } catch (e) {
	                // client.xmlhttp.status not available - failed requests
	            }
	        break;
	    }

	    try //to reschedule listener (see notes above about onreadystatechange)
	    {
	    	client.wait_id = 0;
		    if (state < 4)
				client.wait_id = wait(stateChangeCallback,250,"ajaxListener",false);
		} catch (ex) {
            if ( client.userhandler.onError ) {
                try {
	                var err = new Error('unknown exception rescheduling listener');
	                client.userhandler.onError(JPSpan_Client_Error(err,1010), callName);
                } catch(e) {
                    client.displayHandlerError(e);
                }
            }
		}
	}
	
	return stateChangeCallback;
}


// $Id: request.js,v 1.4 2004/11/19 20:04:05 harryf Exp $
// Base request "class"
function JPSpan_Request(encoder) {
    this.encoder = encoder;
}
JPSpan_Request.prototype = {

    // Instance of an encoder
    encoder: null,
    
    // The URL of the server
    serverurl: '',
    
    // The actual URL the request is sent to (may be modified for GET requests)
    requesturl: '',
    
    // Body of request (for HTTP POST only)
    body: '',
    
    // Remote method arguments list
    args: null,
    
    // Type of request (async / sync)
    type: null,

    // Timeout in milliseconds for requests
    timeout: JPSpan_RequestTimeout,
    
    // Add an argument for the remote method
    // @param string argument name
    // @param mixed value
    // @return void
    // @throws Error code 1004
    addArg: function(name, value) {
        if ( !this.args ) {
            this.args = Utils.newArray();
        }
        var illegal = /[\W_]/;
        if (!illegal.test(name) ) {
            this.args[name] = value;
        } else {
            throw JPSpan_Client_Error(
                    new Error('Invalid parameter name ('+name+')'),
                    1004
                );
        }
    },

    // Reset the request object
    // @return void
    // @access public
    reset: function() {
        this.serverurl = '';
        this.requesturl = '';
        this.body = '';
        this.args = null;
        this.type = null;
        this.timeout = JPSpan_RequestTimeout;
    },
    
    // Used internally by request objects to build the request payload
    // @protected
    // @abstract
    build: function() {},
    
    // Used by JPSpan_HTTPClient to prepare the XMLHttpRequest object
    // @param XMLHttpRequest
    // @return void
    // @protected
    // @abstract
    prepare: function(http) {},
    
    // Used by JPSpan_HTTPClient to call send on the XMLHttpRequest object
    // @return void
    // @protected
    // @abstract
    send: function(http) {},
    
    cleanup: function() { 
    	this.encoder = null;
    	this.args = null;
    	this.type = null;
    	this.body = null;
    }
};

// @version $Id: rawpost.js,v 1.3 2004/11/15 12:14:28 harryf Exp $

// For building raw (not urlencoded) HTTP POST requests
function JPSpan_Request_RawPost(encoder) {

    var oParent = new JPSpan_Request(encoder);
    
    // Builds the post body
    // @protected
    // @throws Error code 1006
    oParent.build = function() {
        try {
            this.body = this.encoder.encode(this.args);
        } catch (e) {
            throw JPSpan_Client_Error(e, 1006);
        };
        this.requesturl = this.serverurl;
    };
    
    // Called from JPSpan_HttpClient to prepare the XMLHttpRequest object
    // @param XMLHttpRequest
    // @protected
    // @throws Error codes 1005, 1006 and 1007
    oParent.prepare = function(http) {
        this.build();
        switch ( this.type ) {
            case 'async':
                try {
                    http.open('POST',this.requesturl,true);
                } catch (e) {
                    throw JPSpan_Client_Error(new Error(e),1007);
                };
            break;
            case 'sync':
                try {
                    http.open('POST',this.requesturl,false);
                } catch (e) {
                    throw JPSpan_Client_Error(new Error(e),1007);
                };
            break;
            default:
                throw JPSpan_Client_Error(
                        new Error('Call type invalid '+this.type),
                        1005
                    );
            break;
        };
       	http.setRequestHeader('Accept-Charset','UTF-8');
        http.setRequestHeader('Content-Length', this.body.length);
        http.setRequestHeader('Content-Type',this.encoder.contentType);
        http.setRequestHeader('X-AID', this.args[0]);
    };
    
    // Send the request
    // @protected
    oParent.send = function(http) {
        http.send(this.body);
    };
   
    return oParent;
};

// $Id: remoteobject.js,v 1.12 2004/11/22 23:28:11 harryf Exp $
// Base class for generated classes
function JPSpan_RemoteObject() {}

JPSpan_RemoteObject.prototype = {

    // Switch to asyncronous mode
    // @param Object user defined handler to call
    // @access public
    Async: function(userHandler) {
        JPSPan_initResponseHandler(this,userHandler);
        this.__callState = "async";
    },
    
    // Switch to syncronous mode. Be warned: timeouts not supported!
    // @access public
    Sync: function() {
        this.__responseHandler = null;
        this.__callState = "sync";
    },
    
    // Returns the instance of XMLHttpRequest being used by
    // JPSpan_HttpClient. Allows you to bypass the APIs and
    // access it directly, for things like setting / getting HTTP 
    // headers - calling open() or send() not recommended
    // @return XMLHttpRequest
    // @access public
    GetXMLHttp: function() {
        if ( !this.__client ) {
            this.__initClient();
        }
        return this.__client.xmlhttp;
    },
        
    // Called when a error occurs in making the request
    // on the client-side. Typically these be transport errors
    // e.g. server HTTP status code != 200
    // Replace with your own function as required
    // @access public
    clientErrorFunc: function(e) {
    
        try {
            var errorMsg = '['+e.name+'] '+e.message;
        } catch (ex) {
            var errorMsg = '[Client_Error] '+e;
        }

        if ( e.client && e.call ) {
            errorMsg = errorMsg + ' while calling '+e.client+'.'+e.call+'()';
        }

        Log.error(errorMsg);
        
    },
    
    // Timeout for async requests in milliseconds
    // @access public
    timeout: JPSpan_RequestTimeout,
    
    // Called when a error in handling the response from
    // the server (e.g. the response was junk or some PHP
    // error occurred)
    // Replace with your own function as required
    // @access public

    serverErrorFunc: function(e) {
        try {
            var errorMsg = '['+e.name+'] '+e.message;
        } catch (ex) {
            var errorMsg = '[Server_Error] '+e;
        }

        if ( e.client && e.call ) {
            errorMsg = errorMsg + ' while calling '+e.client+ '.'+e.call+'()';
        }
        
        if (e.response)
            errorMsg = errorMsg + '\n' + e.response;
        
        if (e.stack)
            errorMsg = errorMsg + '\n' + e.stack;

        Log.error(errorMsg);

    },
    
    // Called when the application running on the server
    // returns an error (e.g. a divide by zero error).
    // When making async calls, local error methods
    // will be called first (if they exist)
    // Replace with your own function as required
    // @access public
    applicationErrorFunc: function(e) {

        try {
            var errorMsg = '['+e.name+'] '+e.message;
        } catch (ex) {
            var errorMsg = '[Application_Error] '+e;
        }

        if ( e.client && e.call ) {
            errorMsg = errorMsg + ' while calling '+e.client+ '.'+e.call+'()';
        }

        Log.error(errorMsg);

    },
    
    // Private stuff from here...
    // @var string Url to server handler
    // @access private
    __serverurl: null,

    // @var JPSpan_Request subclass object
    // @access private
    __request: null,
    
    // @var JPSpan_HttpClient
    // @access private
    __client: null,
    
    // @var Object handlers responses to async calls
    // @access private
    __responseHandler: null,
    
    // @var string type of calls to make: sync or async
    // @access private
    __callState: 'sync',
    
    // @var string Name of the remote class for error messages
    // @acess private
    __remoteClass: '',
    
    // Initialize the XmlHttpClient
    // @access private
    __initClient: function() {
        this.__client = new JPSpan_HttpClient();
    },
    
    __cleanup: function() {
    	if (this.__request)
    	{
			this.__request.cleanup();
			this.__request = null;
		}
		
		if (this.__client)
		{
			this.__client.cleanup();
			this.__client = null;
		}

		if (this.__responseHandler)
		{
			this.__responseHandler.context = null;
			this.__responseHandler.userHandler = null;
			this.__responseHandler = null;
		}
    },
    
    // Call remote procedure (passes onto __asyncCall or __syncCall)
    // @access private
    __call: function(url,args,callName) {
    
        if ( !this.__client ) {
            this.__initClient();
        }

        
        this.__request.reset();
        this.__request.serverurl = url;
        this.__request.timeout = this.timeout;
        
        for(var i=0; i < args.length; i++) {
            this.__request.addArg(i,args[i]);
        };

        if ( this.__callState == "async" ) {
            return this.__asyncCall(this.__request,callName);
        } else {
            return this.__syncCall(this.__request);
        }

    },
    
    // Call remote procedure asynchronously
    // @access private
    __asyncCall: function(request, callName) {

        try {
            this.__client.asyncCall(request,this.__responseHandler,callName);
        } catch (e) {
            this.clientErrorFunc(e);
        }

        return;

    },
    
    // Call remote procedure synchronously
    // @access private
    __syncCall: function(request) {
        try {
            var response = this.__client.call(request);

            try {

            	function syncTimeout(func,time) { return func(); }
				var tmp = response.replace(/setTimeout/g,"syncTimeout");

				eval("var dataFunc = "+tmp);
				var t = this;
				var rVal = null;
                try {
                    dataFunc(function(data) 
                    {
						rVal = data;
	                });
					return rVal;
                } catch (e) {
                
                    if ( e.name == 'Server_Error' ) {
                        this.serverErrorFunc(e);
                    } else {
                        this.applicationErrorFunc(e);
                    }

                }

            } catch (e) {
                e.name = 'Server_Error';
                e.code = 2006;
                e.response = response;
                this.serverErrorFunc(e);
            }

        } catch(e) {
            this.clientErrorFunc(e);
        }

    }

};

// Sets up the response handler
// @access private
function JPSPan_initResponseHandler(self,userHandler) {

    self.__responseHandler = new Object();

    self.__responseHandler.context = self;
    
    self.__responseHandler.userHandler = userHandler;

    self.__responseHandler.onInit = function(callName) {
        var initFunc = callName+'Init';
        if ( this.userHandler[initFunc] ) {
            try {
                this.userHandler[initFunc]();
            } catch(e) {
                self.__client.displayHandlerError(e);
            }
        }
    },
    
    self.__responseHandler.onOpen = function(callName) {

        var openFunc = callName+'Open';
        if ( this.userHandler[openFunc] ) {
            try {
                this.userHandler[openFunc]();
            } catch(e) {
                self.__client.displayHandlerError(e);
            }
        }
    },
    
    self.__responseHandler.onSend = function(callName) {

       var sendFunc = callName+'Send';
        if ( this.userHandler[sendFunc] ) {
            try {
                this.userHandler[sendFunc]();
            } catch(e) {
                self.__client.displayHandlerError(e);
            }
        }
    },
    
    self.__responseHandler.onProgress = function(callName) {

        var progressFunc = callName+'Progress';
        if ( this.userHandler[progressFunc] ) {
            try {
                this.userHandler[progressFunc]();
            } catch(e) {
                self.__client.displayHandlerError(e);
            }
        }
    },
    
    self.__responseHandler.onLoad = function(response, callName) {

        try {
			var tmp = response.replace(/setTimeout/g,"wait");
			//This is really quick
			eval("var dataFunc = "+response);
			var t = this;

            try {
				//This could take awhile, so do the rest in a callback
                dataFunc(function(data) 
                {
                    if ( t.userHandler[callName] ) {
                        try {
                            t.userHandler[callName](data);
                        } catch(e) {
                            // Error in handler method (e.g. syntax error) - display it
                            self.__client.displayHandlerError(e);
                        }
                    } else {
                        Log.error('Your handler must define a method '+callName);
                    }
                });
            } catch (e) {
                e.client = self.__responseHandler.context.__remoteClass;
                e.call = callName;
                
                if ( e.name == 'Server_Error' ) {

	              try {
	                    this.userHandler['failure'](e);
	                } catch(ex) {
						// Error in handler method (e.g. syntax error) - display it
	                    this.context.serverErrorFunc(e);
	                }

                } else {
                
                    var errorFunc = callName+'Error';
                    
                    if ( this.userHandler[errorFunc] ) {
                        try {
                            this.userHandler[errorFunc](e);
                        } catch(e) {
                            // Error in handler method (e.g. syntax error) - display it
                            self.__client.displayHandlerError(e);
                        }
                    } else {
                        this.context.applicationErrorFunc(e);
                    }

                }

            }

        } catch (e) {

            e.name = 'Server_Error';
            e.code = 2006;
            e.response = response;
            e.client = self.__responseHandler.context.__remoteClass;
            e.call = callName;

			self = this;

			if (!self.userHandler && self.__responseHandler)
			{
				self = self.__responseHandler;
			}

			if (self && self.userHandler && self.userHandler['failure'])
			{
                try {
                    self.userHandler['failure'](e);
                } catch(ex) {
					// Error in handler method (e.g. syntax error) - display it
					this.__client.displayHandlerError(ex);
                }
			}
	            
			if (self && self.context && self.context.serverErrorFunc)
			{
				self.context.serverErrorFunc(e);
	        }
        }
       
    };
    
    self.__responseHandler.onError = function(e, callName) {
        e.client = self.__responseHandler.context.__remoteClass;
        e.call = callName;
        this.context.clientErrorFunc(e);

        var errorFunc = callName+'Error';
        if ( this.userHandler[errorFunc] ) {
            try {
                this.userHandler[errorFunc](e);
            } catch(e) {
				Log.error(e);
            }
        }

        if ( this.userHandler['failure'] ) {
            try {
                this.userHandler['failure'](e);
            } catch(ex) {
				// Error in handler method (e.g. syntax error) - display it
				self.__client.displayHandlerError(ex);
            }
        }

    };
    
}

var ajaxRequestList = new Array();
var ajaxCurrentRequestId   =  1;

function actionproxy(server) {
    
    var oParent = new JPSpan_RemoteObject();
    var callback = null;

    if ( arguments[0] ) {
		callback = arguments[0];

		var apCallback = function() {};
        apCallback.prototype = ActionCallback.prototype;

		apCallback.cleanup = function()
		{
			oParent.__cleanup();
			delete ajaxRequestList[oParent.requestId];
			oParent = null; 
			callback = null;
			apCallback = null;
		}

        apCallback.invoke = function(response) {
			callback.invoke(response);
			this.cleanup();
        };

        apCallback.failure = function(error) {
			callback.failure(error);
			this.cleanup();
        };
        
        if (!callback.sync)
	        oParent.Async(apCallback);
	    else
	        oParent.Sync();
    }
    
	oParent.__serverurl = arguments[1]+'pages/jpspan.php?actionproxy';
    
    oParent.__remoteClass = 'actionproxy';

    oParent.__request = new JPSpan_Request_RawPost(new JPSpan_Encode_Xml());
    
    // @access public
    oParent.invoke = function() {
        var url = this.__serverurl+'/invoke/';
        var returnVal = this.__call(url,arguments,'invoke');

        this.requestId = ++ajaxCurrentRequestId;
        ajaxRequestList[this.requestId] = this.__client;

		if (callback.sync)
		{
			if (!returnVal)
			{
				returnVal = new Error('Syntax Error in Server Response : '+this.GetXMLHttp().responseText);
			}

			apCallback.invoke(returnVal);
		}
		
        return returnVal;
    };
    
    return oParent;
}

function ajaxRequestCleanup()
{
	// In IE, if you close a browser window in the middle of an XMLHTTP request,
	//	the browser can choke on the request.  Since IE uses at most 2 connections
	//  for a single host, this choking then prevents the browser from reaching
	//	the server that the XMLHTTP request was destined for
	// So, ajax.js keeps track of the outstanding XMLHTTP requests, and 
	for (var id in ajaxRequestList)
	{
		if ( ajaxRequestList[id] && ajaxRequestList[id] instanceof JPSpan_HttpClient )
		{
			ajaxRequestList[id].abort();
			delete ajaxRequestList[id];
		}
	}
}


// /var/www/trailfire.com-release/content/client/lib/proxy.js 

function ActionCallback() {}
 
ActionCallback.prototype = {

    // Before XMLHttpRequest.open()
    invokeInit : function () {},
    
    // After XMLHttpRequest.open()
    invokeOpen : function () {},
    
    // After XMLHttpRequest.send()
    invokeSend : function () {},
    
    // Loading....
    invokeProgress : function () {},
    
    invoke : function (response) { alert("ActionCallback.invoke"); }
}

function ActionProxy(sync) {

    this._sync = sync;
    
    this._setup = function(aid, parameters, callback) {
        
        // Create the wrapper callback that invokes the callback event listeners: 
        var wrapperCallback = {
            invoke: function(response) {
                if (this.sync)
	                callback.invoke(response);
                else 
                    wait(function() { callback.invoke(response); }, 1, aid+"-callback");
            },
            
            failure: function(error) {
            	if (callback.failure)
            		callback.failure(error);
            }
        };
        
        // Give control back to the action proxy object: 
        if (this._sync) 
            wrapperCallback.sync = true; 

        return wrapperCallback;
    };

    this._invoke = function(aid, parameters, success, failure) {
        var self = this;
        
        // Prepare for collecting diagnostics data:
        var diagnostics = new Array();
        
        // Prepare the server callback:
        function callback() {};

        callback.prototype = ActionCallback.prototype;

        callback.invoke = function(response) {
            var latency = (new Date().getTime() - startTime);
                     
            if (typeof(response) == "object" && response["rpc"]) {

                if ("success" == response["rpc"]["what"] && success) {
	                success(response);
	            } else if (response["rpc"]["what"] == "failure" ) {
					if (failure) 
	                	failure(response);
	            	 else 
	                	throw new Error("No request failure handler.") 	                	
	            }
	        }        
	         
	        // Server said nonsense:       
            else if (failure)
            	failure(response);
            else
	            throw new Error("Network error during service invocation, no failure handler.");
        };

        callback.failure = function(error) {
            if (failure) {
                failure(error,parameters);
            }
        };
        
        // Make the request:
        var wrapperCallback = this._setup(aid, parameters, callback);
        var startTime = new Date().getTime();

        (new actionproxy(wrapperCallback,PIPESTONE)).invoke(aid, parameters);
    };
}

ActionProxy.prototype = {

    login: function(username, password, remember, success, failure) {
        var aid = "login";
        var parameters = new Array();
        parameters["aid"] = aid;
        parameters["login"] = username;
        parameters["password"] = password;
        parameters["rememberme"] = remember;
        this._invoke(aid, parameters, success, failure);
    },

    autoLogin: function(success, failure) { //auto login (no interest lookup)
        var aid = "profile";
        var parameters = new Array();
        parameters["aid"] = aid;
        parameters["simple"] = "1";
        this._invoke(aid, parameters, success, failure);
    }, 

    profile: function(success, failure) { //auto login and lookup interests for user
        var aid = "profile";
        var parameters = new Array();
        parameters["aid"] = aid;
        this._invoke(aid, parameters, success, failure);
    }, 

	getUserHint: function(success, failure) { //get the current contextual help for the user
        var aid = "get-user-hint";
        var parameters = new Array();
        parameters["aid"] = aid;
        parameters["target"] = 'ext';
        this._invoke(aid, parameters, success, failure);		
	},
	
    logout: function(success, failure) {
        this._invoke("logout", new Array(), success, failure); 
    },

	error: function(type,success,failure) { //for testing errors
        var aid = "error";
        var parameters = new Array();
        parameters["type"] = type;
        this._invoke(aid, parameters, success, failure);
	},

    annotate: function(id, tempId, url, mimeType, element, offsets, subject, content, interests,  trailOwner,charSet, isPublic, success, failure) {
        if (!url)
            throw new Error("Missing URL");
        if (!element)
            throw new Error("Missing element path");
            
        var parameters = new Array(); 
        parameters["tempId"] = tempId; 
        parameters["url"] = url; 
        parameters["mimeType"] = mimeType;
        parameters["element"] = element;
        parameters["actionType"] = "Tagging";

        if (offsets) {
            parameters["x"] = offsets["x"];
            parameters["y"] = offsets["y"];
        }
        
        if (subject)
            parameters["subject"] = subject;
        else
        	parameters["subject"] = "untitled";

		if (trailOwner)
			parameters["trailOwner"] = trailOwner;
		else
			parameters["trailOwner"] = "";
			
        if (content)
            parameters["content"] = content;
        else
        	parameters["content"] = "";
            
        if (interests && (interests.length>0))
            parameters["interest"] = interests[0];     

        var aid = "annotate";
        if (id) {
            parameters["id"] = id;       
            aid = "save-bubble";
        }
        else
            aid = "create-bubble";

        parameters["charSet"] = charSet;
        parameters["public"] = isPublic;

        this._invoke(aid, parameters, success, failure);
    },

    annotateSaveMin: function(id, element, offsets, size, success, failure) {
        if (!element)
            throw new Error("Missing element path");
            
        var parameters = new Array(); 
        parameters["id"] = id; 
        parameters["element"] = element;
        if (offsets) {
            parameters["x"] = offsets["x"];
            parameters["y"] = offsets["y"];
        }
        if (size) {
            parameters["w"] = size["x"];
            parameters["h"] = size["y"];
        }

        this._invoke("save-bubble-partial", parameters, success, failure);
    },
    
    transferBubbles: function(cids, fromInterestId, toInterestId, afterBubbleId, success, failure) {
        var parameters = new Array(); 
        parameters['ids'] = cids;
        parameters['fromInterestId'] = fromInterestId;
        parameters['toInterestId'] = toInterestId;
        parameters['afterBubbleId'] = afterBubbleId;
        this._invoke("transfer-bubbles", parameters, success, failure);
    },  
    
    arrangeBubbles: function(cids, interestId, afterBubbleId, success, failure) {
        var parameters = new Array(); 
        parameters['ids'] = cids;
        parameters['interestId'] = interestId;
        parameters['afterBubbleId'] = afterBubbleId;
        this._invoke("arrange-bubbles", parameters, success, failure);
    },  
    
    deleteBubble: function(id, success, failure) {
        var parameters = new Array(); 
        parameters['id'] = id;
        this._invoke("destroy-bubble", parameters, success, failure);
    },

    /******
     * send(bubbleId,sendTo,successProc,failureProc): 
     *  send a bubble (and share the trail) with the username or email 'sendTo'
     ******/
    send: function(bubbleId, sendTo, success, failure) {
        var parameters = new Array(); 
        parameters['key'] = bubbleId;
        parameters['sendTo'] = sendTo;
        this._invoke("send", parameters, success, failure);
    },

    sendInsecure: function(bubbleId, sendTo, success, failure) {
        var parameters = new Array(); 
        parameters['key'] = bubbleId;
        parameters['sendTo'] = sendTo;
        parameters['securityConfirmation'] = sendTo;
        this._invoke("send", parameters, success, failure);
    },
    
    /*****
     * contacts(success,failure): fetch the list of the users' contacts (friends)
     *****/
    contacts: function(success, failure) {
        var parameters = new Array(); 
        this._invoke("contacts", parameters, success, failure); 
    },

    addContacts: function(contacts, success, failure) {
        var parameters = new Array(); 
		parameters['contacts'] = contacts;
        this._invoke("add-contacts", parameters, success, failure); 
    },
    
    
    /* retrieve bubbles for the given set of interests */
    lookupByInterest: function(interestIds, success, failure) {
        var parameters = new Array(); 
        parameters["interests"] = interestIds;
        parameters["actionType"] = "";
        this._invoke("lookup-interest", parameters, success, failure); 
    },
    
     /* retrieve bubbles for the given URL */
     lookupByURL: function(url, showPublic, success, failure, token) {
        var parameters = new Array(); 
        parameters["url"] = url.toString(); 
        parameters["showPublic"] = showPublic;
        parameters["actionType"] = "";
        if (token)
            parameters["token"] = token;
        this._invoke("lookup-url", parameters, success, failure); 
    },
    
    /* look up trail and url for a bubble, return all bubbles from the trail at that url */
    lookupByBubbleId : function(bubbleId, showPublic, success, failure, token) {
        var parameters = new Array(); 
        parameters["bubbleId"] = bubbleId; 
        parameters["showPublic"] = showPublic;
        parameters["actionType"] = "";
        if (token)
            parameters["token"] = token;
        //Log.debug("ActionProxy._lookupByBubbleId()  bubbleId = " + bubbleId);
        this._invoke("lookup-bubble-id", parameters, success, failure); 
    },
    
    /* look up the first trail mark and other page marks residing on the 
     * same trail mark url, for the given trail 
     */
    lookupByTrailId: function(trailId, showPublic, success,failure, token){
        var parameters = new Array(); 
        parameters["trailId"] = trailId; 
        parameters["showPublic"] = showPublic;
        parameters["actionType"] = "";
        if (token)
            parameters["token"] = token;
        Log.debug("ActionProxy._lookupByTrailId()  id = " + trailId);
        this._invoke("lookup-page-marks-by-trail", parameters, success, failure); 
	},
    
   /* look up new marks since timestamp, optionally with constraints */
    lookupByTimestamp: function(timestamp, constraints, success, failure, token){
        var parameters = new Array(); 
        parameters["timestamp"] = timestamp; 
        parameters["constraints"] = constraints;
        if (token)
            parameters["token"] = token;
        Log.debug("ActionProxy._lookupByTimestamp()  timestamp = " + timestamp);
        this._invoke("lookup-mark-by-timestamp", parameters, success, failure); 
	},

    createInterest: function(label, publicFlag, success, failure, actionType) { 
        if (!label || 1 > label.length)
            throw new Error("Missing interest label");
        var parameters = new Array();
        parameters["label"] = label;
        parameters["public"] = publicFlag;
        parameters["actionType"] = actionType;
        this._invoke("create-interest", parameters, success, failure); 
    }, 

	// Same as destroyInterest, but takes an interest ID instead of an interest object
    destroyInterestId: function(interestId, success, failure) { 
        var parameters = new Array(); 
        parameters["interestId"] = interestId;
        this._invoke("destroy-interest", parameters, success, failure); 
    },

    destroyInterest: function(interest, success, failure) { 
        var parameters = new Array(); 
        parameters["interestId"] = interest.getId(); 
        this._invoke("destroy-interest", parameters, success, failure); 
    },

    lookupInterestChanges: function(lastTimestamp, getActivityInfo, success, failure) { 
        var parameters = new Array(); 
        parameters["action"] = "findChanges";
        parameters["lastTimestamp"] = lastTimestamp;
        parameters["getActivityInfo"] = 0;
        if (getActivityInfo)
        	parameters["getActivityInfo"] = 1;
        this._invoke("poll-interests", parameters, success, failure); 
    },

    subscribeInterest: function(interestId, success, failure) { 
        var parameters = new Array(); 
        parameters["interestId"] = interestId; 
        this._invoke("subscribe-to-interest", parameters, success, failure); 
    },

	// This function is the same as the following function, but it takes an
	// interest ID instead of an interest object
    unsubscribeInterestId: function(interestId, success, failure) { 
        var parameters = new Array(); 
        parameters["interestId"] = interestId; 
        this._invoke("unsubscribe-from-interest", parameters, success, failure); 
    },

    unsubscribeInterest: function(interest, success, failure) { 
        var parameters = new Array(); 
        parameters["interestId"] = interest.getId(); 
        parameters["owner"] = 0; //TODO: remove this param when server side updated.ff 
        this._invoke("unsubscribe-from-interest", parameters, success, failure); 
    },

	addFavorite: function(userId, favId, favType, favName, success, failure) {
		var parameters = new Array();
		parameters["userId"] = userId;
		parameters["favId"] = favId;
		parameters["favType"] = favType;
		parameters["favName"] = favName;
		this._invoke("add-favorite", parameters, success, failure);
	},

	deleteFavorite: function(userId, favId, favType, success, failure) {
		var parameters = new Array();
		parameters["userId"] = userId;
		parameters["favId"] = favId;
		parameters["favType"] = favType;
		this._invoke("delete-favorite", parameters, success, failure);
	},
    
    addUserAsContact: function(ownerId, userId, success, failure) {
    	var parameters = new Array();
    	parameters['ownerId'] = ownerId;
		parameters["userId"] = userId;
    	this._invoke("add-user-as-contact", parameters, success, failure);
    },
    
    addContactByName: function(ownerId, userName, success, failure) {
    	var parameters = new Array();
    	parameters['ownerId'] = ownerId;
		parameters["userName"] = userName;
    	this._invoke("add-contact-by-name", parameters, success, failure);
    },
    
    deleteUserAsContact: function(ownerId, userId, success, failure) {
    	var parameters = new Array();
    	parameters['ownerId'] = ownerId;
		parameters["userId"] = userId;
    	this._invoke("delete-user-as-contact", parameters, success, failure);
    },
    
    deleteContacts: function(userId, contactIds, success, failure) {
    	var parameters = new Array();
		parameters["userId"] = userId;
    	parameters['contactIds'] = contactIds;
    	this._invoke("delete-contacts", parameters, success, failure);
    },
    
    addEmailContacts: function(contactAddresses, success, failure) {
        var parameters = new Array(); 
    	parameters['contactAddresses'] = contactAddresses;
        this._invoke("add-email-contacts", parameters, success, failure); 
    },

    deleteEmailContacts: function(userId, contactAddresses, success, failure) {
    	var parameters = new Array();
		parameters["userId"] = userId;
    	parameters['contactAddresses'] = contactAddresses;
    	this._invoke("delete-email-contacts", parameters, success, failure);
    },
    
    addToContactGroup: function(userId, contactIds, groupName, success, failure) {
    	var parameters = new Array();
		parameters["userId"] = userId;
    	parameters['contactIds'] = contactIds;
    	parameters['groupName'] = groupName
    	this._invoke("contact-group-add", parameters, success, failure);
    },
    
    deleteFromContactGroup: function(userId, contactIds, groupName, success, failure) {
    	var parameters = new Array();
		parameters["userId"] = userId;
    	parameters['contactIds'] = contactIds;
    	parameters['groupName'] = groupName
    	this._invoke("contact-group-delete", parameters, success, failure);
    },
    
    updateContactsWatch: function(userId, contactIds, success, failure) {
    	var parameters = new Array();
		parameters["userId"] = userId;
    	parameters['contactIds'] = contactIds;
    	this._invoke("update-contacts-watch", parameters, success, failure);
    },
    
    setInterestLabel: function(interestId, label, success, failure) { 
        var parameters = new Array(); 
        parameters["interestId"] = interestId; 
        parameters["label"] = label; 
        this._invoke("set-interest-label", parameters, success, failure); 
    }, 
    
    setInterestDescription: function(interestId, description, success, failure) { 
        var parameters = new Array(); 
        parameters["interestId"] = interestId; 
        parameters["description"] = description; 
        this._invoke("set-interest-description", parameters, success, failure); 
    }, 

	setInterestVisibility: function(interestId, visibility, success, failure) {
		var parameters = new Array();
		parameters['interestId'] = interestId;
		parameters["visibility"] = visibility;
		this._invoke("set-interest-visibility", parameters, success, failure);
	},
    
    makePrivate: function(interestId, success, failure) { 
        var parameters = new Array(); 
		parameters['interestId'] = interestId;
        this._invoke("set-interest-private", parameters, success, failure); 
    }, 
    
    makePublic: function(interestId, success, failure) { 
        var parameters = new Array(); 
		parameters['interestId'] = interestId;
        this._invoke("set-interest-public", parameters, success, failure); 
    }, 

	lookupCategories: function(success,failure)
	{
		this._invoke("all-categories", new Array(), success, failure);
	},

    setTrailCategories: function(trailId, categories, success, failure) { 
        var parameters = new Array(); 
        parameters["trailId"] = trailId; 
        parameters["categories"] = categories; 
        this._invoke("set-trail-categories", parameters, success, failure); 
    },     

    setTrailInfo: function(trailId, trailname, description, success, failure) {
        var parameters = new Array(); 
        parameters["trailId"] = trailId;
        parameters["trailname"] = trailname; 
        parameters["description"] = description; 
    	this._invoke("set-trail-info", parameters, success, failure);
    },
    
    setTrailPermission: function(trailId, permission, clearSharing, success, failure) { 
        var parameters = new Array(); 
        parameters["trailId"] = trailId; 
        parameters["permission"] = permission; 
        parameters["clearSharing"] = clearSharing; 
        this._invoke("set-trail-permission", parameters, success, failure); 
    },     

    setTrailSharing: function(trailId, visibility, editability, success, failure) { 
        var parameters = new Array(); 
        parameters["trailId"] = trailId; 
        parameters["visibility"] = visibility;
        parameters["editability"] = editability;
        this._invoke("set-trail-sharing", parameters, success, failure); 
    },     

    setTrailAllowSecret: function(trailId, allowSecret, success, failure) {
        var parameters = new Array(); 
        parameters["trailId"] = trailId; 
        parameters["allowSecret"] = allowSecret;
        this._invoke("set-trail-allow-secret", parameters, success, failure); 
    },
    
    recordDiagnostics: function(data, success, failure) {
		this._invoke("record-diagnostics", data, success, failure);
    },

	//recordStatistics - a hook into the server side logging facility.  Used to log
	// user activity that does not otherwise stimulate a server communication
    recordStatistics: function(action_id, mark_id, trail_id, success, failure) {
    	var parameters = new Array(); 
    	parameters["action_id"] = action_id; 
    	parameters["mark_id"] = mark_id; 
    	parameters["trail_id"] = trail_id; 
    	this._invoke("record-statistics", parameters, success, failure);
    },
    
    /**********
     * Trail feedback APIs
     *********/
    rateTrail: function(interestId, rating, success, failure) { 
        var parameters = new Array(); 
		parameters["interestId"] = interestId;
		parameters["rating"] = rating;
        this._invoke("rate-trail", parameters, success, failure); 
    }, 
    
    addComment: function(interestId, comment, success, failure) { 
        var parameters = new Array(); 
		parameters["interestId"] = interestId;
		parameters["comment"] = comment;
        this._invoke("add-trail-comment", parameters, success, failure); 
    }, 
    
    editComment: function(interestId, commentId, comment, success, failure) { 
        var parameters = new Array(); 
		parameters["interestId"] = interestId;
		parameters["commentId"] = commentId;	
		parameters["comment"] = comment;
        this._invoke("edit-trail-comment", parameters, success, failure); 
    }, 
    
    deleteComment: function(interestId, commentId, success, failure) { 
        var parameters = new Array(); 
		parameters["interestId"] = interestId;
		parameters["commentId"] = commentId;
        this._invoke("delete-trail-comment", parameters, success, failure); 
    }, 

	// mark comments
	lookupMarkComments: function(markId, success, failure)
	{
        var parameters = new Array(); 
		parameters["markId"] = markId;
        this._invoke("lookup-mark-comments", parameters, success, failure); 
	},
	
    addMarkComment: function(markId, comment, lastCommentId, success, failure) { 
        var parameters = new Array(); 
		parameters["markId"] = markId;
		parameters["comment"] = comment;
        this._invoke("add-mark-comment", parameters, success, failure); 
    }, 
    
    editMarkComment: function(markId, commentId, comment, success, failure) { 
        var parameters = new Array(); 
		parameters["markId"] = markId;
		parameters["commentId"] = commentId;	
		parameters["comment"] = comment;
        this._invoke("edit-mark-comment", parameters, success, failure); 
    }, 
    
    deleteMarkComment: function(markId, commentId, success, failure) { 
        var parameters = new Array(); 
		parameters["markId"] = markId;
		parameters["commentId"] = commentId;
        this._invoke("delete-mark-comment", parameters, success, failure); 
    }, 

	getTrailTabContent: function(tab, category, page, limit, success, failure) {
		var parameters = new Array();
		parameters["tab"] = tab;
		parameters["cat"] = category;
		parameters["page"] = page;
		parameters["limit"] = limit;
        this._invoke("tab-trails", parameters, success, failure); 
	},

	getUserTabContent: function(tab, category, page, limit, success, failure) {
		var parameters = new Array();
		parameters["tab"] = tab;
		parameters["cat"] = category;
		parameters["page"] = page;
		parameters["limit"] = limit;
        this._invoke("tab-users", parameters, success, failure); 
	}, 

	deleteAvatarImage: function(success, failure) {
		var parameters = new Array();
        this._invoke("delete-avatar", parameters, success, failure); 
	},

	updateNotifications: function(filters, success, failure) {
		var parameters = new Array();
		parameters['filters'] = filters;
        this._invoke("update-notifications", parameters, success, failure); 
	},

	lookupSimilarMarks:function(url, markId, analyze, content, success, failure) {
		var parameters = new Array();
		parameters['url'] = url;
		if (markId)
			parameters['markId'] = markId;
		parameters['analyze'] = analyze;
		parameters['content'] = content;
        this._invoke("lookup-similar-marks", parameters, success, failure);
	},

	lookupSimilarPages:function(url, content, success, failure) {
		var parameters = new Array();
		parameters['url'] = url;
		parameters['content'] = content;
        this._invoke("lookup-similar-pages", parameters, success, failure);
	},

	addUserComment: function(userId, content, success, failure) {
		var parameters = new Array();
		parameters['userId'] = userId;
		parameters['content'] = content;
        this._invoke("add-user-comment", parameters, success, failure);
	},

	editUserComment: function(commentId, content, success, failure) {
		var parameters = new Array();
		parameters['commentId'] = commentId;
		parameters['content'] = content;
        this._invoke("edit-user-comment", parameters, success, failure);
	},

	deleteUserComment: function(commentId, userId, success, failure) {
		var parameters = new Array();
		parameters['commentId'] = commentId;
		parameters['userId'] = userId;
        this._invoke("delete-user-comment", parameters, success, failure);
	},
	
    /**********
     * Trail Tagging APIs
     *********/	

	lookupUserTags: function(userId,success,failure)
	{
		var parameters = new Array();
		if (userId)
			parameters['userId'] =userId;				
		this._invoke("get-tags",parameters,success,failure);		
	},

	lookupTrailTags: function(trailId,success,failure)
	{
		var parameters = new Array();
		parameters['trailId'] =trailId;		
		this._invoke("get-tags",parameters,success,failure);		
	},

	setTrailTags: function(trailId,tags,success,failure)
	{
		var parameters = new Array();
		parameters['trailId'] =trailId;
		parameters['replace'] ='1';
		parameters['tags'] =tags.split(',');
		this._invoke("tag-trail",parameters,success,failure);		
	}
}

ActionProxy.Diagnostics = {

	counter: 0,
	
	interval: 0, 

	requests: new Array(), 
	
	traces: new Array(), 

	recordRequest: function(latency, response) {
		var data = new Array();
        data[data.length] = latency; 
        data[data.length] = response["rpc"]["timestamp"]; 
        data[data.length] = _platform.memoryFootprint();
        this.requests[this.requests.length] = data;
        this.flush();
	}, 
	
	recordTrace: function(data) {
		this.traces[this.traces.length] = data;
		this.flush();
	}, 

	recordProfilerTrace: function(profilerTrace) {
		this.traces[this.traces.length] = profilerTrace;
		this.flush();
	}, 
	
	flush: function() { 
		++this.counter;
		if (this.counter > this.interval) {
			this.counter = 0;
			var data = new Array();
			if (0 < this.requests.length)
                data["requests"] = this.requests;
            if (0 < this.traces.length)
                data["traces"] = this.traces;
			this.requests = new Array();
			this.traces = new Array();
			(new ActionProxy).recordDiagnostics(data, 
				function success(response)
				{
				}, 
				function failure(error)
				{
				});
		}
	}
}


// /var/www/trailfire.com-release/content/client/model/browser-model.js 

/* Browser Model 
	
	user model
	list of page models
	active page

	browser type/platform/os
	client version
*/
function BrowserModel(server,version,platform,imageServer,videoServer)
{
	ModelBase.call(this,"BrowserModel");
	ActionProxy.call(this);

	//imageServer and ajaxServer are optional params,
	// just use the single server if they are not present
	if (!imageServer)
		imageServer = server;
		
	if (!videoServer)
		videoServer = server.replace('http://','') + "videoMarks";

	this._getId = function() { return platform+" v"+version+" "+server; }

	this._optionDefaults = 
		{
			"pageDecorations.showBubbles"	:true,
			"pageDecorations.showAllBubbles":false,
			"postInstall.firstUse"			:true,
			"hints.search"					:true,
			"hints.myStuff"					:true,
			"hints.cloud"					:true,
			"hints.bubble"					:true,
			"hints.site"					:true,
			"trails.defaultPrivate"			:false,	
			"bubbles.showHints"				:true,
			"bubbles.viewHintOffset"		:0,
			"bubbles.editHintOffset"		:0,
			"bubbles.slideshow"				:false,
			"bubbles.slideshowLoop"			:false,
			"bubbles.slideshowDelay"		:5000
		};

	var user = new UserModel(this);
	var browserWindows = Utils.newArray();
	var activeBrowserWindow = null;
	var ajax_queue = new Array();
	var optionalUpgradeAvailable = false;
	var mandatoryUpgradeAttempted = false;
	var upgradeMessage ="";
	
	var browserWindowListener = bind(function(event)
	{
		switch(event.name) 
		{
			case "close":
					event.browserWindow.ignore(browserWindowListener);
					delete browserWindows[event.browserWindow.getId()];
					if (activeBrowserWindow == event.browserWindow)
						activeBrowserWindow = null;
				break;
		}	
	},this);


	this._quit = function()
	{
		for (var i in browserWindows)
		{
			if (browserWindows[i].ignore)
			{
				browserWindows[i].ignore(browserWindowListener);
				browserWindows[i].close();
			}
		}

		this._fire({name:"quit",browser:this});

		activePage = null;

		this._destroy();

	    clearWaitQueue();

		//as long as we are clearing all DOM memory references when the page goes away,
		// there is no reason to destroy the user object (which takes allot of time when
		// quitting in IE
		if (Log.memory)
		{
			user.destroy();

			if (classBase_objectCount != 0)
			{
				if (window.originalAlert) //in the proxy
					window.originalAlert(Utils.classDump());
				else
					alert(Utils.classDump());

				//inspect classbase_objects
			}
		}

	}
	
	this._newBrowserWindow = function(bwId)
	{
		Log.debug("BrowserModel._newBrowserWindow() for id: " + bwId);
		if (!browserWindows[bwId])
		{
			browserWindows[bwId] = new BrowserWindowModel(this,bwId);
			browserWindows[bwId].listen(browserWindowListener);
			activeBrowserWindow = browserWindows[bwId];
			this._fire({name:"browser-window", browserWindow:browserWindows[bwId]});
		}
		
		return browserWindows[bwId];
	}
	
	this._getBrowserWindow = function(bwId)
	{
		return browserWindows[bwId];
	}

	this._getActiveBrowserWindow = function()
	{
		return activeBrowserWindow;
	}

	this._getActivePage = function()
	{
		var page = null;
		var browserWindow = this.getActiveBrowserWindow();
		if (browserWindow){
			page = browserWindow.getActivePage();
		}
		return page;
	}

	this._closeBrowserWindow = function(bwId)
	{
		if (!browserWindows[bwId])
			return;
		
		browserWindows[bwId].ignore(browserWindowListener);
		browserWindows[bwId].close();
		delete browserWindows[bwId];
		
		for (var i in browserWindows)
		{
			activeBrowserWindow = browserWindows[i];
			return;
		}
	}

	this._browserAlert = function(message)
	{
		alert(message);
	}

	this._browserConfirm = function(message,callback)
	{
		callback(confirm(message));
	}

	this._alert = function(title,message)
	{
		if (activeBrowserWindow)
			return activeBrowserWindow.alert(title,message)
		else
			this._browserAlert(message);
	}
	
	this._confirm = function(title,message,callback)
	{
		if (activeBrowserWindow)
			return activeBrowserWindow.confirm(title,message,callback)
		else
			this._browserConfirm(message,callback);
	}
	
	this._getVersion = function() { return version; }
	this._getServer  = function() { return server;  }
	this._getImageServer  = function() { return imageServer;  }
	this._getVideoServer  = function() { return videoServer;  }
	this._getPlatform  = function() { return platform;  }

	this._urlAtServer = function(url) { 

		if (url)
		{
			// does the url match server domain
	    	var serverString = server.toLowerCase();
	    	var urlServer = url.substring(0,server.length).toLowerCase();
			if (urlServer==serverString)
				return true;
		}	
		
		return false;
	}

	this._toggleOption = function(key)
	{
		this._setOption(key,!this._getOption(key));
	}
	
	this._optionChanged = function(optionName,optionValue,previousValue)
	{
		this._fire({name:"option-change", option:optionName, value:optionValue, was:previousValue});
	}

	this._getUser = function()
	{
		return user;
	}

	this._clientVersion = function() { return version; }

	this._addVersion = function(parameters)
	{
	    parameters["v"] = new Array();
	    parameters["v"]["js"] = version;
	    parameters["v"]["site"] = this._clientVersion();
	}

	this._addUser = function(parameters)
	{
		if (user.isLoggedIn())
			parameters["u"] = user.getUserName();		
	}	

	// functions to be overloaded by platform specific browser class
	this._getOption = function (optionName)
	{
		var val = Utils.readCookie(window.document,optionName);
		if (val != null)
		{
			if (val == "true")
				return true;
	
			if (val == "false")
				return false;
	
			return val;
		} else if (typeof(this._optionDefaults[optionName]) != "undefined") {
			return this._optionDefaults[optionName]; //default value
		} else {
			return false;
		}
	}
	
	this._setOption = function (optionName,optionValue)
	{
		var oldValue = this._getOption(optionName);
		if (optionValue != oldValue)
		{
	
			Utils.setCookie(optionName,optionValue,1000 * 60 * 60 * 24 * 30,window.document);
			this._optionChanged(optionName,optionValue,oldValue);
		}
	}

	this._event = function(jsWindow,event)
	{
		if (typeof(event) != "undefined") 
		{
			e = event;
		} else {
			e = jsWindow.event;
		}

		try 
		{
			if (e && (!e.target)) e.target=e.srcElement;  
		} catch (ex) { ;} //fails on firefox
		
		return e;
	}

	this._open = function(url,name,options,page)
	{
		var w = page ? page.getWindow() : window;	
		w.open(url,name,options);
	}

	this._isDemo = function()
	{
		return false;
	}

	this._isSite = function()
	{
		return false;
	}	

	this._useShim = function()
	{
		return false;
	}

	this._clientUpgrade = function(silent)
	{
		return false;
	}

	this._navigate = function(jsWindow,url)
	{
		jsWindow.document.location.href=url;
	}

	this._refresh = function(jsWindow)
	{
		try
		{
			jsWindow.location.reload( true );
		} catch (ex) {
			jsWindow.location.replace = jsWindow.document.location.href;
		}
	}

	this._log = function(message)
	{
		;
	}
	
	this._debug = function(message)
	{
		;
	}

	this._showSidebar = function(jsWindow,fShow)
	{
		return false;
	}
	
	this._optionalUpgrade = function(message)
	{
		if (optionalUpgradeAvailable || mandatoryUpgradeAttempted) { return; }
		optionalUpgradeAvailable=true;
		upgradeMessage = message;
		this._fire({name:"upgrade-available"});
	}	
	this._getUpgradeMessage = function()
	{
		return upgradeMessage;
	}
	this._isUpgradeOptional = function()
	{
		return optionalUpgradeAvailable;
	}
	
	this._isUpgradeMandatory = function()
	{
		return (mandatoryUpgradeAttempted);
	}
	
	this._confirmMandatoryUpgrade = function()
	{
        this._browserConfirm(upgradeMessage,
        bind(function(fOk)
        {
        	if (fOk)
	            this._clientUpgrade(false);
        },this));
	}

	this._mandatoryUpgrade = function(message)
	{	
		if (mandatoryUpgradeAttempted) { return; }
		mandatoryUpgradeAttempted=true;
		upgradeMessage = message;
 		this._fire({name:"upgrade-available"});
        
        //disable further server communication
        this._invoke = bind(function(aid,parameters,success,failure)
        { 
			var error = {rpc:{why:'Your trailfire extension needs to be upgraded.'}};
			if (this._sync)
				failure(error)
			else
	        	wait(function(){failure(error);},1,"upgrade failure");
        },this);

		user.notifyLoggedOut(true);
	}

	this._checkForUpgrade = function(response)
	{
        // Check whether the server supports versioning at all:
        if (!response["rpc"]["version"])
            return;
            
        // Check whether the server has indicated mismatched versions:
        //if (_platform.doVersionsMatch(response["rpc"]["version"]))
        if (this._getVersion() == response["rpc"]["version"])
        {
             return;
        }
        // Parse the server advice:
        var mandatory = response["rpc"]["version"]["mandatory"];
        var hint = response["rpc"]["version"]["recommend"];

        // Display the server advice to the user if applicable:
        if (mandatory)
            this._mandatoryUpgrade(hint); 
        else if (hint)
        {
  	        this._optionalUpgrade(hint);
        }
	}

	//server communication
	this._invoke = function(aid,parameters,success,failure)
	{
		if (Log.ajax && Log.ajaxRequests)
		{	
			var p = new Array();
			for (var i in parameters)
				p.push(i+":"+parameters[i]);
			Log.debug("##>Invoke  : "+aid+" ["+p.join(",")+"]");
		}
        // Prepare the server callback:
        function callback() {};
        callback.prototype = ActionCallback.prototype;

        callback.invoke = bind(function(response) 
        {
	         if (typeof(response) == "object" && response["rpc"]) {
	
	            this._checkForUpgrade(response);
	
	            if ("success" == response["rpc"]["what"] && success) 
	            {
					if (Log.ajax && Log.ajaxRequests)
						Log.debug("#!>Success : " + aid + (Log.ajaxData ? (" ==> "+Utils.arrayToString(response)):'') );

	                success(response);
	                
	                //fire off notification of the server communication, for the user model 
	                // who may need to update hints
	                this._fire({name:'invoke',action:aid});
	            } else if (response["rpc"]["what"] == "failure" ) {
	               	if (response["rpc"]["why"] == "Access denied." ) 
	               	{
		                user.notifyLoggedOut(true);
		                user.autoLogin(false);
	            	} 
	            	
					if (failure) 
					{
						if (Log.ajax && Log.ajaxFailures)
							Log.debug("#!>Failure : "+aid+" ==> "+Utils.arrayToString(response));

	                	failure(response);
	                }
	            	 else 
	                	throw new Error("No request failure handler.") 	                	
	            }
	        } else if (failure) { // Server said nonsense
				if (Log.ajax && Log.ajaxFailures)
					Log.debug("#!>Failure : "+aid+" ==> "+Utils.arrayToString(response));

	        	failure(response);
	        } else {
	            throw new Error("Network error during service invocation, no failure handler.");
	        };
		},this);

        callback.failure = function(error) {
            if (failure) {
				if (Log.ajax && Log.ajaxFailures)
					Log.debug("#!>Failure : "+aid+" ==> "+Utils.arrayToString(error));
                failure(error,parameters);
            } else throw new Error("Network error during service invocation, no failure handler.");
        };
        
        // Add version information to the request parameters: 
        this._addVersion(parameters);
        this._addUser(parameters);
                
        // Make the request:
        var wrapperCallback = this._setup(aid, parameters, callback);
        (new actionproxy(wrapperCallback,server)).invoke(aid, parameters);	
	}
	this._canCopyToClipboard = function()
	{
		var canCopy = false;
		if (Controls.detectIE())
		{
			canCopy = true;
		}
		if (Controls.detectFirefox())
		{	
			if (platform != "proxy")
				canCopy = true;
		}
		return canCopy; 
	}
	
	this._copyToClipboard = function(text, doc)
	{
		if (Controls.detectIE())
		{
			if (!doc)
				throw new Error("Called _copyToClipboard with no document");
				
			// This method needs to be implemented by derived browser class.
			var tempElement = Controls.createElement(doc,"button");
			tempElement.innerText = text;
			var textRange = tempElement.createTextRange();
			textRange.execCommand("Copy", null);
			tempElement = null;
			
		}
		else if (Controls.detectFirefox())
		{	
			// This method needs to be implemented by derived browser class.
			gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"] .getService(Components.interfaces.nsIClipboardHelper); 
			gClipboardHelper.copyString(text); 
		}
		else 
			// This method needs to be implemented by derived browser class.
			throw new Error ("_copyToClipboard not implemented for this platform.");
	}	
}

Utils.implement(BrowserModel,ModelBase);
Utils.implement(BrowserModel,ActionProxy);
eval(Utils.declarePublic(
	"BrowserModel",
	{
		newBrowserWindow:1,
		getBrowserWindow:1,
		getActiveBrowserWindow:0,
		getActivePage	:0,
		getVersion		:0,
		getServer		:0,
		getPlatform		:0,
		getImageServer	:0,
		getVideoServer	:0,
		urlAtServer		:1,
		getUser			:0,
		alert			:2,
		confirm			:3,
		browserAlert	:1,
		browserConfirm	:2,
		quit			:0,
		//functions overloaded by specific platforms
		event			:2,
		open			:4,
		useShim			:0,
		navigate		:2,
		refresh			:1,
		getOption		:1,
		setOption		:2,
		isDemo			:0,
		isSite			:0,
		confirmMandatoryUpgrade:0,
		isUpgradeMandatory:0,
		isUpgradeOptional:0,
		getUpgradeMessage:0,
		clientUpgrade   :1,
		log				:1,
		debug			:1,
		showSidebar		:2,
		clientVersion	:0,
		copyToClipboard :2,
		canCopyToClipboard:0
	}
));

// /var/www/trailfire.com-release/content/client/model/page-model.js 

/*

	PageModel(browser) - a subclass used for lower level control and window classes
				    to wrap platform specific calls and relay them to the browser model

*/

function PageModel(browser,modelType)
{
	if (!modelType) { modelType = "PageModel"; }
	ModelBase.call(this,modelType);

	var browserListener = bind(function(event)
	{
		switch(event.name)
		{
			case "quit":
					this._close();
				break;
		}
	},this);
	browser.listen(browserListener);

	var drawCount = 0;

	//in order to keep our hands off of the DOM, sometimes it is necessary
	// to store information about an element - use setElementData and getElementData for that
	var _elementsData = Utils.newArray();
	var _elements = Utils.newArray();
	var _lastElementId = 0;
	
	//in IE we can't add elements to the document
	// before some certain condition are met - 
	// these arrays hold elements that are waiting to be inserted
	var bodyElems = Utils.newArray();
	var headElems = Utils.newArray();
	var removedImages = Utils.newArray();

	this._setElementData = function(element,label,value)
	{
		var id = 0;

		for (var i in _elements)
			if (_elements[i] === element)
			{	
				_elementsData[i][label] = value;		
				return;
			}

		id = ++_lastElementId;
		_elements[id] = element;
		_elementsData[id] = Utils.newArray();
		_elementsData[id][label] = value;		
	}

	this._getElementData = function(element,label)
	{
		var id = 0;

		for (var i in _elements)
			if (_elements[i] === element)
				return _elementsData[i][label]
				
		return false;
	}
		
	this._getWindow=function()			 	 { return window; }
	this._getMasterWindow=function()		 { return window; }
	this._event=function (paramEvent) 		 { return browser.event(window,paramEvent); }
	this._isDemo = function() 				 { return browser.isDemo(); }
	this._open = function(url,name,features) { return browser.open(url,name,features,this); }
	this._navigate = function(url)			 { return browser.open(url); }
	this._getServer = function()			 { return browser.getServer(); }
	this._useShim = function()				 { return false; }

	this._refresh = function()
	{
		browser.refresh(this.getWindow());
	}

	this._getUrl	= function() 
	{
		var w = this._getWindow();
		if (w)
			return w.location.href;
		return null;
	};
	
	this._getStandardizedUrl = function()
	{
		return Utils.standardizeUrl(this.getUrl());
	}

	this._getReferrer= function()
	{
		var d = this._getDocument()
		if (d)
			return d.referrer;
		return null;
	}

	this._getDocument = function()			 
	{ 
		var w=null; 
		w = this._getWindow(); 
		if (w) 
		{
			try
			{
				return w.document; 
			} 
			catch (e) // sometimes doc is protected 
			{
				return null;
			}
			
		}
		return null; 
	}

	this._getCharSet = function()
	{
		var d = this._getDocument();
		if (d)
			return Utils.getCharSet(d);
		return 'UTF-8';
	}

	this._getMimeType = function()
	{
		var currentMimeType = "text/html";
		var doc = this._getDocument();
		if (doc)
		{
			if (doc.contentType)
			{
				currentMimeType = doc.contentType;
			} else {
				currentMimeType = doc.body.childNodes[0].type;
			}
		}
	
		return currentMimeType;
	}

	this._getSelection = function()
	{
		return Utils.getSelection(this.getWindow());
	}

	this._find = function(elemId)			 
	{
		var e = null;
		var d = this._getDocument();
		if (d) e = Controls._find(elemId,d);
		return e;
	}

	this._alert = function(title,message,element)
	{
		this._fire({name:"alert", title:title, message:message, element:element});
	}
	
	this._confirm = function(title, message,callback,element)
	{
		this._fire({name:"confirm", title:title, message:message, callback:callback, element:element});
	}
	
	this._readyToModifyHead = function()
	{
		return true;
	}

	this._readyToModifyBody = function()
	{
		return true;
	}

	this._modify = function()
	{
		var d = this._getDocument();

		var elem;
		while (elem = headElems.shift())
		{
			try
			{
				if (elem.ownerDocument === d)
					d.head.appendChild(elem);
			} catch (ex) { ; }
		}		
		headElems = Utils.newArray();

		var elemObj;
		while (elemObj = bodyElems.pop())
		{
			try
			{
				if (elemObj.e && elemObj.e.ownerDocument)
					if (elemObj.b)
						d.body.insertBefore(elemObj.e,elemObj.b);			
					else
						d.body.appendChild(elemObj.e);
	
				if (elemObj.c)
					elemObj.c();
			} catch(ex) {
				;
			}
		}
		bodyElems = Utils.newArray();

		wait(bind(this._showImages,this),50);
	}

	this._showImages = function()
	{		
		try
		{
			var imgSources = Utils.newArray();
			
			//put the images back in the document in reverse order (so that
			// insert before always has the right before element), but hide
			// it so that the broken image doesn't show
			while (imgObj = removedImages.pop())
			{
				imgObj.p.insertBefore(imgObj.i,imgObj.n);
				imgSources.push({i:imgObj.i, s:imgObj.s, v:imgObj.i.style.visibility});
				imgObj.i.style.visibility = "hidden";
			}

		} catch(ex) { //sometimes we'll get a permission denied or something
					  // putting the image back in - use recursion
					  // so that we can still set the .src of any images
					  // that we did re-insert
			this._showImages();
		}
	
		//then set the image sources from top down (so that the images
		// display in page order
		while (imgObj = imgSources.pop())
		{
			try
			{
				//set the image src
				imgObj.i.src = imgObj.s;

				// store the current onload handler (we can't attach event, it doesn't work here)
				imgObj.i.__onload = imgObj.i.onload;

				// store the image visibility
				imgObj.i.__visibility = imgObj.v;

				// store a reference to this (page), for use in the onload handler
				imgObj.i.__page = this;

				// set the onload handler function
				imgObj.i.onload = bind(function(event)
				{
					//restore the visiblity of the image
					this.style.visibility = this.__visibility;
					this.__visibility = null;

					//clear our onload hander, get the original
					var oldOnLoad = this.__onload;
					this.__onload = null;		
					this.onload = null;			

					//invoke the original onload handler (with window as "this")
					var page = this.__page;
					this.__page = null;					
					if (oldOnLoad)
						bind(oldOnLoad,page.getWindow())(event);
				},imgObj.i);

			} catch (ex) {
				; //skip errors to try the rest of the images
			}
		}

		removedImages = Utils.newArray();
	}
	
	this._hideImages = function()
	{
		//don't hide images on the site
		if (browser.isSite())
			return false;

		var d = this._getDocument();

		if (d.readyState == "complete")
			return;

		var img;
		for (var i = 0;img = d.body.getElementsByTagName('img')[i]; i++)
		{
			if ((img.readyState != "complete") && (img.src.indexOf(browser.getServer()) == -1))
			{
				try
				{
					removedImages.push({i:img, s:img.src, p:img.parentNode, n:img.nextSibling});
					img.src = "about:blank";
					img.parentNode.removeChild(img);
				} catch (ex) {
					; //ignore errors
				}
			}
		}		
	}

	this._appendToBody = function(elem,beforeElem,callback)
	{
		var d = this._getDocument();

		try
		{
			if (this._readyToModifyBody())
			{	
				if (beforeElem)
					d.body.insertBefore(elem,beforeElem)
				else
					d.body.appendChild(elem);

				if (callback)
					callback();
			} else {
				this._hideImages();
				bodyElems.push({e:elem, b:beforeElem, c:callback});			
			}
		} catch (ex) {
			bodyElems.push({e:elem, b:beforeElem, c:callback});			
		}
	}
	
	this._appendToHead = function(elem)
	{
		var d = this._getDocument();

	    var head = d.getElementsByTagName('head')[0];

		if (!head) {  //create a head section
			try
			{
				var head = Controls.createElement(d,"head");
				d.childNodes[0].insertBefore(head,d.childNodes[0].childNodes[0]);
			} catch (ex) {
				return null; 
			}
		}

		try
		{
			if (this._readyToModifyHead())
				head.appendChild(elem);
			else
				headElems.push(elem);			
		} catch (ex) {
			headElems.push(elem);			
		}	
	}
	
	this._closePage = function()
	{
		browser.ignore(browserListener);
		this._fire({name:"close", page:this});

		_elements = null;
		_elementsData = null;
	}
	
	this._close = function()
	{
		this._closePage();
		this._destroy();
	}
}

Utils.implement(PageModel,ModelBase);

eval(Utils.declarePublic(
	"PageModel",
	{
		getWindow:0,
		getMasterWindow:0,
		getDocument:0,
		event:1,
		isDemo:0,
		open:3,
		close:0,
		navigate:1,
		refresh:0,
		getServer:0,
		getUrl:0,
		getStandardizedUrl:0,
		getMimeType:0,
		getReferrer:0,
		getCharSet:0,
		getMimeType:0,
		getSelection:0,
		useShim:0,
		alert:3,
		confirm:4,
		find:1,
		setElementData:3,
		getElementData:2,
		appendToBody:3,
		appendToHead:1
	}
));

// /var/www/trailfire.com-release/content/client/lib/themes.js 

function Theme(id,server,version) 
{
	ClassBase.call(this,"Theme");

	if (!Number(id)) { id = 101; }
	id = Number(id);

	this._setId = function(_id)
	{
		id = Number(_id);
		if (!id) { id = 101; }
	}

	this._getId = function() 
	{ 
		return id;
	}
				
	this._avatarPath = function(owner, format, width, height, cacheBuster)
	{
		var path = server + 'theme/avatar.php/';

		if (!format) { format = "png"; }

		if (width)
			if (height)
				path += width + '/' + height + '/'

		path += owner+'.'+format;

		if (cacheBuster)
			path += '?'+(new Date).getTime();

		return path;
	}

	this._logoPath = function(format)
	{
		if (!format) { format = "png"; }
		return server + "theme/logo.php/"+id+"."+version+"/logo."+format; 
	};

	this._logoButtonPath = function(state,format)
	{
		if (!format) { format = "png"; }
		if (!state)  { state = "on";   }
		return server + "theme/logo.php/"+id+"."+version+"/"+state+"."+format; 
	};

	this._buttonPath = function(buttonName,state,format)
	{
		if (!format) { format = "png"; }
		if (!state) { state = "on"; }
		return server + "theme/button.php/"+id+"."+version+"/"+state+"/"+buttonName+"."+format; 
	};

	this._iconPath = function(monogram,format)
	{
		if (!format) { format = "png"; }
		return server + "theme/icon.php/"+id+"."+version+"/"+monogram+"."+format; 
	};

	this._previewPath = function(width,format)
	{
		if (!format) { format = "png"; }
		return server + "theme/preview.php/"+id+"."+version+"/"+width+"."+format; 
	};

	this._popupPath = function(size,format)
	{
		if (!format) { format = "png"; }
		return server + "theme/popup.php/"+id+"."+version+"/"+size+"."+format; 
	};

	this._scrollPath = function(component,format)
	{
		if (!format) { format = "png"; }
		if (Controls.detectIE() && (format == "png"))
		{
			switch (component)
			{
				case "sb1":
				case "hsb1":
					componentId = "ie"+component;
					break;
			}
		}
		return server + "theme/scrollbar.php/"+id+"."+version+"/" +component+"."+format; 
	}	
	
	this._styleSheetPath = function(windowId,pageServer)
	{
		var browser;
		var ssServer = pageServer || server;
		//IE & firefox have style differences, so we'll send in the browser type in the request (since server side browser detection is so bad)
		if (Controls.detectIE())
		{ browser = "ie"; } else { browser = "ff"; }

		return ssServer + "theme/style.php/"+id+"."+version+"/"+windowId+"."+browser+".css";
	};

	this._windowComponentPath = function(componentId)
	{	
		//IE & firefox have style differences, so we'll send in the browser type in the request (since server side browser detection is so bad)
		if (Controls.detectIE())
		{
			switch (componentId)
			{
				case "r0":
				case "r1":
				case "r2":
				case "r3":
					componentId = "ie"+componentId;
					break;
			}
		}
		return server + "theme/window.php/"+id+"."+version+"/"+componentId+".png";	
	};

//TODO - figure out a good way to load this information from the theme.xml file (probably something in the stylesheet)
	this._getMinSize = function() 
	{
		switch(name)
		{
			case 'default':
			default:
				return {x:175,y:175}; 
		}
	};
	
	this._getMaxSize = function()
	{
		switch(name)
		{
			case 'default':
			default:
				return {x:600,y:600}; 
		}
	};

	this._isResizable = function()
	{
		switch(name)
		{
			case 'default':
			default:
				return true;
		}
	};
	
	this._destroy = function()
	{
		this._cleanup();
	}
	
	this._insert = function(themeElem,page)
	{
		if (!themeElem) { return; }
		var d = themeElem.ownerDocument;

		if (d)
		{
			try {
			    var tElem=d.getElementById(themeElem.id); 
		    } catch (ex) { return null; } // page has gone away

			if (tElem)
				return tElem;

			if (themeElem)
			{
				if (page)
					page.appendToHead(themeElem)
				else
				{
					var head = d.getElementsByTagName("HEAD")[0];
					if (head)
						head.appendChild(themeElem);
				}
			}
			
			return themeElem;
		}
	}
	
	this._styleSheetId = function(elemId)
	{
		return "psWindowStyle-"+id+"-"+elemId;
	}

	this._create = function(page,elemId)
	{
		var themeId = this._styleSheetId(elemId);
		var d = page._getDocument();

		if (d)
		{
			try {
			    var themeElem=d.getElementById(themeId); 
		    } catch (ex) { return null; } // page has gone away

			if (!themeElem)
				themeElem = Controls.createStyleElem(d,themeId,this._styleSheetPath(elemId,page.getServer()));
			
			return themeElem;
		}

		return null;
	}
	
	this._add = function(page,elemId)
	{
		this._insert(this._create(page,elemId));
	}

}
Utils.implement(Theme,ClassBase);
eval(Utils.declarePublic("Theme",
	{
		setId:1,
		getMinSize:0,
		getMaxSize:0,
		isResizable:0,

		styleSheetPath:1,
		styleSheetId:1,
		windowComponentPath:1,
		logoPath:1,
		logoButtonPath:1,
		previewPath:2,
		popupPath:2,
		iconPath:2,
		scrollPath:2,
		buttonPath:3,
		avatarPath:5,

		add:2,
		insert:2,
		create:2,
		destroy:0
	}
));


// /var/www/trailfire.com-release/content/client/lib/controls/controls-base.js 

/*
 * DHTML Controls Library
 * Utilities for creating and managing DHTML elements 
 * Copyright 2006 Pipestone
 *
 * relies on utils.js
 */

	/**
	 * utility functions for dhtml controls
	 **/

var Controls = {

	// there is a rare bug in IE where accessing an node's offsetLeft, offsetTop, clientWidth or clientHeight
	// could lead to a window resize event being fired (see dispatch.js).  This
	// flag is set to stop that from happening
	blockWindowResize: false,
	ieDetectCache: { detected:false, value:false, detected6:false, value6:false },
	
	/**
	 * _find(id,doc): given a document and DOM element id, find and return the element
	 */
 	_find : function(id,doc)
	{
		if (doc != null) {
			this.doc = doc;
			return doc.getElementById(id);
		}
		return null;
	},

	hide : function(elem)
	{
		elem.style.display="none";
		elem.style.visibility="hidden";
	},
	
	show : function(elem,isInline)
	{
		if (isInline)
			elem.style.display = "inline";
		else
			elem.style.display="block";

		elem.style.visibility="visible";

	},
	/**
	 * createElement(doc,type): wrapper to create a HTML element
	 */
	createElement : function(doc,elemType)
	{
		var e = false;
		
		if (doc.standardCreateElement != undefined) //Ebay Active Content Blocker
		{
			try
		 	{
		 		if (doc.parentWindow)
				 	w = doc.parentWindow
				else
					w = doc.defaultView

			 	e = w.ebay.oDocument.createElement(elemType);
			} catch (ex) { 
				;
			}
			if (!e)	
				e = doc.standardCreateElement(elemType);
		}
		
		if (!e)
			e = doc.createElement(elemType);
		
		return e;
	},

	/**
	 * createElem: create a DOM element that is managed by the decorator
	 */
	createElem : function(elemType,doc,parentId,elemId,elemClass,clickHandler,mouseoverHandler) {
		var tElem = this.createElement(doc,elemType);
		if (parentId)
		{
			tElem.id = parentId+"-"+elemId;
		} else { tElem.id=elemId; }
		tElem.className=elemClass;
		if (clickHandler) 
			//tElem.onclick = clickHandler;
			Controls.addHandler(tElem,'click',clickHandler);
		
		if (mouseoverHandler) 
			//tElem.onmouseover = mouseoverHandler;	
			Controls.addHandler(tElem,'mouseover',mouseoverHandler);
		return tElem;
	},
	
	createStyleElem: function(doc,id,href)
	{
		var tElem   = this.createElem('link',doc,null,id,"styleElem");
	    tElem.rel   = 'stylesheet';
	    tElem.type  = 'text/css';
		tElem.media = 'all';
	    tElem.href  = href;
	    return tElem;
	},

	/**
	 * createDiv: create a DOM div that is managed by the decorator
	 */
	createDiv : function(doc,parentId,divId,divClass,clickHandler, mouseoverHandler) {
		return this.createElem('div',doc,parentId,divId,divClass,clickHandler,mouseoverHandler);
	},
	
	/**
	 * createChildElem: create a DOM element that is a child of a div managed by the decorator
	 */
	createChildElem : function(elemType, parentElem, parentId, elemId, elemClass, clickHandler, mouseoverHandler)
	{
		var tElem = this.createElem(elemType,parentElem.ownerDocument,parentId,elemId,elemClass, clickHandler,mouseoverHandler);
		parentElem.appendChild(tElem);
		return tElem;
	},
	
	/**
	 * createChildDiv: create a DOM div that is a child of a div managed by the decorator
	 */
	createChildDiv : function(parentElem,parentId,divId,divClass,clickHandler, mouseoverHandler) {
		return this.createChildElem('div',parentElem,parentId,divId,divClass,clickHandler,mouseoverHandler);
	},
	
	/**
	 * createChildButton: create a DOM button that is a child of a div managed by the decorator
	 */
	createThemedChildButton : function(page,parentElem, parentId, buttonId, buttonClass, buttonImage, buttonHoverText, clickHandler, mouseoverHandler)
	{
		function mousedownSink(event) {
			event=page.event(event); if (!event) { return; }
			if (event.preventDefault) {event.preventDefault();}		
		}
		var tElem = this.createChildElem("IMG",parentElem,parentId, buttonId, buttonClass, clickHandler, mouseoverHandler);
		function fix()
		{
			Controls.imageFix(tElem,page);
		}
		//tElem.onload = fix;
		Controls.addHandler(tElem, 'load',fix);
		tElem.src = buttonImage;
		//tElem.onmousedown=mousedownSink;
		Controls.addHandler(tElem, 'mousedown',mousedownSink);
		
		if (buttonHoverText)
			setHoverText(tElem,buttonHoverText,page);
		return tElem;
	},
	
	addHandler : function(elem,eventName,handler)
	{
		if (elem.attachEvent) //IE
		{
			elem.attachEvent("on"+eventName,handler);
		} else { //W3C DOM Level 2
			elem.addEventListener(eventName,handler,false);
		}
	},
	
	removeHandler : function(elem,eventName,handler)
	{
		if (!elem)
			return; 

		if (elem.detachEvent) //IE
		{
			elem.detachEvent("on"+eventName,handler);
		} else if (elem.removeEventListener) { //W3C DOM Level 2 (firefox)
			elem.removeEventListener(eventName,handler,false);
		}
	},

	clearEvents : function(elem)
	{
		if (!elem) { return; }
		try
		{
			elem.onmouseover = null;
			elem.onmouseout  = null;
			elem.onmousedown = null;
			elem.onmouseup   = null;
			elem.onmousemove = null;
			elem.onclick     = null;
			elem.ondrag      = null;
			elem.onselectstart 	= null;
			elem.onload			= null;
			elem.onerror		= null;	
			elem.onreadystatechange = null;
			elem.onmousewheel 	= null;
			elem.onresize	  	= null;
			elem.onunload 	  	= null;
			elem.onchange		= null;
			elem.onkeyup		= null;
			elem.onkeydown		= null;
			elem.onkeypress		= null;
			elem.onfocus		= null;
			elem.onblur			= null;

			if (elem.effect)
			{ 
				elem.effect.cancel(); 
				elem.effect 	= null;
			}
			
			clearHoverText(elem);
		} catch (ex) { ; } //ignore
	},
		
	/** 
	 * setPosition : absolutely position the element at x,y (in pixels)
	 */
	setPosition: function(elem,x,y)
	{
		elem.style.position="absolute";
		elem.style.left=x+"px";
		elem.style.top=y+"px";
	},
		
	getPosition: function Controls_getPosition(elem)
	{
		Controls.blockWindowResize = true;
		var pos = Utils.getBodyCoordinates(elem);
		Controls.blockWindowResize = false;
		return ({x:Number(pos[0]),y:Number(pos[1],10)});
	},
	
	getRelativePosition: function(elem,par)
	{
		var pos = Controls.getPosition(elem);
		if (!par) { par = elem.parentNode; }
		var parentPos = Controls.getPosition(par);
		pos.x = pos.x - parentPos.x;
		pos.y = pos.y - parentPos.y;
		return pos;		
	},

	getStylePosition: function(elem,absolute)
	{
		if (!elem) { return {x:0,y:0}; }
		var size = {x:0, y:0};
		var l,t;

		Controls.blockWindowResize = true;

		if (elem.currentStyle && elem.currentStyle.left)
		{
			l = elem.currentStyle.left;
			t = elem.currentStyle.top;
		} else {
			l = elem.style.left;
			t = elem.style.top;
		}

		//strip the "px" from the end
		size.x = Number(l.substring(0,l.length-2));
		size.y = Number(t.substring(0,t.length-2));

		if (isNaN(size.x)) { size.x=0; }
		if (isNaN(size.y)) { size.y=0; }
		
		if (size.x == 0) { size.x = elem.offsetLeft; }
		if (size.y == 0) { size.y = elem.offsetTop; }

		if (absolute && elem.parentNode && (elem.parentNode != elem.ownerDocument.body))
		{
			var pSize = Controls.getStylePosition(elem.parentNode,absolute);
			size.x += pSize.x;
			size.y += pSize.y;
		}

		Controls.blockWindowResize = false;
		return size;
	},

	setSize: function(elem,x,y)
	{
		try
		{
			elem.style.width = x + "px";
			elem.style.height = y + "px";
		} catch (ex) { ; } //sink IE page change errors
	},
	
	getStyleSize: function(elem)
	{
		if (!elem) { return {x:0,y:0}; }
		var size = {x:0, y:0};
		var h,w;
		if (elem.currentStyle)
		{
			h = elem.currentStyle.height;
			w = elem.currentStyle.width;
		} else {
			h = elem.style.height;
			w = elem.style.width;
		}

		//strip the "px" from the end
		size.x = Number(w.substring(0,w.length-2));
		size.y = Number(h.substring(0,h.length-2));

		if (isNaN(size.x)) { size.x=0; }
		if (isNaN(size.y)) { size.y=0; }

		return size;
	},
	
	getSize: function Controls_getSize(elem)
	{
		try
		{
			var h,w;

			if (!elem) { return {x:0,y:0}; }
			Controls.blockWindowResize = true;
	
			var size = {x:elem.clientWidth, y:elem.clientHeight};
	
			if (size.x === 0) { size.x=elem.offsetWidth; }
			if (size.x === 0)//not displayed, get value from style
			{
				if (elem.currentStyle)
				{
					w = elem.currentStyle.width;
				} else {
					w = elem.style.width;
				}
				//strip the "px" from the end
				size.x = Number(w.substring(0,w.length-2));				
				if (isNaN(size.x)) { size.x=0; }
			}
	
			if (size.y === 0) { size.y=elem.offsetHeight; }
			if (size.y === 0)//not displayed, get value from style
			{
				if (elem.currentStyle)
				{
					h = elem.currentStyle.height;
				} else {
					h = elem.style.height;
				}
				//strip the "px" from the end
				size.y = Number(h.substring(0,h.length-2));
				if (isNaN(size.y)) { size.y=0; }
			}
	
			Controls.blockWindowResize = false;
			return size;
		} catch (ex) {
			 Log.debug("Exception in Controls.getSize(). ex = " + ex.toString());
			 return {x:0,y:0}; 
		}
	},
	
	makeShim : function Controls_makeShim(elem)
	{
		var shim = this.createElement(elem.ownerDocument,'IFRAME');
		shim.className = "iframe-shim";
		shim.frameBorder = 0;
		
		Controls.setOpacity(shim,0);
		shim.style.display="none";					
		shim.style.position="absolute";
		shim.src = "javascript:false;";
				
		return shim;
	},

	updateShimZIndex : function Controls_updateShimZIndex(page,elem,shim,rect)
	{
		try {
			//set the zindex of the shim
			var tElem = elem;
			while (tElem)
			{
				if (tElem.currentStyle && tElem.currentStyle.zIndex)
				{
					shim.style.zIndex = tElem.currentStyle.zIndex;
					tElem = null;
				} else if (tElem.style && tElem.style.zIndex) {			
					shim.style.zIndex = tElem.style.zIndex;
					tElem = null;
				} else {
					tElem=tElem.parentNode;
				}
			}
			
			if (rect)
				Controls.moveShim(elem,shim,rect)
			else 
			{
				var elemSize = Controls.getStyleSize(elem);
				if (elemSize.x === 0) {	//wait for the browser to calculate the element size
					wait(function(){Controls.moveShim(elem,shim);},0,"shim");
				} else {
					Controls.moveShim(elem,shim);
				}
			}
		} catch (ex) {
			; //error silently
		}
	},

	/**
	 *UpdateShim - create an IFrame Shim to allow elements to float above
	 *				java applets and input elements
	 */
	updateShim : function Controls_updateShim(page, elem, shim, rect)
	{
		try	{

			if (!elem) { return; }
			if (!shim) { return; }

			if ( 
				  !(  								// if neither the shim's next sibling 
					(shim.nextSibling == elem) || 	// is the reference element nor
							  						// is the next sibling an iframe-shim, 
					(shim.nextSibling && shim.nextSibling.className == "iframe-shim") 
				   ) 
				) 									// we re-parent before our reference 
			{										//  element in the DOM
				var id = "";						// create an id for debugging

				if (elem.id)
				{
					id = elem.id
				} else if (elem.className) {
					id = elem.className.concat("-",(new Date()).getTime());
				} else {
					id = elem.tagName.concat("-",(new Date()).getTime());
				}

				shim.id = id + "-shim";

				if (shim.parentNode)					//take the shim out of the DOM
					shim.parentNode.removeChild(shim);

				// the iframe shim works best at under the body
				if (elem.parentNode == elem.ownerDocument.body)
					page.appendToBody(shim,elem,function()
					{
						Controls.updateShimZIndex(page,elem,shim,rect);
					});
				else	//find the parent of the reference
				{		// element that is a child of the body, and insert before that
					var par = elem.parentNode;
					while (par && (par.parentNode != par.ownerDocument.body))
					{
						par=par.parentNode;
					}

					par.parentNode.insertBefore(shim,par);
				}
			}

			Controls.updateShimZIndex(page,elem,shim,rect);
		} catch (ex) {
			; //error silently
		}
	},
	
	/**
	 * moveShim(elem): move the iframe shim for elem
	 */
	moveShim : function Controls_moveShim(elem,shim,rect)
	{
		if (!elem) { return; }
		if (!shim) { return; }

		try
		{
			var pos,size;
			if (rect)
			{
				pos = { x:rect.left, y:rect.top }
				size = { x:rect.right - rect.left,
						 y:rect.bottom - rect.top }
			} else {
		
				pos = Controls.getStylePosition(elem,true);
				if (pos.x == 0) 
					pos = Controls.getPosition(elem); 
	
				size = Controls.getStyleSize(elem);
				if (size.x == 0) 
					size = Controls.getSize(elem); 

			}

			shim.style.left = pos.x+"px";	
			shim.style.top = pos.y+"px";

			if (size.x) 
			{
				if (Controls.detectMac())
					{	size.x = Math.max(size.x,50); }

				shim.style.width = size.x+"px";
				shim.style.height = size.y+"px";
			}

			shim.style.display="block";
		} catch (ex) {
			Controls.hideShim(shim); //hide the shim if we can't move it
		}

	},
	
	highlightTerms: function(terms, parentElem)
	{		
		for (var i = 0; i < terms.length; i++)
			Controls.highlight(terms[i],parentElem);
	},
	
	highlight:function(term, parentElem)
	{
		var doc = parentElem.ownerDocument;
		
		term = term.toLowerCase();

		for(var i=0; i<parentElem.childNodes.length; i++)
		{
			var node = parentElem.childNodes[i];

			// if text node
			if (node.nodeType === 3)
			{
				var text = node.data;
				
				if (text.toLowerCase().indexOf(term) > -1)
				{
					var replacement = Controls.createElement(doc, 'SPAN');
					node.parentNode.replaceChild(replacement,node);
					
					var index = text.toLowerCase().indexOf(term);
					while(index > -1)
					{
						var prependTextNode = doc.createTextNode(text.substr(0,index));
						replacement.appendChild(prependTextNode);
						var highlightTextNode = Controls.createElement(doc, 'SPAN');
						highlightTextNode.innerHTML= text.substr(index,term.length);
						highlightTextNode.style.backgroundColor = '#ffffbb';
						//highlightTextNode.style.color = '#000000';
						highlightTextNode.style.padding = '0px 0px';
						replacement.appendChild(highlightTextNode);
						text = text.substr(index + term.length);
						index = text.toLowerCase().indexOf(term);
					}
					var postTextNode = doc.createTextNode(text);
					replacement.appendChild(postTextNode);
				}
			}else{
				//recurse the tree
				Controls.highlight(term, node);
			}
		}
	},
		
	/**
	 * hideShim(elem): hide the iframe shim for elem
	 */
	hideShim : function Controls_hideShim(shim)
	{
		if (shim && shim.style)
		{
			shim.style.display="none";
			Controls.setPosition(shim,-10000,-10000);
		}
	},
	
	/**
	 * destroyShim(elem): remove the iframe shim for elem
	 */
	destroyShim : function Controls_destroyShim(shim)
	{
		if (shim) 
		{
			if (shim.parentNode)
				shim.parentNode.removeChild(shim);
		}
	},

	getZIndex : function Controls_getZIndex(elem)
	{
		if (elem.currentStyle && elem.currentStyle.zIndex)
		{
			return elem.currentStyle.zIndex;
		} else if (elem.style && elem.style.zIndex) {			
			return elem.style.zIndex;
		}
		return 0;
	},
	
 	/**
 	  * scrollToView(doc,left,top,width,height)
	  * scroll the page to insure the specific rectangle is in view
	  **/
 	scrollToView : function(doc,left,top,width,height) 
	{
		var returnVal = false;

		var windowSize = Utils.getBrowserWindowSize(doc);
		var scrollOffsets = Utils.scrollOffsets(doc);
	
		var scrollLeft = scrollOffsets[0];
		var scrollTop = scrollOffsets[1];
	
		var screenW = windowSize[0];
		var screenH = windowSize[1];
	
		if (screenH <= 0) { screenH=600; }
		if (screenW <= 0) { screenW=800; }

		height = Math.min(screenH,height);
		width = Math.min(screenW,width);

		var newTop = scrollTop;

		if ( (top+height) > (screenH + scrollTop))
		{
			newTop += (top+height) - (screenH + scrollTop);
		} else	if ( top < scrollTop )
		{
			newTop = top;
		}

		if (newTop != scrollTop)
		{
			returnVal = true;
		}
	
		var newLeft = scrollLeft;

		if ( (left+width) > (screenW + scrollLeft))
		{
			newLeft += (left+width) - (screenW + scrollLeft);
		} else	if ( left < scrollLeft )
		{
			newLeft = left;
		}

		if (newLeft != scrollLeft)
		{
			returnVal = true;
		}

		if (returnVal)
			Utils.scrollTo(doc,newLeft,newTop);

		return returnVal;
	},

	/*****
	 * enableAlphaImages(elem) : IE does not natively support alpha tranparent PNGs, but
	 * 								using the magic of DirectX and the 'filter' directive,
	 *								we can fudge it and make the alpha channel function.
	 *								
	 ****/
	enableAlphaImages: function(elem,page,loopCount)
	{
		var profilerId = profiler.start("Controls.enableAlphaImages");

		if (!elem) { return; }

		var tagName = elem.tagName;

		if (!tagName) { return; }

		if (!loopCount) { loopCount = 0; }

		// if we have an image, replace the img src 
		if (tagName.toUpperCase() === "IMG")
		{ 
			var img = elem; 
			var src = img.src;
			function onImageReady()
			{
				try {
					if ((img.readyState==="complete") && (elem.offsetWidth>0))
					{
						Controls.enableAlphaImages(img,page);					
						img.onreadystatechange=null;
					}
				} catch (e) {	//in case of errors, just clear the event handler
					try { img.onreadystatechange=null; }
					catch(e){;} //if you can't do that, just abort w/o errors
				}
			}
			if (img && src && (src.toUpperCase().indexOf('.PNG')!=-1))
			{
				// has the image loaded?
				if (img.readyState != "complete")
				{	
					// if not, do it when the image loads 
					//img.onreadystatechange=onImageReady;
					Controls.addHandler(img,'readystatechange',onImageReady);

					profiler.stop(profilerId);
					return;
				}
				//img.onreadystatechange=null;
				Controls.removeHandler(img,'readystatechange',onImageReady);

				// get the dimensions
				var imgSize = Controls.getSize(img);
				if (imgSize.x > 0)
				{
					img.style.width = imgSize.x+"px";
					img.style.height = imgSize.y+"px";
				} else {

//					Utils.assert(loopCount < 20, "Invalid Image Parameters - no size set");
					if (loopCount < 20)
					{				
						wait(function()
						{
							Controls.enableAlphaImages(elem,page,loopCount + 1);
						},50,"imageFix");
					}

					profiler.stop(profilerId);
					return;
				}	
				// set the filter to AlphaImageLoader
				img.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+src+"', sizingMethod='crop'); ";
				// replace the image with a blank pixel
				img.src = page.getServer()+"apps/browser/images/spacer.gif";	
			}
		} else {
			var obj = elem;
			var bg = null;

			// copy the current background image
			if (obj.currentStyle)
			{
				bg = obj.currentStyle.backgroundImage; 
			} else {

//				Utils.assert(loopCount < 20, "Invalid Image Parameters - no size set");
				if (loopCount < 20)
				{				
					wait(function()
					{
						Controls.enableAlphaImages(elem,page,loopCount + 1);
					},50,"imageFix");
				}

				profiler.stop(profilerId);
				return;
			}

			// is the background image a PNG?
			if (bg && (bg.toUpperCase().indexOf('.PNG')!=-1)) 
			{
				// set the filter to the AlphaImageLoader
				var img = bg.substring(5,bg.length-2);
				var offset = obj.style["background-position"];
				var s = obj.style;

				// reapply the background position
				s["background-position"] = offset; 
				// use the AlphaImageLoader filter to display the image
				s.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+img+"', sizingMethod='crop')";
				// replace the background image with a blank pixel
				s.backgroundImage = "url('"+page.getServer()+"apps/browser/images/spacer.gif')";
			}
		}

		profiler.stop(profilerId);
	},

	/********
	 * imageFix(elem): when an element has an image, call this function to make sure it is displayed
	 *					properly across browsers (see enableAlphaImages above)
	 ********/
	imageFix: function(elem,page) {
		try {
			if (!elem) { return; }
			if (Controls.detectIE())
				Controls.enableAlphaImages(elem,page);
		} catch (e) { ; } //just ignore errors
	},
	
	detectIE: function () {
	//This is called in during resize loops, so implement a cache to 
	// avoid repeated string comparisons
		if (!this.ieDetectCache.detected)
		{
			this.ieDetectCache.value = (navigator.platform === "Win32" && 
					 navigator.appName === "Microsoft Internet Explorer" && 
					 window.attachEvent)
			this.ieDetectCache.detected = true;
		}
		return this.ieDetectCache.value;
	},

	detectIE6: function () {

		if (!this.ieDetectCache.detected6)
		{
			this.ieDetectCache.detected6 = true;

			if (Controls.detectIE()) 
			{ 
				this.ieDetectCache.value6 = window.XMLHttpRequest ? false : true
			} else {
				this.ieDetectCache.value6 = false 
			}
		}

		return (this.ieDetectCache.value6)
	},
	
	detectFirefox: function () 
	{
		return (!Controls.detectIE()); //we only support IE and firefox for now
/*		
		// Simple function to detect firefox.
		function detectFirefox()
		{
			if (navigator.appName != "Netscape") return false;
			if (navigator.userAgent.indexOf("Firefox") == -1) return false;
			return true;
		}
	
		// Initialize the persistent flag on the first call.
		if (arguments.callee.firefoxDetectedFlag == null) 
		{
			arguments.callee.firefoxDetectedFlag = detectFirefox();
		}
		
		// Return the Firefox-detected flag.
		return arguments.callee.firefoxDetectedFlag;
*/
	},
	
	detectMac: function () 
	{
		var isMac = false;
		if (navigator.platform.indexOf("Mac") != -1)
		{
			isMac = true;
		}
		return isMac;
	},
	
	eventPreventDefault: function(event)
	{
		if (event.preventDefault) { event.preventDefault(); }
		event.returnValue = false;
	},

	eventDone: function(event) {
		if (event)
		{
			if (Controls.detectIE())
				event.cancelBubble = true
			else
				if (event.stopPropagation)
					event.stopPropagation()
		}
	},
	
	eventKey: function(event) {
		if (event.keyCode)
			{ return event.keyCode; }
		return event.which;	
	},
	
	eventIsRightClick: function(event)
	{
		if (event)
		{
			if (event.ctrlKey || event.altKey || event.metaKey)
				return true;
			if (event.which)
				return event.which != 1;
			if (event.button)
				return event.button != 1;
		}
		
		return false;		
	},
	
	//element vertical position
	AbsTop:function(elem)
	{
		var t = Utils.getBodyCoordinates(elem);
		return t[1];
	},
	
	//element horizontal position
	AbsLeft:function(elem)
	{
		var t = Utils.getBodyCoordinates(elem);
		return t[0];
	},
	
	//calculate the distance between an element and a screen coordinate
	ElemDist:function(elem,x,y)
	{

		var t = Utils.getBodyCoordinates(elem);
		var x2 = t[0];
		var y2 = t[1];
		closestDist=1000;

		Controls.blockWindowResize = true;
				
		tDist=Math.sqrt(Math.pow(x-x2,2)+Math.pow(y-y2,2))
		if (tDist<closestDist) { closestDist=tDist; }
	
		tDist=Math.sqrt(Math.pow(x-x2+elem.clientWidth,2)+Math.pow(y-y2,2))
		if (tDist<closestDist) { closestDist=tDist; }
	
		tDist=Math.sqrt(Math.pow(x-x2,2)+Math.pow(y-y2+elem.clientHeight,2))
		if (tDist<closestDist) { closestDist=tDist; }
	
		tDist=Math.sqrt(Math.pow(x-x2+elem.clientWidth,2)+Math.pow(y-y2+elem.clientHeight,2))
		if (tDist<closestDist) { closestDist=tDist; }

		Controls.blockWindowResize = false;
		
		return (Math.sqrt(Math.pow(x-x2,2)+Math.pow(y-y2,2)))
	},
	
	//calculate if an event point falls in an element
	PointInElem: function(x,y,elem,margin)
	{
		if (!elem) { return false; }
		var t = Utils.getBodyCoordinates(elem);
		var eX = t[0];
		var eY = t[1];
		var elemSize = Controls.getSize(elem);
		var eW = elemSize.x;
		var eH = elemSize.y;
	
		if (margin)
		 { eX += margin; eY +=margin; eW -=margin; eH -= margin; }
		
		if ((x>=eX)&&(x<=(eX+eW)))
			if ((y>=eY)&&(y<=(eY+eH)))
				return true;
		
		return false;
	},

	/**
	 * setStyle : fill in the element styles from an associative array
	 */
	setStyle: function(elem,styles)
	{
		var fix = false;
		for (var s in styles) 
		{
			try
			{
				if (typeof(styles[s])==="string")
					elem.style[s]=styles[s];
			} catch (ex) {
				//non fatal error;
			}
		}
	},

	getStyle: function(element, style) 
	{
		var d = element.ownerDocument;
		var value = element.style[Utils.camelize(style)];
		if (!value) {
		  if (d.defaultView && d.defaultView.getComputedStyle) {
		    var css = d.defaultView.getComputedStyle(element, null);
		    value = css ? css.getPropertyValue(style) : null;
		  } else if (element.currentStyle) {
		    value = element.currentStyle[Utils.camelize(style)];
		  }
		}

		if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
    
		if (Controls.getStyle(element, 'position') === 'static') value = 'auto';

		return value === 'auto' ? null : value;
	},

	getInlineOpacity: function(element)
	{  
		var op;  
		op = element.style.opacity;  
		if (op) return op;  
		return "";  
	}, 
 
	setInlineOpacity: function(element, value)
	{  
		var els = element.style;  
		els.opacity = value;  
	},  

	getOpacity: function(element)
	{  
		var opacity;  
		if (opacity = Controls.getStyle(element, "opacity"))  
		  	return parseFloat(opacity);  
		if (opacity = (Controls.getStyle(element, "filter") || '').match(/alpha\(opacity=(.*)\)/))  
		  	if(opacity[1]) return parseFloat(opacity[1]) / 100;  
		return 1.0;
	},

	setOpacity: function(element, value)
	{  
		try
		{
			var els = element.style;  
			if (value === 1){  

				if (Controls.detectIE())
				{
					els.filter = "";
				} else {
					els.opacity = '1';
				}
			} else {  
				if(value < 0.00001) value = 0;  
				els.opacity = value;  
				if (Controls.detectIE())
				{
					els.filter = "progid:DXImageTransform.Microsoft.BasicImage(opacity="+
								value+") progid:DXImageTransform.Microsoft.Blur(pixelradius=2)";
				}
			}   
		} catch (ex) {
			; //page or element went away
		}
	},

	Effect : {
	
		speed: 20,	
		pause: 10,
		
		Appear: function(element,callback)
		{
			try
				{
				var timer;
				var sOpacity = Math.max(0.001,Controls.getStyle(element, "display") === "none" ? 0.0 : Controls.getOpacity(element) || 0.0);		
				var cOpacity = sOpacity;		
				var step = (1 / Controls.Effect.speed) * Controls.Effect.pause;
				var self = this;
				self.state = 'running';
		
				if (step <= 0) { step = 1; }

				try
				{
					if (element.__controls_effect)
						element.__controls_effect.cancel();
				} catch (ex) { ; }

				this.cancel=function()
				{
					clearWait(timer);
					self.state = 'finished';
					element.__controls_effect = null;
					if (callback) callback();
				}
				
				function iter()
				{
					try
					{
						cOpacity += step;
						if (cOpacity >= 1)
						{
							element.__controls_effect = null;
							self.state = 'finished';
							Controls.setOpacity(element,1.0);
							if (callback) callback();
						} else {
							Controls.setOpacity(element,cOpacity);
							timer = wait(iter,Controls.Effect.pause,"effects");
						}
					} catch(ex) {
						try
						{
							self.state='finished'; 
							if (element)
							{
								element.__controls_effect = null;
								if (element.style)
								{
									element.style.display="block";
									Controls.setOpacity(element,1.0);
									if (callback) callback();
								}
							}
						} catch (ex) { if (callback) callback(); }
					} 
				}
		
				Controls.setOpacity(element,sOpacity);
				element.style.display = "block";
				timer = wait(iter,Controls.Effect.pause,"effects");
				element.__controls_effect = this;
			} catch (ex) { if (callback) callback(); };
		},
		
		Fade: function(element,callback)
		{
			try
			{
				var timer;
				var sOpacity = Math.max(0.001,Controls.getStyle(element, "display") === "none" ? 0.0 : Controls.getOpacity(element) || 0.0);
				var cOpacity = sOpacity;	
				var step = 0 - (1 / Controls.Effect.speed) * Controls.Effect.pause;
				var self = this;
				self.state = 'running';
		
				if (step >= 0) { step = -1; }
				
				try
				{
					if (element.__controls_effect)
						element.__controls_effect.cancel();
				} catch (ex) { ; }

				this.cancel=function()
				{
					clearWait(timer);
					self.state = 'finished';
					element.__controls_effect = null;
					if (callback) callback();
				}
				
				function iter()
				{
					try
					{
						cOpacity += step;
						if (cOpacity <= 0)
						{
							element.__controls_effect = null;
							self.state='finished'; 
							element.style.display = "none";
					        Controls.setInlineOpacity(element, cOpacity);
							if (callback) callback();
			        	} else {
							Controls.setOpacity(element,cOpacity);
							timer = wait(iter,Controls.Effect.pause,"effects");
						}
					} catch(ex) {
						try
						{
							self.state='finished'; 
							if (element)
							{
								element.__controls_effect = null;
								if (element.style)
								{
									element.style.display = "none";
								    Controls.setInlineOpacity(element, 1.0);
								    if (callback) callback();
								}
							}
						} catch (ex) { if (callback) callback(); }
					}
				}
				
				timer = wait(iter,Controls.Effect.pause,"effects");
				element.__controls_effect = this;
			} catch (ex) { if (callback) callback(); }
		}
	},
	
	// Returns the outerText of an element, similar to outerText property in IE
	getOuterText: function(element)
	{  
		var SKIP_NODES = {"script":1, "noscript":1, "form":1, "style":1, "embed":1, "object":1};
		
		if (!element)
			return '';
			
		var content = ' ';
		var tagName = "";
		if (element.nodeName)
		{
			tagName = element.nodeName;
			tagName = tagName.toLowerCase();
		}

  		// if text node
		if (element.nodeType === 3)
        	content += element.data;    	
		else if (element.childNodes && !(SKIP_NODES[tagName]))
		{ 
			for (var i = 0; i < element.childNodes.length; i++)
			{
		        content += Controls.getOuterText(element.childNodes[i]);
			}
		}
		return Utils.trim(content) + ' ';
	}
}



// /var/www/trailfire.com-release/content/client/lib/controls/drawarea.js 

/* Draw Area - an area on the page that should float above all others, 
	manages iframe shims and hiding/showing elements on the page 
		
	This file is mostly bug fixes for various pages, elements and browser bugs in regards to 
	z-index stacking.  
	
	When we detect that a draw area overlaps a control that might show above it, we invoke this process:
	
	First try to use an iframe-shim to make a draw area show above "windowed" controls on the page
		- <select> boxes
		- iframes
		- flash objects (with wmode not set to "opaque" or "transparent")

	There are certain situations where using an iframe shim causes problems with elements inside
	 the draw area
		- in firefox, an input box will not have a cursor
		- in firefox, a shockwave flash will not accept input
		- in firefox, while dragging or resizing a visual effect ("ghosting"?) occurs

	In the situation where we cannot use an iframe shim, or an iframe shim still would not
	 insure that we could be above the element, then we modify the element in order to 
	 still show above it
	 	- we can sometimes set a wmode on shockwave flash, though this causes the flash to restart
		- we can hide the element, which we try to avoid at all costs

	When we hide an element, we place a "hint" where it used to be to indicate to the user
	 what has happened.  The user can then click in that hint to override this behaviour and show the
	 element anyway.	

	*/

Controls.DrawArea = function(page,DOMElement)
{
	ClassBase.call(this,"Controls.DrawArea");

	var _mode = {};
	var _shown = true;
	var _needsShim = false;

	var _shim = null;
	var element = DOMElement;
	var doc = element.ownerDocument;
	
	var _obscuredElements = Utils.newArray();
	var _obscuredIds = 0;	
	var _hasObscured = false;
	
	var _areaRect = { top:0, left:0, right:0, bottom:0 };

//var _updateCount = 0;
//var _itemCount = 0;
//var _updateTime = 0;

	var pageListener = bind(function(event)
	{
		switch(event.name)
		{
			case "unload":
					page.ignore(pageListener);
					pageListener = null;
				break;
			case "complete":
					wait(bind(this.update,this),50);
				break;
		}
	},this);
	page.listen(pageListener);

	this.setElement = function(newElement)
	{
		element = newElement;
		this.update();
	}

	// return a list of element types that might show up above the draw area and would 
	//  need to be shimmed or obscured.
	this.obscureTags = function()
	{
		//descriptions of tags that will show above our draw area in all browsers,
		// and what fixes to try to apply
		var _obscureTags = {}; 

		_obscureTags['applets'] =
			{
				nodeName:'APPLET',
				shim:true,
				obscure:true
		   }

		_obscureTags['appletObject'] =
			{
				nodeName:'OBJECT',
				CLASSID:'CLSID:8AD9C840-044E-11D1-B3E9-00805F499D93',
				shim:true,
				obscureParent:true
		   }

		_obscureTags['iframes'] = 
			{
				nodeName:'IFRAME',
				shim:true,
				obscure:true
		   }

		_obscureTags['flashObject'] = 
			{
				nodeName:'OBJECT',
				type:'application/x-shockwave-flash',
				shim:true, //try to use iframe shim
				wmode:true, //try to fix wmode
				obscure:true
			}

		_obscureTags['flashEmbed'] = 
			{
				nodeName:'EMBED',
				type:'application/x-shockwave-flash',
				shim:true, //try to use iframe shim
				wmode:true, //try to fix wmode
				obscure:true
			}

		//inputs and text areas with focus cause a cursor to blink through the draw area
		// take focus away from those elements (but not during drag operations, that 
		// makes too many elements and slows down dragging)
		if (!_mode["drag"])
		{
			_obscureTags['inputs'] =
				{
					nodeName:'INPUT',
					blur:true
			    }
	
			_obscureTags['textareas'] =
				{
					nodeName:'TEXTAREA',
					blur:true
			    }
		}

		if (Controls.detectIE())
		{
			if (Controls.detectIE6())
			{
				_obscureTags['selects'] =
					{
						nodeName:'SELECT',
						shim:true
				    }
			}

			// in IE, flash objects might be specified by classid
			_obscureTags['flashObjectClassId'] = 
				{
					nodeName:'OBJECT',
					CLASSID:'CLSID:D27CDB6E-AE6D-11CF-96B8-444553540000',
					shim:true, //try to use iframe shim
					wmode:true, //try to fix wmode
					obscure:true
				}

			// in IE, the windowsMediaPlayer shim always shows above, even with a shim
			_obscureTags['mediaPlayerShim'] = 
				{
					nodeName:'OBJECT',
					CLASSID:'CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95',
					obscure:true
				}

		} else {
			//shim doesn't help in firefox
			_obscureTags['appletObject'].shim = false;
			_obscureTags['flashObject'].shim = false;
			_obscureTags['flashEmbed'].shim = false;

			if (Controls.detectMac())
			{
				//on the mac, text area scrollbars will show through the mark, unless we shim
				// obscuring does not hide the scrollbars, so there
				// are situations where the textareas will show through
				_obscureTags['textareas'] =
					{
						nodeName:'TEXTAREA',
						shim:true
				    }

				//shim doesn't help applets in firefox on the mac
				_obscureTags['applets'].shim = false;

				//wmode doesn't help in firefox on the mac
				_obscureTags['flashObject'].wmode = false;
				_obscureTags['flashEmbed'].wmode = false;

				//iframes may contain non-shimmable elements, 
				// making the shim unreliable on the mac for iframes
				_obscureTags['iframes'].shim = false;
			}
		}

		// don't obscure any elements in the proxy
		if (page.isDemo())
		{
			for (var tag in _obscureTags)
			{
				if (_obscureTags[tag].obscure)
					_obscureTags[tag].obscure = false;
			}		
		}

		return _obscureTags;
	}

	// there are some elements that can be modified to show up under the
	// draw area.  This is better that hiding the element.
	// returns true if the element no longer needs to be hidden,
	// and false if obscuring is unavoidable 
	this.adjustWMode = function(elem)
	{
		try 
		{
			//we can set the wmode on flash objects
			if (elem.nodeName == "OBJECT")
			{
				// check flash object attributes
	            var hasWMode=false;
	
	            param=elem.firstChild;
	            while (param != null && !hasWMode)
	            {
	                if ((param.nodeType == 1) && (param.nodeName=="PARAM") &&
	                    (param.getAttribute("name").toLowerCase()=="wmode"))
	                {
						hasWMode = true;
						var wmode = param.getAttribute("value").toLowerCase();
	
						//the wmode is alreay set properly
						if ((wmode == "opaque") || (wmode == "transparent"))
							return true;					
					}
	
	                param=param.nextSibling;
	            }
	
	            if (!hasWMode) 
	            {	// set the wmode to transparent
	                param=Controls.createElement(doc,'param');
	                param.setAttribute("name","wmode");
	                param.setAttribute("value","opaque");
	                flashobject.appendChild(param);
				} 
			} else 	if (elem.nodeName == "EMBED" && elem) {
				var wmode = elem.getAttribute("wmode");

				if (wmode)
				{
					wmode = wmode.toLowerCase();
	
					//the wmode is alreay set properly
					if ((wmode == "opaque") || (wmode == "transparent"))
						return true;	
				}

				elem.setAttribute("wmode","opaque");
		        elem.setAttribute("src",elem.getAttribute("src"));
			}

			//only try to adjust the wmode on an element once
			if (page.getElementData(elem,"triedWMode"))
				return false;
			page.setElementData(elem,"triedWMode",true);	

			//need to put take the element out of the dom and reinsert to trigger
			// the wmode change to take effect
            tmp=Controls.createElement(doc,'div')
            var p=elem.parentNode;
            p.replaceChild(tmp,elem);
            p.replaceChild(elem,tmp);
            
            return true;
        } catch (e) {
         	return false;
        }
	}

	this.showElem = function(elem)
	{
		var hintElem = page.getElementData(elem,"obscureHintElem");

		if (hintElem)
		{
			hintElem.style.visibility="hidden";
			//hintElem.onclick = null;
			Controls.removeHandler(hintElem,"click",null);
		}

		elem.style.visibility = "visible";
	}
	
	//in firefox we can use the canvas element to create snapshot of an element we are hiding
	this.createSnapshot = function(elem,hintElem,elemPos,elemSize)
	{
		try
		{
			var hintCanvas = hintElem.getElementsByTagName("CANVAS")[0]
			if (!hintCanvas)
			{
				hintCanvas = Controls.createElement(page.getDocument(),"CANVAS");
				hintElem.appendChild(hintCanvas);
			}
	
	        hintCanvas.style.width = elemSize.x+"px";
	        hintCanvas.style.height = elemSize.y+"px";
	        hintCanvas.width = elemSize.x;
	        hintCanvas.height = elemSize.y;
	
			var hCtx = hintCanvas.getContext("2d");
	
			hCtx.fillStyle = "blue";
			hCtx.fillRect(0,0,elemSize.x,elemSize.y);
	
			if (elem.contentWindow)
			{
				hCtx.drawWindow(elem.contentWindow,0,0,elemSize.x,elemSize.y, "rgb(0,0,0)");
			} else {
		        var windowSize = Utils.getBrowserWindowSize(page.getDocument());
		        var pageW = windowSize[0];
		        var pageH = windowSize[1];
	
				var pageCanvas = Controls.createElement(page.getDocument(),"CANVAS");
	
	            pageCanvas.style.width = pageW+"px";
	            pageCanvas.style.height = pageH+"px";
	            pageCanvas.width = pageW;
	            pageCanvas.height = pageH;
	
				var pCtx = pageCanvas.getContext("2d");
	
				pCtx.drawWindow(page.getWindow(), 0, 0, pageW, pageH, "rgb(0,0,0)");
				
				if (elemSize.x > pageW)
					elemSize.x = pageW;
					
				if (elemSize.y > pageH)
					elemSize.y = pageH;
	
				hCtx.drawImage(pageCanvas,elemPos.x,elemPos.y,elemSize.x,elemSize.y,0,0,elemSize.x,elemSize.y);
			}	

			return true;
		} catch(ex) {
			; //but if this doesn't work, no reason not to go on
		}
		return false
	}
	
			
	this.hideElem = function(elem,obscureId)
	{
		if (elem.nodeName != "SELECT")
		{
			var hintElem = page.getElementData(elem,"obscureHintElem");
			
			if (!hintElem)
			{
				hintElem = Controls.createDiv(doc);
				hintElem.className = "obscureHintElem";
				page.setElementData(elem,"obscureHintElem",hintElem);
				page.appendToBody(hintElem);
			}
			var elemPos = Controls.getPosition(elem);
			var elemSize = Controls.getSize(elem);
	
			Controls.setSize(hintElem,elemSize.x,elemSize.y);
			Controls.setPosition(hintElem,elemPos.x,elemPos.y);	
			hintElem.style.overflow="hidden";

			hintElem.style.visibility="visible";

			var onClickHint = bind(function()
			{
				//bug #1945 - User needs to be told how to get the Mark back
				// display a little message telling the user to refresh the page to
				// see things properly
				if (!page.getElementData(elem.ownerDocument,"obscureOverrideMessage"))
				{
					page.confirm("Override Stacking Order","Are you sure you would like to change the stacking order?  You may need to refresh the page to see the mark again.",
					bind(function(bConfirm)
					{
						if (bConfirm)
						{
							page.setElementData(elem.ownerDocument,"obscureOverrideMessage",true);
							page.setElementData(elem,"noObscure",true);
							this.unobscureElem(obscureId);
							this.showElem(elem);
						}
					},this),elem);
				}
			},this);
 
			//hintElem.onclick = this.onClickHint;
			Controls.addHandler(hintElem,"click", onClickHint);

			hintElem.style.font="12px verdana,sans-serif";

			var snapshot = false;
			//in firefox we can use the canvas object to create a snapshot of iframes that we hide
			if ((elem.nodeName === "IFRAME") && Controls.detectFirefox() && !page.isDemo())
			{
				if (this.createSnapshot(elem,hintElem,elemPos,elemSize))
					snapshot = true;
			} 
			
			if (!snapshot)
			{
				hintElem.innerHTML="<center><b>This item is being hidden in order to properly display the mark.  Click here to see this element.</b></center>";
				hintElem.style.border="1px dashed #999";
				hintElem.style.padding="10px";
				hintElem.style.background = "white";
			}	
		}

		elem.style.visibility = "hidden";	
	}

	// hide an element
	this.obscureElem = function(elem)
	{
		_hasObscured = true;

		for (var i in _obscuredElements)
		{
			if (_obscuredElements[i] == elem)
				return;
		}

		if (page.getElementData(elem,"noObscure"))
			return;

		var oId = ++_obscuredIds;
		_obscuredElements[oId] = elem;
		
		//there may be other draw areas obscuring this
		// add to the use counter for the element
		var cnt = page.getElementData(elem,"obscureUseCount");

		if (cnt)
			page.setElementData(elem,"obscureUseCount",cnt + 1);
		else
		{
			page.setElementData(elem,"obscureUseCount",1);
			this.hideElem(elem,oId);
		}
	}
	
	// show an element
	this.unobscureElem = function(id)
	{
		if (_obscuredElements[id])
		{
			var elem = _obscuredElements[id]; 

			//there may be other draw areas obscuring this
			// decrement to the use counter for the element
			var cnt = page.getElementData(elem,"obscureUseCount");
			if (cnt > 0)
			{
				page.setElementData(elem,"obscureUseCount",--cnt);

				//if the use counter is 0, we can make the element visible
				if (cnt == 0)
				{
					this.showElem(elem);
				}
			}

			//remove from the list
			delete _obscuredElements[id];
		}
	}

	this.getAreaRect = function()
	{
		var rect = {};
		var pos = Utils.getBodyCoordinates(element);
		rect.left = pos[0];
		rect.top = pos[1];
		rect.right = rect.left + element.offsetWidth;
		rect.bottom = rect.top + element.offsetHeight;

		return rect;
	}
	
	this.collides = function(elem,rect)
	{
		//see if elem collides with the draw area
	    var l = 0, t = 0, r, b;	    	
	
	    var node = elem;
		do 
		{
			if (node == element) { return false; }
	        l += node.offsetLeft;
	        t += node.offsetTop;
	        node = node.offsetParent;
	    } while (node != null && node.nodeName != 'BODY')
	
		r = l + elem.offsetWidth;

		//bug #1942 - some elements, like OBJECT on youtube in IE in proxy
		// will have an offsetHeight, but will later not have one
		// so we're going to cache this height in the object if we
		// find it, then pull it out if it goes away
		var h = elem.offsetHeight;

		if (elem.nodeName != "APPLET")
		{
			if (h)
				elem.cachedHeight = h;
			else
			{
				if (elem.cachedHeight)
					h = elem.cachedHeight;
				else //no cached height, and no height - assume that conflicts
					return (elem.offsetWidth > 0);
			}
		}

		b = t + h;

		if (b < rect.top) return false;
		if (t > rect.bottom) return false;
		if (r < rect.left) return false;
		if (l > rect.right) return false;

		return true;
	}
	
	this.matchesTag = function(tag,elem,rect)
	{
		if (tag.nodeName == "IFRAME")
		{
			if (elem.className == "iframe-shim") //don't worry about iframe shims
				return false;
	
			if (elem.className == "edit editor") //don't worry about editor iframe
				return false;
		}
		if (tag.type)
		{
			var attr = elem.type;
			if (!attr)
			{
				if (elem.attributes && (typeof(elem.attributes) == "function")
					&& (typeof(elem.attributes("type")) != "undefined"))
					attr = elem.attributes("type").nodeValue
				else if (elem.attributes && (elem.attributes.length > 0)
					&& (typeof(elem.attributes["type"]) != "undefined"))
					attr = elem.attributes["type"].nodeValue
				else
					attr = "";
			}

			if (attr.toLowerCase() != tag.type)
				return false
		}
		
		if (tag.CLASSID)
		{
			var attr = false;

			if (Controls.detectIE())
				attr = elem.classid;
			else if (elem.getAttribute && typeof(elem.getAttribute) == "function")
				attr = elem.getAttribute("classid");

			if (!attr)
				return false;

			if (attr.toUpperCase() != tag.CLASSID)
				return false
		}

		// TODO: this may be an optimization... need to test
		//if (Utils.isChildOf(elem,element))
		//	return false;

		// in IE6, the collision detection is too slow during drag mode
		// so we're just going to assume that everything collides (as long
		// as it is not a child of the draw area).  the impact of this is small 
		// - only media player objects using a "media player shim" will be hidden 
		// and shims will always be used.
		if (_mode["drag"])
			if (Controls.detectIE6())
				return !Utils.isChildOf(elem,element);

		if (tag.obscureParent)
			return this.collides(elem.parentNode,rect)
		else
			return this.collides(elem,rect)
	}

	// unobscure all elements
	this.unobscure = function()
	{
		for (var i in _obscuredElements)
		{
			this.unobscureElem(i);
		}
		
		if (_shim)
			Controls.hideShim(_shim);
	}

	//can we use the shim - the shim is the preferred method, 
	// but does not alway work
	this.canShim = function()
	{
		if (!page.useShim())
			return false;
		
		if (Controls.detectFirefox())
		{
			if (_mode["drag"] || _mode["input"] || _mode["flash"])
				return false;
		}

		return true;
	}
	
	this.updateShim = function(needsShim,moving,rect)
	{
		if (needsShim)
		{
			if (!_shim) //create the shim
			{
				_shim = Controls.makeShim(element);
				Controls.updateShim(page,element,_shim, rect)
			} else {
				if (moving)			
					Controls.moveShim(element,_shim, rect)
				else
					Controls.updateShim(page,element,_shim, rect)
			}
		} else {
			if (_shim)
				Controls.hideShim(_shim);
		}
	}

	this.update = function(moving)
	{
		var profilerId = profiler.start("drawArea.update");

		this.unobscure();

		if (!_shown) return;

		var areaRect = this.getAreaRect(),		
			canShim = this.canShim(),
			tags = this.obscureTags(),
			needsShim = false,
			tag, nodeName, elem, i;

		//get a list of potential elements

		Controls.blockWindowResize = true;

		for (var t in tags)
		{
			tag = tags[t];
			nodeName = tag.nodeName;
			for (i = 0; (elem = doc.getElementsByTagName(nodeName)[i]); i++)
			{
				if (this.matchesTag(tag,elem,areaRect))
				{
					if (tag.shim && canShim)
					{
						needsShim = true;
					} else if (tag.wmode  && this.adjustWMode(elem)) {
						; // don't need to obscure
					} else if (tag.blur) {
						elem.blur();   // loose focus from input elems
					} else if (tag.obscure) {
						this.obscureElem(elem);
					} else if (tag.obscureParent) {
						this.obscureElem(elem.parentNode);
					}
				}

			}
		}

		// update the shim if needs be
		this.updateShim(needsShim,moving,areaRect);

		Controls.blockWindowResize = false;

		profiler.stop(profilerId);
	}

	this.move = function()
	{
		this.update(true);
	}	

	this.hide = function()
	{
		if (!_shown) return;
		_shown = false;

		this.update();
	}

	this.show = function()
	{
		if (_shown) return;
		_shown = true;

		this.update();
	}
	
	this.setMode = function(mode,bModeValue)
	{
		if (_mode[mode] == bModeValue) return;
		_mode[mode] = bModeValue;

		element.setAttribute(mode,bModeValue);

		this.update(mode=="drag");
	}

	this.destroy =  function()
	{
		this.unobscure();

		if (pageListener)
			page.ignore(pageListener);
		pageListener = null;

		if (_shim)
			Controls.destroyShim(_shim);
		_shim = null;

		element = null;
		doc = null;

		_obscuredElements = null;

		this._cleanup();
	}

	this.update();
}

/* public accessors */
Utils.implement(Controls.DrawArea,ClassBase);

// /var/www/trailfire.com-release/content/client/lib/controls/dialog.js 


/**************** Simple Dialog **********************/

/**
 * createDialog(..): create a modal popup dialog
 */
function SimpleDialog(page,doc,id,title,message,x,y,responses,callbacks,wide,inSidebar,theme)
{
	ClassBase.call(this,"SimpleDialog");
	
	var d=doc;
	var _drawArea = null;

	if (wide)
	{
		var dialog=Controls.createDiv(d,id,"dialogWide","psDialogWide");
	}
	else
	{
		var dialog=Controls.createDiv(d,id,"dialog","psDialog");
		x -= 172;
		y -= 25;
	}

	Controls.setPosition(dialog,Math.max(x,0),Math.max(y,0));
	
	if (!inSidebar)
		if (wide)
		{
			Controls.scrollToView(doc,x,y,160,100);
		} else {
			Controls.scrollToView(doc,x,y,230,100);
		}	

	var bgDiv = Controls.createChildDiv(dialog,id,"dialog-bg","bgdiv");
	bgDiv.innerHTML = "&nbsp;";

	var wrap = Controls.createChildDiv(dialog,id,"dialog-wrap","psDialog-Wrap");
	var header = Controls.createChildDiv(wrap,id,"dialog-header","psDialog-Header");
	var bodyWrap = Controls.createChildDiv(wrap,id,"dialog-body-wrap","psDialog-Body-Wrap"); 	
	var body = Controls.createChildDiv(bodyWrap,id,"dialog-body","psDialog-Body");
	var buttons = Controls.createChildDiv(wrap,id,"dialog-buttons","psDialog-Buttons");
	var scrollbar = null;
			
	header.innerHTML=title;
	body.innerHTML=message;
	
	if (typeof(theme) != "undefined")
	{
		scrollbar = new Scrollbar(page,body,theme,dialog.id,"scroll");
		if (wide)
		{
			bgDiv.style.background="transparent url("+theme.popupPath("wide","png")+") no-repeat";
			Controls.setSize(bodyWrap,230,55);
			wait(function()
			{
				scrollbar.setSize(220,55);
			},1,"scrollbar.setSize");
			Controls.setPosition(buttons,0,83);
		} else {
			bgDiv.style.background="transparent url("+theme.popupPath("small","png")+") no-repeat";
			Controls.setSize(bodyWrap,108,65);
			wait(function()
			{
				scrollbar.setSize(94,65);
			},1,"scrollbar.setSize");
			Controls.setPosition(buttons,0,95);
		}
	} else {
		if (wide)
			body.style.width="230px"
		else
			body.style.width="100px"
	}

	if ((typeof(responses) != "undefined") && (responses != null))
	{
		for (i=0; i<responses.length; i++)
		{
			var button=Controls.createChildElem("a",buttons,id,"dialog-button-"+i,"psDialog-Button",callbacks[i]);
			button.innerHTML=responses[i];
			buttons.appendChild(button);
			if (i%2 == 1)
			{
				buttons.appendChild(Controls.createElement(d,"BR"));
			}
		}
	}

	if (typeof(Controls.Effect) != "undefined")
		dialog.effect=new Controls.Effect.Appear(dialog);
	else
		dialog.style.display = "block";	
	d.body.appendChild(dialog);	

	//create a draw area on top of other page elements
	_drawArea = new Controls.DrawArea(page,dialog);

	Controls.imageFix(bgDiv,page);

	/**
	 * destroyDialog(id): remove a popup dialog
	 */
	this._destroy = function()
	{
		//remove DOM memory references
		if (dialog)
		{
			if (_drawArea)
				_drawArea.destroy();
			_drawArea = null;

			Controls.clearEvents(dialog);
			Controls.clearEvents(bgDiv);
			Controls.clearEvents(wrap);
			Controls.clearEvents(header);
			Controls.clearEvents(body);
			for (var i=0; i<buttons.childNodes.length; i++)
			{
				Controls.clearEvents(buttons.childNodes[i]);
			}
			Controls.clearEvents(buttons);

			if (scrollbar)
				scrollbar.destroy;

			scrollbar = null;

			if (dialog.parentNode)
				dialog.parentNode.removeChild(dialog);
		}

		dialog = null;			
		bgDiv = null;
		wrap = null;
		header = null;
		body = null;
		buttons = null;		
		this._cleanup();
	}

	this._getDOMElement = function()
	{
		return dialog;
	}
}

Utils.implement(SimpleDialog,ClassBase);
SimpleDialog.prototype.destroy = function() { this._destroy(); }
SimpleDialog.prototype.getDOMElement = function() { return this._getDOMElement(); }

// /var/www/trailfire.com-release/content/client/lib/controls/scrollbars.js 

/*
	scrollbar - dhtml scrollbar
*/

function Scrollbar(page,element,theme,parentId,className,scrollbarParentElement)
{
	ClassBase.call(this,"Scrollbar");

	if (!element) { return; }
	if (!className) { className = "scroll"; }

	var minHeight=50;
	var minWidth =50;

	//scrolling variables
	var dragOffsetX,dragOffsetY		//mouse offset from drag element top left
	var origThumbTop=0	//original height of resize element
	var origThumbLeft=0; 

	var skipAreaClick=false;	// skip the next click even in the scroll area 
	
	var scrollOuterElem;

	var scrollbar, scrollUp, scrollDown, scrollArea;
	var thumb, thumbTop, thumbCenter, thumbBottom;

	var hScrollbar, hScrollUp, hScrollDown, hScrollArea;
	var hThumb, hThumbTop, hThumbCenter, hThumbBottom;
	 
	var updateCallback = null;

	//make an element scrollable 
	this._make = function ()
	{
		this._makeScrollWrapper();
		this._makeScrollbar();
		this._makeHScrollbar();	
		this._update(true);
	}

	this._makeScrollWrapper = function()
	{
		//create the wrapper element
	    scrollOuterElem=Controls.createElem('div',element.ownerDocument,parentId,"scroll",className);
	    scrollOuterElem.style.position = "relative";
	    scrollOuterElem.style.overflow = "hidden";

		//put the wrapper element around element
	    var p=element.parentNode;
	    p.insertBefore(scrollOuterElem,element);
	    p.removeChild(element);

		//Stylize the elements
		element.className = "scrollContent";

	    scrollOuterElem.appendChild(element);

		//mousewheel events
		if (Controls.detectIE())
		{
			element.onmousewheel = this._scrollWheelIE;
		} else {
			element.addEventListener('DOMMouseScroll',this._scrollWheelFF, false);
		}			

	}

	this._makeScrollbar = function()
	{
		scrollbar	= Controls.createElem('div',element.ownerDocument,parentId,"scrollbar","psScrollBar");	    
		    scrollUp=Controls.createChildElem('div',scrollbar,parentId,"scroll-up","top");
		    scrollArea=Controls.createChildElem('div',scrollbar,parentId,"scroll-area","area");
		    thumb=Controls.createChildElem('div',scrollArea,parentId,"scroll-thumb","thumb");
				thumbTop=Controls.createChildElem('img',thumb,parentId,"scroll-thumb-top","thumbTop");
				thumbCenter=Controls.createChildElem('img',thumb,parentId,"scroll-thumb-center","thumbCenter");	
				thumbBottom=Controls.createChildElem('img',thumb,parentId,"scroll-thumb-bottom","thumbBottom");
	    	scrollDown=Controls.createChildElem('div',scrollbar,parentId,"scroll-down","bottom");

		//append the scrollbar to the wrapper element
		if (scrollbarParentElement)
		    scrollbarParentElement.appendChild(scrollbar);
		else
		{
			if (scrollOuterElem.nextSibling)
			    scrollOuterElem.parentNode.insertBefore(scrollbar,scrollOuterElem.nextSibling);
			else
			    scrollOuterElem.parentNode.appendChild(scrollbar);
		}
		Controls.hide(scrollbar);

	    scrollUp.innerHTML='&nbsp;';
	    scrollDown.innerHTML='&nbsp;';

	    thumb.style.position="relative";
		thumbCenter.style.height = "1px";

		scrollUp.style.background = "transparent url("+theme.scrollPath('sb0','gif')+") bottom center no-repeat";
		scrollArea.style.background = "transparent url("+theme.scrollPath('sb1','gif')+") center center repeat-y";
		scrollDown.style.background = "transparent url("+theme.scrollPath('sb2','gif')+") top center no-repeat";

	    thumbTop.src=theme.scrollPath('st0','gif');
	    thumbCenter.src=theme.scrollPath('st1','gif');
	    thumbBottom.src=theme.scrollPath('st2','gif');

		//scrollbar events

	    //thumbTop.onmousedown	=this._thumbMouseDown;
	    Controls.addHandler(thumbTop, 'mousedown',this._thumbMouseDown);

	    //thumbCenter.onmousedown =this._thumbMouseDown;
	    Controls.addHandler(thumbCenter, 'mousedown',this._thumbMouseDown);

	    //thumbBottom.onmousedown =this._thumbMouseDown;
	    Controls.addHandler(thumbBottom, 'mousedown',this._thumbMouseDown);

	    //thumb.onmousedown		=this._thumbMouseDown;
	    Controls.addHandler(thumb, 'mousedown',this._thumbMouseDown);

	    //scrollDown.onclick		=this._bottomMouseClick;
	    Controls.addHandler(scrollDown, 'click',this._bottomMouseClick);
	    
	    //scrollUp.onclick		=this._topMouseClick;
	    Controls.addHandler(scrollUp, 'click',this._topMouseClick);
	    
	    //scrollArea.onclick		=this._areaMouseClick;
	    Controls.addHandler(scrollArea, 'click',this._areaMouseClick);
	}	

	this._makeHScrollbar = function()
	{
		//create the horizontal scrollbar
		hScrollbar	= Controls.createElem('div',element.ownerDocument,parentId,"scrollbar","psHScrollBar");	    
		    hScrollUp=Controls.createChildElem('div',hScrollbar,parentId,"scroll-up","top");
		    hScrollArea=Controls.createChildElem('div',hScrollbar,parentId,"scroll-area","area");

		    hThumb=Controls.createChildElem('div',hScrollArea,parentId,"scroll-thumb","thumb");
				hThumbTop=Controls.createChildElem('img',hThumb,parentId,"scroll-thumb-top","thumbTop");
				hThumbCenter=Controls.createChildElem('img',hThumb,parentId,"scroll-thumb-center","thumbCenter");	
				hThumbBottom=Controls.createChildElem('img',hThumb,parentId,"scroll-thumb-bottom","thumbBottom");

	    	hScrollDown=Controls.createChildElem('div',hScrollbar,parentId,"scroll-down","bottom");

		Controls.hide(hScrollbar);
	    
		//append the horizontal scrollbar to the wrapper element
		if (scrollbarParentElement)
		    scrollbarParentElement.appendChild(hScrollbar);
		else
		{
			if (scrollbar.nextSibling)
			    scrollbar.parentNode.insertBefore(hScrollbar,scrollbar.nextSibling);
			else
			    scrollbar.parentNode.appendChild(hScrollbar);
		}

	    hScrollUp.innerHTML='&nbsp;';
	    hScrollDown.innerHTML='&nbsp;';

	    hThumb.style.position="relative";
		hThumbCenter.style.width = "1px";

		hScrollUp.style.background = "transparent url("+theme.scrollPath('hsb0','gif')+") center left no-repeat";
		hScrollArea.style.background = "transparent url("+theme.scrollPath('hsb1','gif')+") center center repeat-x";
		hScrollDown.style.background = "transparent url("+theme.scrollPath('hsb2','gif')+") center right no-repeat";

	    hThumbTop.src=theme.scrollPath('hst0','gif');
	    hThumbCenter.src=theme.scrollPath('hst1','gif');
	    hThumbBottom.src=theme.scrollPath('hst2','gif');

		if (Controls.detectIE())
		{
			hScrollUp.style.top = "-1px";
			hScrollDown.style.top = "-1px";
		}

		//horizontal scrollbar events
	    //hThumbTop.onmousedown	=this._hThumbMouseDown;
	    Controls.addHandler(hThumbTop, 'mousedown',this._hThumbMouseDown);

	    //hThumbCenter.onmousedown =this._hThumbMouseDown;
	    Controls.addHandler(hThumbCenter, 'mousedown',this._hThumbMouseDown);

	    //hThumbBottom.onmousedown =this._hThumbMouseDown;
	    Controls.addHandler(hThumbBottom, 'mousedown',this._hThumbMouseDown);

	    //hThumb.onmousedown		=this._hThumbMouseDown;
	    Controls.addHandler(hThumb, 'mousedown',this._hThumbMouseDown);

	    //hScrollDown.onclick		=this._hBottomMouseClick;
	    Controls.addHandler(hScrollDown, 'click',this._hBottomMouseClick);

	    //hScrollUp.onclick		=this._hTopMouseClick;
	    Controls.addHandler(hScrollUp, 'click',this._hTopMouseClick);

	    //hScrollArea.onclick		=this._hAreaMouseClick;
	    Controls.addHandler(hScrollArea, 'click',this._hAreaMouseClick);

	}

	this._destroy = function()
	{
		updateCallback = null;

	    var p=scrollOuterElem.parentNode;
	    scrollOuterElem.removeChild(element);
	    p.insertBefore(element,scrollOuterElem);
	
		if (element.removeEventListener)
		{
			element.removeEventListener('DOMMouseScroll',this._scrollWheelFF,false);
		}		

		Controls.clearEvents(scrollOuterElem);
		scrollOuterElem = null;

		Controls.clearEvents(scrollbar);
		scrollbar = null;
		Controls.clearEvents(scrollUp);
		scrollUp = null;
		Controls.clearEvents(scrollDown);
		scrollDown = null;
		Controls.clearEvents(scrollArea);
		scrollArea = null;

		Controls.clearEvents(thumb);
		thumb = null;
		Controls.clearEvents(thumbTop);
		thumbTop = null;
		Controls.clearEvents(thumbCenter);
		thumbCenter = null;
		Controls.clearEvents(thumbBottom);
		thumbBottom = null;	

		Controls.clearEvents(hScrollbar);
		hScrollbar = null;
		Controls.clearEvents(hScrollUp);
		hScrollUp = null;
		Controls.clearEvents(hScrollDown);
		hScrollDown = null;
		Controls.clearEvents(hScrollArea);
		hScrollArea = null;

		Controls.clearEvents(hThumb);
		hThumb = null;
		Controls.clearEvents(hThumbTop);
		hThumbTop = null;
		Controls.clearEvents(hThumbCenter);
		hThumbCenter = null;
		Controls.clearEvents(hThumbBottom);
		hThumbBottom = null;	

		Controls.clearEvents(element);
		element = null;

		this._cleanup();
	}

	this._setUpdateCallback = function(callback) { updateCallback = callback; }
	
	this._isShowing = function()
	{
		var outerSize = Controls.getSize(scrollOuterElem);
		var innerSize = Controls.getSize(element);

		if ((innerSize.y <= outerSize.y) || (outerSize.y <= 0) || (innerSize.y <= 0))
			return false;
		
		return true;
	}

	this._getScrollWidth = function()
	{
		var scrollW = Math.max(element.scrollWidth,scrollOuterElem.scrollWidth);

		return scrollW;
	}
	
	this._getClientWidth = function()
	{
		var clientW = Math.min(element.clientWidth,scrollOuterElem.clientWidth);		

		if (!clientW)
		{
			if (!element.clientWidth)
				clientW = scrollOuterElem.clientWidth;
			else if (!scrollOuterElem.clientWidth)
				clientW = element.clientWidth;
			else
				clientW = this._getScrollWidth();
		}

		return clientW;		
	}

	this._isHShowing = function()
	{
		var clientW = this._getClientWidth();
		var scrollW = this._getScrollWidth();

		if (clientW < scrollW)
			return true;

		return false;
	}

	//update scroll area (after resize)
	this._update = function (resize)
	{
		try
		{	
			var vShowing = this._isShowing();
			var hShowing = this._isHShowing();

 			if (vShowing)
			{
				Controls.show(scrollbar);
			} else {
				Controls.hide(scrollbar);
				scrollOuterElem.scrollTop = 0;
			}

			if (hShowing)
			{
				Controls.show(hScrollbar);
			} else {
				Controls.hide(hScrollbar);
				scrollOuterElem.scrollLeft = 0;
			}

			var parentSize = Controls.getSize(scrollOuterElem.parentNode);
			var scrollSize = Controls.getSize(scrollbar);

			var width = (parentSize.x);
			var height = (parentSize.y);

			if (vShowing) { width = width - scrollSize.x - 4; }
			if (hShowing) { height = height - scrollSize.x - 4; }

			scrollOuterElem.style.width = width + "px";
			scrollOuterElem.style.height = height + "px";

//			if (resize)
//			{				

				var topSize = Controls.getSize(scrollUp);
				var bottomSize = Controls.getSize(scrollDown);
				
				scrollArea.style.height = "" + Math.max(1,height - topSize.y - bottomSize.y)+"px";
				scrollbar.style.height  = "" + height + "px";

				if (hShowing)
				{
					var topSize = Controls.getSize(hScrollUp);
					var bottomSize = Controls.getSize(hScrollDown);

					hScrollArea.style.width = "" + Math.max(1,width - topSize.x - bottomSize.x)+"px";
					hScrollbar.style.width  = "" + width+ "px";
				}
//			} 


			this._adjustThumb();
			
			if (updateCallback)
				updateCallback();

		} catch (ex) {
			; //sink page change errors
		}
	}
	
	//adjust the scroll bar height
	this._adjustScrollArea = function (delta)
	{
		if (!scrollOuterElem) { return delta; }

		var size = Controls.getSize(scrollOuterElem);	
		if (size.y > 0) 
		{
			var tHi=Math.max(minHeight,size.y+delta);
			scrollOuterElem.style.height=tHi+"px";
		}
	
		this._updateScroll(true);
	
		return delta - (tHi - size.y); //delta
	}
	
	//adjust the thumb position
	this._adjustThumb = function () 
	{	
		if (!this._isShowing()) 
			Controls.hide(scrollbar)
		else
		{
			var outerSize = Controls.getSize(scrollOuterElem);
			var innerSize = Controls.getSize(element);
			var areaSize = Controls.getSize(scrollArea);
			var topSize = Controls.getSize(thumbTop);
			var bottomSize = Controls.getSize(thumbBottom);

			// the amount of scrollable area
			var scrollMax = Math.max(0,innerSize.y - outerSize.y); 

			// the scroll position
			var scrollPos = Math.max(0,Math.min(scrollMax ,scrollOuterElem.scrollTop)); 

			// find the percentage of scrollMax that scrollPos represents
			var scrollPercent = 1; 
	 		if (scrollMax > 0) 
				scrollPercent = scrollPos/scrollMax;

			//determine the size of the scroll thumb
			var thumbSize = Math.floor(areaSize.y * Math.min(1,outerSize.y / innerSize.y));
				thumbSize = Math.max(topSize.x + bottomSize.x + 1,thumbSize);
			
			//subtract the top and bottom of the thumb from the size
			var thumbCenterSize = Math.max(1,thumbSize - topSize.y - bottomSize.y);
					
			//determine largest thumb position posible
			var thumbMax = areaSize.y - thumbSize - 1;

			//calculate the thumb position
			var thumbTop = Math.max(0,Math.min(Math.floor(scrollPercent*thumbMax),thumbMax));

			//set the DOM attributes
			thumbCenter.style.height=thumbCenterSize+"px";
			thumb.style.top=thumbTop+"px";
			scrollOuterElem.scrollTop = scrollPos;
		}
		
		this._adjustHThumb();
	}
	
	//adjust the thumb position
	this._adjustHThumb = function () 
	{	
		if (!this._isHShowing())
			Controls.hide(hScrollbar)
		else
		{
			var clientW = this._getClientWidth();
			var scrollW = this._getScrollWidth();
	
			var innerSize = Controls.getSize(element);
			var outerSize = Controls.getSize(scrollOuterElem);
			var areaSize = Controls.getSize(hScrollArea);
			var rightSize = Controls.getSize(hScrollDown);
			var topSize = Controls.getSize(hThumbTop);
			var bottomSize = Controls.getSize(hThumbBottom);

			// the amount of scrollable area
			var scrollMax = Math.max(0,scrollW - clientW); 

			// the scroll position
			var scrollPos = Math.max(0,Math.min(scrollMax ,scrollOuterElem.scrollLeft)); 

			// find the percentage of scrollMax that scrollPos represents
			var scrollPercent = 1; 
	 		if (scrollMax > 0) 
				scrollPercent = scrollPos/scrollMax;

			//determine the size of the scroll thumb
			var thumbSize = Math.floor(areaSize.x * Math.min(1,clientW / scrollW));
				thumbSize = Math.max(topSize.x + bottomSize.x + 1,thumbSize);

			//subtract the top and bottom of the thumb from the size
			var thumbCenterSize = Math.max(1,thumbSize - topSize.x - bottomSize.x);
					
			//determine largest thumb position posible
			var thumbMax = areaSize.x  - thumbSize - 1;

			//calculate the thumb position
			var thumbLeft = Math.max(0,Math.min(Math.floor(scrollPercent*thumbMax),thumbMax));

			//set the DOM attributes
			hThumbCenter.style.width=thumbCenterSize+"px";
			hThumb.style.width=thumbSize+"px";
			hThumb.style.left=thumbLeft+"px";
			scrollOuterElem.scrollLeft = scrollPos;
		}
	}
	
	//scroll by an amount
	this._doScroll = function (yAmt,xAmt) 
	{
		var outerSize = Controls.getSize(scrollOuterElem);
	  	var innerSize = Controls.getSize(element);
	
		if (yAmt && (innerSize.y>outerSize.y))
		{
			scrollMax = Math.max(0,element.clientHeight-scrollOuterElem.clientHeight);
			scrollOuterElem.scrollTop = Math.max(0,Math.min(scrollMax,scrollOuterElem.scrollTop + yAmt))		
		}
	
		if (xAmt && (this._isHShowing()))
		{
			var clientW = this._getClientWidth();
			var scrollW = this._getScrollWidth();

			var scrollMax = Math.max(0,scrollW - clientW); 
			scrollOuterElem.scrollLeft = Math.max(0,Math.min(scrollMax,scrollOuterElem.scrollLeft + xAmt))		
		}
	
		this._adjustThumb();
	}

	//scroll by a page (+ or -)
	this._hScrollPage = function (direction) 
	{
		var innerSize = Controls.getSize(element);
		this._doScroll(0,direction*Math.ceil(innerSize.x/2));
	}	

	//scroll by a page (+ or -)
	this._scrollPage = function (direction) 
	{
		var innerSize = Controls.getSize(element);
		this._doScroll(direction*Math.ceil(innerSize.y/2));
	}
	
	//scroll to a percentage of total, called while dragging thumb (so we don't update thumb here)
	this._scrollTo = function (perc) {
	 	var innerSize = Controls.getSize(element);
		var outerSize = Controls.getSize(scrollOuterElem);
	 
		if (innerSize.y > outerSize.y)
		{
			lSize=Math.floor((innerSize.y-outerSize.y)*perc)
			scrollOuterElem.scrollTop = lSize;
		}
	}

	//scroll to a percentage of total, called while dragging thumb (so we don't update thumb here)
	this._hScrollTo = function (perc) {
		var clientW = this._getClientWidth();
		var scrollW = this._getScrollWidth();
	 
		if (scrollW > clientW)
		{
			var scrollMax = Math.max(0,scrollW - clientW); 
			var scrollPos=Math.floor( scrollMax * perc)

			scrollOuterElem.scrollLeft = Math.max(Math.min(scrollPos,scrollMax),0);	
		}
	}
	
	this._scrollToTop = function()
	{
		this._scrollTo(0);			
		this._adjustThumb();
	}
	
	this._scrollToBottom = function()
	{
		this._scrollTo(1);			
		this._adjustThumb();
	}

	this._scrollToElem = function (targetElem,alignTop)
	{
		var innerSize = Controls.getSize(element);
		var outerSize = Controls.getSize(scrollOuterElem);
		
		var elemOffset = Controls.getRelativePosition(targetElem,element);
		var elemSize = Controls.getSize(targetElem);
	
		var visTop = scrollOuterElem.scrollTop;
		var visBottom = visTop + outerSize.y;
	
		var tooHigh=0;
		var tooLow=0;
	
		tooHigh = Math.max(0,visTop - elemOffset.y);
		tooLow  = Math.max(0,elemOffset.y + elemSize.y - visBottom);
	
		if (alignTop)
		{
			scrollOuterElem.scrollTop = Math.max(0,Math.min(innerSize.y - outerSize.y, elemOffset.y ))
			this._adjustThumb();
		} else if (tooHigh > 0) //scroll so that the top is in view
		{
			this._doScroll(-tooHigh);
		} else	if (tooLow > 0) //scroll so that the bottom is in view
		{
			this._doScroll(tooLow);
		}
	}

	/**********************************
	 * event handlers
	 **********************************/
	this._sink = bind(function(event)
	{
		return false;
	},this);

	//handle mouse down on scroll thumb
	this._thumbMouseDown = bind(function (event)
	{
		event=page.event(event); if (!event) { return; }
		if (event.preventDefault) {event.preventDefault();}

		Controls.addHandler(thumb.ownerDocument,"mousemove",this._thumbMouseMove);
		Controls.addHandler(thumb.ownerDocument,"mouseup",this._thumbMouseUp);
		Controls.addHandler(thumb,"dragstart",this._sink);
		if (Controls.detectIE())
		{
			Controls.addHandler(thumb.ownerDocument.body,"mouseleave",this._thumbMouseUp);
		} else {
			Controls.addHandler(thumb.ownerDocument,"mouseout",this._thumbMouseOut);
		}

		dragOffsetX=event.clientX
		dragOffsetY=event.clientY

		origThumbTop=Number(thumb.style.top.replace(/px/g,""))
		if (!origThumbTop) { origThumbTop=0; }			

		//prevent the event from causing a click in the area (page up/down)
		skipAreaClick=true;		
		
		Controls.eventDone(event);
		return false;
	},this)
	
	//stop dragging
	this._cancelDrag = bind(function ()
	{
		Controls.removeHandler(thumb.ownerDocument,"mousemove",this._thumbMouseMove);
		Controls.removeHandler(thumb.ownerDocument,"mouseup",this._thumbMouseUp);
		Controls.removeHandler(thumb,"dragstart",this._sink);
		if (Controls.detectIE())
		{
			Controls.removeHandler(thumb.ownerDocument.body,"mouseleave",this._thumbMouseUp);
		} else {
			Controls.removeHandler(thumb.ownerDocument,"mouseout",this._thumbMouseOut);
		}
	},this);
	
	//move the scroll thumb, scroll the scroll area
	this._thumbMouseMove = bind(function (event)
	{
		event=page.event(event); if (!event) { return; }

		var thumbSize = Controls.getSize(thumbCenter);
		var areaSize = Controls.getSize(scrollArea);
		var scrollUpSize = Controls.getSize(scrollUp);

		var min=0;
		var max=areaSize.y - thumbSize.y - scrollUpSize.y - 1;

		var newTop = Math.max(min,Math.min( origThumbTop + (event.clientY-dragOffsetY) ,max));

		thumb.style.top=(newTop-1)+"px";
		this._scrollTo(newTop/(max-min));	

		return false;
	},this);
	
	//lifted the mouse, scroll over
	this._thumbMouseUp = bind(function (event)
	{
		event=page.event(event); if (!event) { return; }
		this._cancelDrag(); 
	
		//enable clicks in the area (page up/down)
		wait(function(){ skipAreaClick=false; },100,"scrollbar");
	
		return false;
	},this);
	
	this._thumbMouseOut = bind(function (event)
	{
		event=page.event(event); if (!event) { return; }
		if (event.relatedTarget == undefined)
		{
			this._cancelDrag(); 

			//enable clicks in the area (page up/down)
			wait(function(){ skipAreaClick=false; },100,"scrollbar");
			return false;
		}
	},this);
	
	// click in the top arrow of the scrollbar
	this._topMouseClick = bind(function (event)
	{
		this._doScroll(-14);		
		return false;
	},this);
	
	// click in the bottom arrow of the scrollbar
	this._bottomMouseClick = bind(function (event)
	{
		this._doScroll(14);
		return false;
	},this);
	
	// click in the scroll area of the scrollbar (causes page up/page down)
	this._areaMouseClick = bind(function (event)
	{
		event=page.event(event); if (!event) { return; }
		if (skipAreaClick) { return; }
	
		tTop=Controls.AbsTop(thumb);

		if (event.clientY>tTop)
			{ this._scrollPage(1); } 
		else 
			{ this._scrollPage(-1); } 
	},this);

	this._scrollWheelIE = bind(function(e)
	{
		var event = page.event(e);
		delta = event.wheelDelta;
		this._doScroll(Math.floor(delta/-10));
		Controls.eventDone(event);
		return false;	
	},this);
	
	this._scrollWheelFF = bind(function(e)
	{
		var event = page.event(e);
		delta = event.detail;
		this._doScroll(delta*10);
		event.preventDefault();
	},this)
		
	/* horizontal scrollbar functions */
	
	//handle mouse down on scroll thumb
	this._hThumbMouseDown = bind(function (event)
	{
		event=page.event(event); if (!event) { return; }
		if (event.preventDefault) {event.preventDefault();}

		Controls.addHandler(hThumb.ownerDocument,"mousemove",this._hThumbMouseMove);
		Controls.addHandler(hThumb.ownerDocument,"mouseup",this._hThumbMouseUp);
		Controls.addHandler(hThumb,"dragstart",this._sink);
		if (Controls.detectIE())
		{
			Controls.addHandler(hThumb.ownerDocument.body,"mouseleave",this._hThumbMouseUp);
		} else {
			Controls.addHandler(hThumb.ownerDocument,"mouseout",this._hThumbMouseOut);
		}

		dragOffsetX=event.clientX
		dragOffsetY=event.clientY

		origThumbLeft=Number(hThumb.style.left.replace(/px/g,""))
		if (!origThumbLeft) { origThumbLeft=0; }			

		//prevent the event from causing a click in the area (page up/down)
		skipAreaClick=true;		
		
		Controls.eventDone(event);
		return false;
	},this)
	
	//stop dragging
	this._hCancelDrag = bind(function ()
	{
		Controls.removeHandler(hThumb.ownerDocument,"mousemove",this._hThumbMouseMove);
		Controls.removeHandler(hThumb.ownerDocument,"mouseup",this._hThumbMouseUp);
		Controls.removeHandler(hThumb,"dragstart",this._sink);
		if (Controls.detectIE())
		{
			Controls.removeHandler(hThumb.ownerDocument.body,"mouseleave",this._hThumbMouseUp);
		} else {
			Controls.removeHandler(hThumb.ownerDocument,"mouseout",this._hThumbMouseOut);
		}
	},this);
	
	//move the scroll thumb, scroll the scroll area
	this._hThumbMouseMove = bind(function (event)
	{
		event=page.event(event); if (!event) { return; }

		var thumbSize = Controls.getSize(hThumbCenter);
		var areaSize = Controls.getSize(hScrollArea);
		var rightSize = Controls.getSize(hScrollDown);
		var scrollUpSize = Controls.getSize(hScrollUp);

		var min=0;
		var max=areaSize.x - thumbSize.x - scrollUpSize.x - rightSize.x - 1;

		var newLeft = Math.max(min,Math.min( origThumbLeft + (event.clientX-dragOffsetX) ,max));

		hThumb.style.left=(newLeft-1)+"px";
		this._hScrollTo(newLeft/(max-min));	

		return false;
	},this);
	
	//lifted the mouse, scroll over
	this._hThumbMouseUp = bind(function (event)
	{
		event=page.event(event); if (!event) { return; }
		this._hCancelDrag(); 
	
		//enable clicks in the area (page up/down)
		wait(function(){ skipAreaClick=false; },100,"scrollbar");
	
		return false;
	},this);
	
	this._hThumbMouseOut = bind(function (event)
	{
		event=page.event(event); if (!event) { return; }
		if (event.relatedTarget == undefined)
		{
			this._hCancelDrag(); 

			//enable clicks in the area (page up/down)
			wait(function(){ skipAreaClick=false; },100,"scrollbar");
			return false;
		}
	},this);
	
	// click in the top arrow of the scrollbar
	this._hTopMouseClick = bind(function (event)
	{
		this._doScroll(0,-14);		
		return false;
	},this);
	
	// click in the bottom arrow of the scrollbar
	this._hBottomMouseClick = bind(function (event)
	{
		this._doScroll(0,14);
		return false;
	},this);
	
	// click in the scroll area of the scrollbar (causes page up/page down)
	this._hAreaMouseClick = bind(function (event)
	{
		event=page.event(event); if (!event) { return; }
		if (skipAreaClick) { return; }
	
		tLeft=Controls.AbsLeft(hThumb);

		if (event.clientX>tLeft)
			{ this._hScrollPage(1); } 
		else 
			{ this._hScrollPage(-1); } 
	},this);
	
	this._getWrapperElement = function()
	{
		return scrollOuterElem;
	}
	/*
	 *  Utility functions
	 *
	 */
	this._getSize = function()
	{
		return Controls.getSize(scrollOuterElem);
	}
	
	this._setSize = function(width,height)
	{
		Controls.setSize(scrollOuterElem,width,height);
		this._update(true);
	}
	
	this._make();
}

Utils.implement(Scrollbar,ClassBase);

eval(Utils.declarePublic("Scrollbar",
	{
		update:1,
		scrollPage:1,
		scrollBy:1,
		scrollTo:1,
		scrollToTop:0,
		scrollToBottom:0,
		scrollToElem:2,
		isShowing:0,
		setUpdateCallback:1,
		destroy:0,
		getWrapperElement:0,
		setSize:2,
		getSize:0
	}
));


// /var/www/trailfire.com-release/content/client/lib/controls/hovertext.js 

/*
 * DHTML Hover Text Controls 
 * Copyright 2006 Pipestone
 *
 * relies on utils.js
 */

function setHoverText(hoverElem,hoverText,page, theme, title)
{
	if (hoverElem.hoverObject)
		hoverElem.hoverObject.setText(hoverText)
	else
	{
		if (theme)
			return new HoverTextThemed(page,hoverElem,hoverText, title);
			
		return new HoverText(page,hoverElem,hoverText);
	}
}

function clearHoverText(hoverElem)
{
	if (hoverElem.hoverObject)
		hoverElem.hoverObject.destroy();
}

function enableHoverText(hoverElem,fEnable)
{
	if (hoverElem.hoverObject)
	{
		if (fEnable)
			hoverElem.hoverObject.enable()
		else
			hoverElem.hoverObject.disable()		
	}
}

function HoverText(page,hoverElem,hoverText)
{
	ClassBase.call(this,"HoverText");

	this.classname = "HoverText";
	
	var hoverDiv = null;
	var hoverClass = 'psHoverOver';

	this.enabled = true;

	var _offsetY = 18;
	var _offsetX = -18;
	var _rightAlign = false;
	
	this.hideTimer = 0;
	this.showTimer =0;
	this.checkTimer = 0;

	this.hideTime = 1000; //milliseconds
	this.showTime = 500;
	this.mousePos = null;
	
	//this._maxShowTime = 5000;

	var posX = 0;
	var posY = 0;
	
	this._init = function()
	{	
		hoverElem.hoverObject = this;
		Controls.addHandler(hoverElem,"mouseover",this._onMouseOver);
		Controls.addHandler(hoverElem,"mouseout",this._onMouseOut);
		
	}
	
	this._destroy = function()
	{
		if (this.showTimer) 
		{ 
			clearWait(this.showTimer);
			this.showTimer = 0;
		}
		if (this.checkTimer)
		{ 
			clearWait(this.checkTimer);
			this.checkTimer = 0;
		}
		
		if (page &&
			this == page.currentHoverText)
			page.currentHoverText = null;
			
		if (hoverElem)
		{	
			Controls.removeHandler(hoverElem,"mouseover",this._onMouseOver);
			Controls.removeHandler(hoverElem,"mouseout",this._onMouseOut);
			hoverElem.hoverObject = null;
		}

		if (hoverDiv)
		{
			Controls.removeHandler(hoverDiv,"mouseover",this._sinkEvent);
			Controls.removeHandler(hoverDiv,"mouseout",this._sinkEvent);

			Controls.hide(hoverDiv);
			Controls.setPosition(hoverDiv,-15000,-15000);
			if (hoverDiv.parentNode)
							hoverDiv.parentNode.removeChild(hoverDiv);

			hoverDiv = null;
		}
		
		this._cleanup();
	
	}
	this._getText = function() { return hoverText; }

	this._setText = function(newText) 
	{
		hoverText = newText;

		if (hoverDiv)
			hoverDiv.innerHTML = newText;		
	}

	this._build = function()
	{
		hoverDiv = Controls.createElement(page.getDocument(),'div');
		hoverDiv.className = hoverClass;
		if (hoverElem.id)
			hoverDiv.id = hoverElem.id + '-hover';

		hoverDiv.innerHTML = hoverText;

		Controls.addHandler(hoverDiv,"mouseover",this._sinkEvent);
		Controls.addHandler(hoverDiv,"mouseout",this._sinkEvent);

		Controls.setPosition(hoverDiv,-15000,-15000);
		try {
			page.getDocument().body.appendChild(hoverDiv);		
		} catch (ex) {
			return ; // sink page change errors (permission denied)
		}
	}

	this._show = bind(function() 
	{ 
		if (!this.enabled) { return; }	
		if (page.currentHoverText && page.currentHoverText !=this)
		{
			page.currentHoverText.hide();
		}
		
		try 
		{ 
			if (!hoverDiv)
			{
				this._build();
			}

			var doc = page.getDocument();
			if (hoverDiv) 
			{
				 // Make sure that that we won't be too narrow
				 var ourSize = Controls.getSize(hoverDiv);
				 var pageSize = Utils.getBrowserWindowSize(doc);
				 var scrollOffsets = Utils.scrollOffsets(doc);
				 if (posX > pageSize[0] + scrollOffsets[0] -70)
				 	posX = pageSize[0] + scrollOffsets[0] -70;
				
			    // if menu we run off bottom of page, adjust so bottom of
			    // our menu will appear at the bottom of the page	
				if (posY + ourSize.y > pageSize[1] + scrollOffsets[1])
				{
					//top = Controls.AbsTop(elem) - ourSize.y;
					posY = pageSize[1] + scrollOffsets[1] - ourSize.y;
				}
		
				Controls.setPosition(hoverDiv,posX,posY); 

				Controls.show(hoverDiv);
				page.currentHoverText = this;
			}

			this.showTimer = 0;
			
			// Record position of hover Elem so we can detect whether it moved
			Controls.addHandler(doc,"mousemove",this._onMouseMove);				
			this.checkTimer =  wait(this._checkHide, 5000,"hoverText:checkHide");
			
		} catch (ex) { ; } 
	},this);
	
	// this checks to see whether the hover text should be hidden, in the case where the element moved in which case we
	// aren't getting the mouse out event
	this._checkHide = bind(function()
	{	
		if (this.mousePos &&
			Controls.PointInElem(this.mousePos.x,this.mousePos.y,hoverElem))
		{
			this.checkTimer =  wait(this._checkHide, 5000,"hoverText:checkHide");
			return;	
		}
		// otherwise, hide this tool tip
		this._hide();
		
	},this);
	
	this._hide = bind(function()
	{
		try {
			//hide hover div
			if (this.showTimer) { 
				clearWait(this.showTimer);
				this.showTimer = 0;
			}
			if (this.checkTimer) {
				clearWait(this.checkTimer);
				this.checkTimer = 0;
			}
			
			if (hoverDiv) {
				if (this == page.currentHoverText)
					page.currentHoverText = null;
				Controls.hide(hoverDiv);
				Controls.setPosition(hoverDiv,-15000,-15000);
			}
			Controls.removeHandler(page.getDocument(),"mousemove",this._onMouseMove);				
			

		} catch (ex) { 
			; //sink page changed errors (permission denied)
		}
	},this);

	this._onMouseOver = bind(function(event) 
	{
		event=page.event(event); if (!event) { return; }

		if (this.hideTimer)
		{
			clearWait(this.hideTimer); 
			this.hideTimer = 0;
		}

		var ePos = Utils.eventToBodyCoordinates(event);
		var doc = page.getDocument();

		if (_rightAlign)
		{
			var pos = Controls.getPosition(hoverElem);
			posX = pos.x;
		}
		else
			posX=ePos.x+_offsetX;
		posY=ePos.y+_offsetY;
		
		if (!this.showTimer)
		{
			this.showTimer = wait(this._show, this.showTime,"hoverText");
		}		
		
	},this);
		
	this._onMouseOut = bind(function(event) 
	{
		this._hide();
	},this);

	this._onMouseMove = bind(function(event) 
	{
		event=page.event(event); if (!event) { return; }

		this.mousePos = Utils.eventToBodyCoordinates(event);
		
	},this);

	this._sinkEvent = bind(function(event)
	{
		event = page.event(event); if (!event) return; 
		Controls.eventDone(event);
	},this);
	
	this._enable = function()
	{
		this.enabled = true;
	}

	this._disable = function()
	{
		this.enabled = false;
		this._hide();
	}
	this._setRightAlign = function(rightAlign)
	{
		_rightAlign = rightAlign;
	}
	this._init();
}

Utils.implement(HoverText,ModelBase);
eval(Utils.declarePublic(
	"HoverText",
	{
		setText:1,
		enable:0,
		disable:0,
		destroy:0,
		setRightAlign:1
	}
));

function HoverTextThemed(page,hoverElem,hoverText, title)
{
	//HoverText.call(this,page,hoverElem,hoverText);
	ClassBase.call(this,"HoverTextThemed");

	var hoverWindow = null;
	var hoverDiv = null;

	this.enabled = true;

	this.hideTimer = 0;
	this.showTimer = 0;
	this.hideTime = 250; //milliseconds
	this.showTime = 0;
	
	this._init = function()
	{		
		hoverElem.hoverObject = this;
		Controls.addHandler(hoverElem,"mouseover",this._onMouseOver);
		Controls.addHandler(hoverElem,"mouseout",this._onMouseOut);		
	}

	this._destroy = function()
	{
		
		if (this.showTimer) 
		{ 
			clearWait(this.showTimer);
			this.showTimer = 0;
		}
				
		Controls.removeHandler(hoverElem,"mouseover",this._onMouseOver);
		Controls.removeHandler(hoverElem,"mouseout",this._onMouseOut);
		Controls.removeHandler(page.getDocument(),"mousemove",this._onMouseMove);				
		
		hoverElem.hoverObject = null;

		if (hoverWindow)
		{
			hoverWindow.destroy();
			hoverWindow = null;
		}
		this._cleanup();
	
	}

	this._build = function()
	{
		var id = hoverElem.id + '-hover';
		var theme = new Theme(105,browser.getImageServer(),browser.getVersion());
		var parent = page.getDocument().body;
		
		//WindowBase(page,id,theme,parent,anchorElem,precacheCallback)
		hoverWindow = new WindowBase(page,id,theme,parent,hoverElem,null);
		hoverWindow.setTitle(title);
		hoverWindow.setTitleClass("dialogTitle");
		var contentDiv = hoverWindow.getContentElement();
		var body = Controls.createChildDiv(contentDiv,id,"dialog-body","psDialog-Body");
		body.innerHTML = hoverText;
		hoverDiv = hoverWindow.getContainerElement();
		hoverWindow.setSize(400,400);
		hoverWindow.setMouseCallbacks(this._onMouseOverHoverDiv, this._onMouseOutHoverDiv)

	}

	this._show = bind(function() 
	{ 	
		this.showTimer = 0; 
		
		if (!this.enabled) {return; }	

		try 
		{ 
			if (!hoverWindow)
			{
				this._build();
			}

			if (hoverWindow) 
			{	
				hoverWindow.setAnchor(hoverElem);
				hoverWindow.show(null);
			}
		} catch (ex) { ; } 
	},this);
		
	this._hide = bind(function()
	{
		this.hideTimer = 0; 
		try {
			if (hoverWindow) {
				hoverWindow.hide();
			}

		} catch (ex) { 
			; //sink page changed errors (permission denied)
		}
	},this);
	
	this._onMouseOverHoverDiv = bind(function(e) 
	{		
		if (this.hideTimer)
		{
			clearWait(this.hideTimer);
			this.hideTimer = 0;
		}
		
	},this);
	this._onMouseOutHoverDiv=bind(function(e) 
	{		
		this.hideTimer = wait(this._hide, this.hideTime,"hoverText.hide");
		
	},this);
	
	this._onMouseOver = bind(function(e) 
	{
		var event = page.event(e); if (!event) {return;}

		var fromElem;
		if (event.fromElement)
		{
			fromElem = event.fromElement;
		} else {
			fromElem = event.relatedTarget;
		}
			
		// If event for one of our children, then ignore
		if (Utils.isChildOf(fromElem,hoverElem))	
		{
			return;
		}

		if (this.hideTimer)
		{
			clearWait(this.hideTimer);
			this.hideTimer = 0;
		}
		
		this.showTimer = wait(this._show, this.showTime,"hoverText");
			
		Controls.eventDone(event);

	},this);
		
	this._onMouseOut = bind(function(e) 
	{
		var event = page.event(e); if (!event) {return;}

		var toElem;
		if (event.toElement)
		{
			toElem = event.toElement;
		} else {
			toElem = event.relatedTarget;
		}
	
		// If event for one of our children, then ignore
		if (Utils.isChildOf(toElem,hoverElem))	
		{
			return;
		}
		
		if (this.showTimer)
		{
			clearWait(this.showTimer);
			this.showTimer = 0;
		}
		this.hideTimer = wait(this._hide, this.hideTime,"hoverText.hide");
		Controls.eventDone(event);

	},this);
	
	this._setText = function(newText) 
	{
		hoverText = newText;

		if (hoverDiv)
			hoverDiv.innerHTML = newText;		
	}

	this._init();	
}
//Utils.implement(HoverTextThemed,HoverText);
eval(Utils.declarePublic(
	"HoverTextThemed",
	{
		setText:1,
		enable:0,
		disable:0,
		destroy:0
	}
));

// /var/www/trailfire.com-release/content/client/lib/controls/buttons.js 

/**
 * DHTML Controls Library
 * Utilities for creating and managing themed DHTML Button controls 
 * Copyright 2006 Pipestone
 **/

function ButtonBase(className,page,callback,hint)
{
	ClassBase.call(this,className);
		
	var element = null;
	var enabled = true;
	var shown = true;
	var down = false;

	// derived classes should define these functions
	this._buildDOMElement = function()
	{
		//create and return the DOM element
	}

	this._destroyDOMElement = function()
	{
		//destroy the DOM element
	}

	this._setState = function(state)
	{
		//adjust the DOM element visual state (null,"down","disable","hover")
	}

	this._getDOMElement = function() { return element; }

	this._setParent = function(elem,beforeElem)
	{
		this._build();
		if (beforeElem)
			elem.insertBefore(element,beforeElem);
		else
			elem.appendChild(element);
	};
	
	this._disable = function(disableHint)
	{
		if (!enabled) { return; }
		enabled=false;

		element.style.cursor = "default";
		this._setState("disable");

		if (disableHint)
			setHoverText(element,disableHint,page);
		else
			enableHoverText(element,false);
	};
	
	this._enable = function()
	{
		if (enabled) { return; }
		enabled = true;

		element.style.cursor = "pointer";
		this._setState();

		enableHoverText(element,true);
		if (hint) setHoverText(element,hint,page);
	};

	this._show = function()
	{
		Controls.show(element,true);
	}

	this._hide = function()
	{
		Controls.hide(element);
	}

	this._onClick = bind(function(event)
	{
		event = page.event(event); if (!event) { return; }

		if (enabled && callback)
			callback(event);

		//Controls.eventDone(event);
	},this);
		
	this._onMouseDown = bind(function(event)
	{
		event = page.event(event); if (!event) { return; }
		Controls.eventPreventDefault(event);

		if (enabled)
		{
			down = true;
			this._setState("down");
		}

		//Controls.eventDone(event);
		return false;
	},this);
		
	this._onMouseUp = bind(function(event)
	{
		event = page.event(event); if (!event) { return; }
		if (enabled && down)
		{
			down = false;
			this._setState("hover");
		}
		
		//Controls.eventDone(event);	
	},this);

	this._onMouseOver = bind(function(event)
	{
		event = page.event(event); if (!event) { return; }
		if (enabled)
			this._setState("hover");
	},this);

	this._onMouseOut = bind(function(event)
	{
		event = page.event(event); if (!event) { return; }
		if (enabled)
		{
			down = false;
			this._setState();
		}
	},this);


	this._build = function()
	{
		element = this._buildDOMElement();
		element.style.cursor = "pointer";

		if (hint) setHoverText(element,hint,page);

		Controls.addHandler(element,"mousedown",this._onMouseDown);
		Controls.addHandler(element,"mouseup",this._onMouseUp);
		Controls.addHandler(element,"click",this._onClick);
		Controls.addHandler(element,"mouseover",this._onMouseOver);
		Controls.addHandler(element,"mouseout",this._onMouseOut);
	};

	this._destroy = function()
	{
		Controls.removeHandler(element,"mousedown",this._onMouseDown);
		Controls.removeHandler(element,"mouseup",this._onMouseUp);
		Controls.removeHandler(element,"click",this._onClick);
		Controls.removeHandler(element,"mouseover",this._onMouseOver);
		Controls.removeHandler(element,"mouseout",this._onMouseOut);

		clearHoverText(element);
		element = null;

		this._destroyDOMElement();
		this._cleanup();
	};
}
Utils.implement(ButtonBase,ClassBase);

ButtonBase.prototype.setParent=function(elem,beforeElem)	{	return this._setParent(elem,beforeElem);		};
ButtonBase.prototype.destroy=function()			{	return this._destroy();				};
ButtonBase.prototype.enable=function()			{	return this._enable();				};
ButtonBase.prototype.disable=function(hint)			{	return this._disable(hint);				};
ButtonBase.prototype.show=function()			{	return this._show();				};
ButtonBase.prototype.hide=function()			{	return this._hide();				};
ButtonBase.prototype.getDOMElement=function()	{   return this._getDOMElement();		};

/* Text Button */
function TextButton(page,callback,label,hint,cssId,cssClass,removable)
{
	ButtonBase.call(this,"TextButton",page,callback,hint);

	var classBase = "textButton";
	if (cssClass) classBase = cssClass;
	var classEnable = classBase;
	var classHover 	= classBase + " " + classBase + "Hover";
	var classDown 	= classBase + " " + classBase + "Down";
	var classDisable= classBase + " " + classBase + "Disabled";

	var element;

	this._buildDOMElement = function()
	{
		element = Controls.createElem("DIV",page.getDocument(),null,cssId,classEnable);
		element.style.display = "inline";

		if (label)
			element.innerHTML = label;

		return element;
	}

	this._destroyDOMElement = function()
	{
		if (removable)
			if (element.parentNode)
				element.parentNode.removeChild(element);
		element = null;
	}

	this._setState = function(state)
	{
		switch(state)
		{
			case "down":
					element.className = classDown;
				break;
			case "hover":
					element.className = classHover;
				break;
			case "disable":
					element.className = classDisable;
				break;
			default:
					element.className = classEnable;
				break;
		}
	}
	
	this._setLabel = function(newLabel)
	{
		if (element)
			element.innerHTML = newLabel;
		label = newLabel;
	}
}
Utils.implement(TextButton,ButtonBase);
TextButton.prototype.setLabel=function(l)	{	return this._setLabel(l);		};

/* Image Button */
function ImageButton(page,callback,theme,buttonName,hint,cssId,cssClass)
{
	ButtonBase.call(this,"ImageButton",page,callback,hint);

	if (!cssClass) { cssClass = "ImageButton"; }

	var element;

	this._fix = bind(function()
	{
		if (element)
		{
			//this._element.onload = null;
			Controls.removeHandler(element, 'load', this._fix);
			Controls.imageFix(element,page);
		}
	},this);
	
	this._buildDOMElement = function()
	{
		element = Controls.createElem("IMG",page.getDocument(),null,cssId,cssClass);
		this._setState();
		return element;
	}

	this._destroyDOMElement = function()
	{
		//element.onload = null;
		Controls.removeHandler(element, 'load', this._fix);

//		if (element.parentNode)
//			element.parentNode.removeChild(element);

		element = null;
	}

	this._setState = function(state)
	{
		//element.onload = fix;
		Controls.addHandler(element, 'load', this._fix);
		
		switch(state)
		{
			case "down":
					element.src = theme.buttonPath(buttonName,"down");
				break;
			case "hover":
					element.src = theme.buttonPath(buttonName,"hover");
				break;
			case "disable":
					element.src = theme.buttonPath(buttonName,"off");
				break;
			default:
					element.src = theme.buttonPath(buttonName,"on");
				break;
		}
	}
}
Utils.implement(ImageButton,ButtonBase);

/* Image Button with own images for normal, disable, hover, down states */
function ImageButtonWithImages(page,callback,hint,cssId,cssClass, images)
{
	this._images = images;
	this._element = null;
	
	ButtonBase.call(this,"ImageButtonWithImages",page,callback,hint);

	if (!cssClass) { cssClass = "ImageButtonWithImages"; }


	this._buildDOMElement = function()
	{
		this._element = Controls.createElem("IMG",page.getDocument(),null,cssId,cssClass);
		this._setState();
		return this._element;
	}

	this._destroyDOMElement = function()
	{
		//this._element.onload = null;
		Controls.removeHandler(this._element, 'load',this._fix);
		

//		if (element.parentNode)
//			element.parentNode.removeChild(element);

		this._element = null;
	}
	
	this._fix = bind(function()
	{
		if (this._element)
		{
			//this._element.onload = null;
			Controls.removeHandler(this._element, 'load', this._fix);
			Controls.imageFix(this._element,page);
		}
	},this);
	
	this._setState = function(state)
	{
		//this._element.onload = bind(fix, this);
		Controls.addHandler(this._element, 'load', this._fix);
		
		switch(state)
		{
			case "down":
					this._element.src = this._images[3];
				break;
			case "hover":
					this._element.src = this._images[2];
				break;
			case "disable":
					this._element.src = this._images[1];
				break;
			default:
					this._element.src = this._images[0];
				break;
		}
	}
	this._setImages=function(images)
	{
		this._images = images;
		this._setState();
	}
}

Utils.implement(ImageButtonWithImages,ButtonBase);
ImageButtonWithImages.prototype.setImages=function(i)	{	return this._setImages(i);		};

/* Image Text Button */
function ImageTextButton(page,callback,label,theme,buttonName,buttonClass,hint,cssId,cssClass)
{
	ButtonBase.call(this,"ImageTextButton",page,callback,hint);

	var classBase = "textButton";
	if (cssClass) classBase = cssClass;
	var classEnable = classBase;
	var classHover 	= classBase + " " + classBase + "Hover";
	var classDown 	= classBase + " " + classBase + "Down";
	var classDisable= classBase + " " + classBase + "Disabled";

	var element;
	var img;
	var labelDiv;

	this._fix = bind(function()
	{
		if (element)
		{
			//this._element.onload = null;
			Controls.removeHandler(element, 'load', this._fix);
			Controls.imageFix(element,page);
		}
	},this);

	this._buildDOMElement = function()
	{
		element = Controls.createElem("DIV",page.getDocument(),null,cssId,classEnable);
		labelDiv = Controls.createChildElem("DIV",element,cssId,"label",classEnable+"Label");
		img = Controls.createChildElem("IMG",element,cssId,"image",buttonClass);

		labelDiv.style.display = "inline";
		element.style.display = "inline";

		this._setState();

		if (label)
			labelDiv.innerHTML = label;

		return element;
	}

	this._destroyDOMElement = function()
	{
		//img.onload = null;
		Controls.removeHandler(img, 'load', this._fix);		
		img = null;
		labelDiv = null;

//		if (element.parentNode)
//			element.parentNode.removeChild(element);
		element = null;
	}

	this._setState = function(state)
	{
		//img.onload = fix;
		Controls.addHandler(img, 'load', this._fix);		

		switch(state)
		{
			case "down":
					element.className = classDown;
					img.src = theme.buttonPath(buttonName,"down");
				break;
			case "hover":
					element.className = classHover;
					img.src = theme.buttonPath(buttonName,"hover");
				break;
			case "disable":
					element.className = classDisable;
					img.src = theme.buttonPath(buttonName,"off");
				break;
			default:
					element.className = classEnable;
					img.src = theme.buttonPath(buttonName,"on");
				break;
		}
	}
	
	this._setLabel = function(newLabel)
	{
		if (labelDiv)
			labelDiv.innerHTML = newLabel;
		label = newLabel;
	}
}
Utils.implement(ImageTextButton,ButtonBase);
ImageTextButton.prototype.setLabel=function(l)	{	return this._setLabel(l);		};

/* Logo Button */
function LogoButton(page,callback,theme,hint,cssId,cssClass)
{
	ButtonBase.call(this,"LogoButton",page,callback,hint);

	if (!cssClass) { cssClass = "LogoButton"; }

	var element;

	this._fix = bind(function()
	{
		if (element)
		{
			//this._element.onload = null;
			Controls.removeHandler(element, 'load', this._fix);
			Controls.imageFix(element,page);
		}
	},this);
	
	this._buildDOMElement = function()
	{
		element = Controls.createElem("IMG",page.getDocument(),null,cssId,cssClass);
		this._setState();
		return element;
	}

	this._destroyDOMElement = function()
	{
		//element.onload = null;
		Controls.removeHandler(element, 'load', this._fix);		
		

//		if (element.parentNode)
//			element.parentNode.removeChild(element);

		element = null;
	}

	this._setState = function(state)
	{
		//element.onload = fix;
		Controls.addHandler(element, 'load', this._fix);

		switch(state)
		{
			case "down":
					element.src = theme.logoButtonPath("down");
				break;
			case "hover":
					element.src = theme.logoButtonPath("hover");
				break;
			case "disable":
					element.src = theme.logoButtonPath("off");
				break;
			default:
					element.src = theme.logoButtonPath("on");
				break;
		}
	}
}
Utils.implement(LogoButton,ButtonBase);



// /var/www/trailfire.com-release/content/client/lib/window/window-base.js 

/*
  DHTML Window Library 
  Copyright 2006 Pipestone
 
  create a new window, optionally with drag, resize and/or anchoring

  usage:
   win = new WindowBase(page,id,theme,parent,anchor)
  
   page - the pageModel that will contain the window

   id  - a unique identifier for the window (must start with a letter)

   theme - a theme object (see lib/themes.js) that will be used to draw the window

   parent - optional, a parent window (for window stacking and ordering [not implemented])

   anchor - optional, a DOM object that will define the position of the window.  Used to 
 				create a "pointer" that indicates a relationship between the element
 				and the window. NOTE: using an achor defines the position; using setPosition
 				while using an anchor is unsupported.

*/

function WindowBase(page,id,theme,parent,anchorElem)
{
	ClassBase.call(this,"WindowBase");

	//define self for event callbacks
	var self				= this;
	var doc = page.getDocument();

	//event callback hooks 
	var dragCallbacks 		= {start:null,	drag:null,	end:null};
	var resizeCallbacks 	= {start:null, resize:null, end:null, size:null};
	var mouseCallbacks 		= {over:null, out:null};

	//window parameters
	var anchor				= null;
	var quadrantAnchor		= null;
	var quadrantOverride	= null;
	var pointerElement		= null;
	var windowSize			= {x:0,y:0};
	var windowPosition		= {x:0,y:0};

	//window states
	var isShown				= false;
	var isSticky			= false;
	var isFrozen			= false;
	var inDrag				= false;
	var inResize			= false;
	var canDrag				= false;
	var canResize			= false;
	var loadingCache		= false;
	var shownForDrag		= false;
	
	var secondPhaseWatchId  = 0;	
	var errorCheckTimer = false;
	
	var minSize				= {x:0,y:0};
	var maxSize				= {x:0,y:0};

	// DOM elements
	var themeCacheElement   = null;
	var cacheDiv			= null;
	var container, background, content, leftbar, rightbar, body;

	var scrollLayer = null;
	var title, status;
	var corners = Utils.newArray();
	var pointers = Utils.newArray();
	var resizers = Utils.newArray();
	var repeats = Utils.newArray();

	// the anchor offset is read from the stylesheet, determines
	// the window offset from the pin
	var anchorOffsets = Utils.newArray();

	// Style Definitions
	var noStyle 			= { display:"none" };

	// Corner Styles
	var c = Utils.newArray();
	c[0] = "TopLeft";
	c[1] = "TopRight";
	c[2] = "BottomLeft";
	c[3] = "BottomRight";

	// Border Styles
	var r = Utils.newArray();
	r[0] = "Top";
	r[1] = "Left";
	r[2] = "Right";
	r[3] = "Bottom";
			
	var _lastQuadrant		= -2;
	
	var _drawArea = null;
	var _pointerDrawArea = null;	

	// _init() - main entry point
	this._init = function()
	{
		var profilerId = profiler.start("windowBase.init");

		try
		{
			isShown = false;
			if ((doc === null) && (parent != null))
				{ doc=parent.ownerDocument; }
				
			//set the default window size
			windowSize = theme.getMinSize();
			minSize = theme.getMinSize();
			maxSize = theme.getMaxSize();
	
			this._loadWait(1);
	
			this._setTheme(theme);
			this._buildWindow();

			//begin the loadDelay system
			secondPhaseWatchId = wait(bind(this._secondPhase,this),loadDelay,"window-load-delay");
	
			this._loadWait(-1);
		} catch(ex) { 
			this._loadFailed();
			return false;
		}

		profiler.stop(profilerId);
	};
	
	//The window can be displayed inproperly if the images are not loaded - so we employ a delay
	// to prevent the display of half drawn windows
	
	var loadDelayCallbacks	= Utils.newArray();	//list of functions to call when the cache has loaded
	var loadDelay			= 150;  //timer to watch for load to finish
	var loadInserted		= false;
	var loadStyleComplete	= false;//when loadDelay has finished (we think all the styles are rendered)
	var loadDelayComplete	= false;//when loadDelay has finished (we think all the images are loaded)
	var loadComplete		= false;//flag for loadWait to set when all elements are loaded
	var loadWaitElements	= 0;	//count of loading images
	var loadWaitElementArray= Utils.newArray();
	var loadWaitCount		= 0;
	var maxLoadWait			= 60000; //wait time at most before displaying window
	var loadFailed 			= false;
	var loadErrorCallbacks		= Utils.newArray();

	this._onLoadImg = bind(function(evt) {
		var event = page.event(evt);
		if (event && event.target)
		{
			this._loadWait(-1,event.target);
		}
	},this);
	
	//loadWait(delta)- +1 to add a wait, -1 when finished
	//part of the loadDelay system, once loadWaitElements reaches 0, loadComplete will be set
	// and the secondPhase of window creation will be called
	this._loadWait = function(delta,elmt)	
	{
		loadWaitElements += delta;

		if (elmt && (delta > 0))
		{
			loadWaitElementArray.push(elmt);
			if (elmt.tagName.toUpperCase() === "IMG")
			{
				Controls.addHandler(elmt, 'load', this._onLoadImg); 
			}

			if (elmt.tagName.toUpperCase() === "LINK")
			{
				if (Controls.detectIE()) //firefox doesn't report the onload on a style sheet
				{
					if (elmt.readyState == "complete")
					{
						this._loadAnchorOffsetsManually(elmt.styleSheet.rules);
						this._loadWait(-1,elmt);						
					} else {
						// don't show until the stylesheet has loaded
						elmt.onload = bind(function(evt) { 
							try
							{
								var event = page.event(evt);
								if (event && event.target)
								{
									this._loadAnchorOffsetsManually(elmt.styleSheet.rules);
									this._loadWait(-1,event.target);
								}
							} catch(ex) { ; }
						},this);
					}
				} else if (Controls.detectFirefox()) {		
					elmt.onload = true;
					// use a sleeping loop to watch the themeCacheElement for clues of being loaded
					var timeout = 20000;
					function ffWatch()
					{
						timeout -= 250;
						try	// we are expecting an "ACCESS VIOLATION" to be called when the style sheet isn't loaded
						{
							if (elmt.sheet.cssRules.length>0)
							{	
								//parse the stylesheet for the anchor offset values
								this._loadAnchorOffsetsManually(elmt.sheet.cssRules)	

								// that is our sign, time to load
								this._loadWait(-1,elmt);
								elmt.loadWatchId = 0;
							} else { //wait

								if (timeout>0)
									elmt.loadWatchId = wait(bind(ffWatch,this),250,"window-css-watch");
								else	//can't wait any longer (this is an error) 
									this._loadWait(-1,elmt);

							}
						} catch(ex) { //wait

							if (timeout>0)
								elmt.loadWatchId = wait(bind(ffWatch,this),250,"window-css-watch");
							else 
								this._loadWait(-1,elmt);

						}
					}
	
					elmt.loadWatchId = wait(bind(ffWatch,this),250,"window-css-watch");
				} else { 
					this._loadWait(-1,elmt);		// just cross our fingers and hope for a speedy load
				}
			}
		}

		if (elmt && (delta < 0))
		{
			//elmt.onload = null;
			Controls.removeHandler(elmt,'load',null);
		}
		
		if (loadWaitElements === 0)
		{
			loadComplete=true;
			loadWaitElementArray=null;
		}
	}

	this._loadWaitRecover = function()
	{
		Utils.map(loadWaitElementArray,bind(function(elmt)
		{
			if (elmt.onload != null && 
				((elmt.complete === true) || (elmt.readyState === "complete")))
			{
				this._loadWait(-1,elmt);
			} 
		},this));
	}

	// from an external object, register a function to be called
	// when loading is complete (or immediately if loading is already complete)
	this._loadDelayedCallback = function(callback)
	{
		if (!loadDelayComplete) //wait for window to be created
		{
			loadDelayCallbacks.push(callback);
			return "delayed";
		} else {
			return callback();
		}
	}

	// from an external object, register a function to be called
	// when loading is complete (or immediately if loading is already complete)
	this._loadErrorCallback = function(callback)
	{
		if (!loadFailed) //wait for window to be created
		{
			if (!loadDelayComplete)
				loadErrorCallbacks.push(callback);
		} else {
			callback();
		}
	}
	
	this._loadFailed = function()
	{
		loadFailed = true;
		Utils.map(loadErrorCallbacks,bind(function(func)
		{
			try //to keep processing, even after errors
			{
				func();
			} catch(ex) {
				Log.error(ex);
			}
		},this));
	}

	//finalize window creation, after the loadDelay (to insure all images are loaded before showing)
	this._secondPhase =	function() 
	{ 
		try
		{
			if (!doc.body) //page went away before elements were loaded
			{
				this._loadFailed();
				return false;
			}
	
			if (loadWaitCount > maxLoadWait)
			{
				this._loadFailed();
				return false;
			}

			var profilerId = profiler.start("windowBase.secondPhase");
		
			if (!loadComplete)	// keep waiting for loading to finish
			{	
				loadWaitCount+=loadDelay;
				this._loadWaitRecover();
				secondPhaseWatchId = wait(bind(this._secondPhase,this),loadDelay,"window-load-delay");

				profiler.stop(profilerId);
				return;
			} 
	
			if (!loadInserted)
			{
				try {	
					page.appendToBody(container,null,bind(function()
					{			
						//trick the browser into loading images that haven't been displayed (so we can imageFix them)
						Controls.setOpacity(container,0.01);
						Controls.setPosition(container,0,0);
						secondPhaseWatchId = wait(bind(function()
						{
							Controls.setPosition(container,-15000,-15000);
							secondPhaseWatchId = wait(bind(this._secondPhase,this),25,"window-load-delay");
						},this),50,"window-load-delay");
			
						loadInserted = true;
					},this));
				} catch (ex) { this._loadFailed(); } //sink page change error, stop processing

				_drawArea = new Controls.DrawArea(page,container);
				_pointerDrawArea = new Controls.DrawArea(page,pointers[0]);

				profiler.stop(profilerId);
				return;
			}
	
			if (!loadStyleComplete)
			{
				if (anchorOffsets.length === 0)
				{
					loadWaitCount+=loadDelay;
					secondPhaseWatchId = wait(bind(this._secondPhase,this),loadDelay,"window-load-delay");
					return;
				} else {
					//fix the images with backgrounds that are alpha channel png
					for (var i=0; i<4; i++)
					{
						Controls.imageFix(repeats[i],page);
						Controls.imageFix(corners[i],page);
						Controls.imageFix(pointers[i],page);
						Controls.imageFix(resizers[i],page);
					}
					loadStyleComplete=true;
					secondPhaseWatchId = wait(bind(this._secondPhase,this),25,"window-load-delay");
					profiler.stop(profilerId);
					return;
				}
			}
	
			// set the load delay flag so functions stop delaying
			loadDelayComplete = true;
			this._setAnchor(anchorElem);
			this._windowEvents();	//attach event handlers
	
			profiler.stop(profilerId);

			var profilerId = profiler.start("windowBase.loadDelayCallbacks");
	
			// functions that require the window to be created may have registered
			//	themselves in the loadDelayCallbacks queue.  The loadDelay has completed
			//	so these functions are called and the queue is cleared
			Utils.map(loadDelayCallbacks,bind(function(func)
			{
				try //to keep processing, even after errors
				{
					func();
				} catch(ex) {
					Log.error(ex);
				}
			},this),bind(function()	{	
	
				loadDelayCallbacks = null;
				loadErrorCallbacks = null;
				// when multiple windows with the same theme are loading, they may all be waiting
				// on the same caching div to complete.  If we are the first window, then we should alert
				// the other windows that the cache is completed loading		
				if (loadingCache)
				{
					cacheDiv.loading=false;
					Utils.map(cacheDiv.loadCallbacks,bind(function(func)
					{
						try //to keep processing, even after errors
						{
							func();
						} catch(ex) {
							Log.error(ex);
						}
					},this),bind(function()	{	
						cacheDiv.loadCallbacks = null;
					},this));
				}
			},this));
			

			profiler.stop(profilerId);

		} catch(ex) {
			this._loadFailed();
			return false;
		}
	}

	this._getPosition = function() 				{ return { x:windowPosition.x, y:windowPosition.y }; }
	this._getSize = function() 					
	{
		if (windowSize.x === 0)
			windowSize = Controls.getSize(container); 
		return {x:windowSize.x,y:windowSize.y}; 
	};

	this._getContentElement = function() 	{ return body; };
	this._getStatusElement= function() 		{ return status; };
	this._getTitleElement = function() 		{ return title; };
	this._getContainerElement = function() 		{ return container; };

	this._stick = function(callback)	//sticky windows do not auto hide					
	{
		if (!loadDelayComplete)	//wait for window to be created
		{
			loadDelayCallbacks.push(bind(function()
				{
					this._stick();
				},this)
			);
			return true;
		}
		
		isSticky = true; 		
		this._show(callback);
	};

	this._unstick = function()					
	{ 
		if (!loadDelayComplete)	//wait for window to be created
		{
			loadDelayCallbacks.push(bind(function()
				{
					this._unstick();
				},this)
			);
			return true;
		}
		
		isSticky = false;
		this._hide();
	};

	this._stuck	= function()					{ return isSticky;		};

	this._freeze = function()  //windows are frozen by modal dialogs, prevents closing, dragging and sizing					
	{
	 	//set the frozen flag
		isFrozen = true;		
		this._show();
	};
	this._thaw	 = function()					{ isFrozen = false;		};
	this._frozen = function()					{ return isFrozen;		};

	this._isInDrag	 = function()				{ return inDrag;		};
	this._isInResize	 = function()			{ return inResize;		};
	
	this._setDragCallbacks = function(onDragStart, onDrag, onDragEnd)
	{
		dragCallbacks.start	=	onDragStart;
		dragCallbacks.drag	=	onDrag;
		dragCallbacks.end	=	onDragEnd;
	}

	this._setResizeCallbacks = function(onResizeStart, onResize, onResizeEnd, onSize) 
	{
		resizeCallbacks.start	=	onResizeStart;
		resizeCallbacks.resize	=	onResize;
		resizeCallbacks.end		=	onResizeEnd;
		resizeCallbacks.size	= 	onSize;
	}

	this._setMouseCallbacks = function(onMouseOver, onMouseOut) 
	{
		mouseCallbacks.over		= 	onMouseOver;
		mouseCallbacks.out		=	onMouseOut;
	}
	
	this._setClass = function(className) 		{ container.className = "psWindow "+className;	};
	this._setTitleClass = function(className)	{ title.className = className; };
	this._setStatusText	= function(message)		{ status.innerHTML=message; }
	this._setTitle	= function(message)			{ title.innerHTML=message; }
	this._setContent	= function(message)			{ content.innerHTML=message; }

											
	this._cachePrefix	= function()
	{
		return "psWindowCacheStyle";
	}

	this._themeCachePrefix	= function()
	{
		return this._cachePrefix() + "-" + theme.getId();
	}

	this._themeCacheDivId = function() 
	{
		return "psWindowCache-"+theme.getId();	
	}

	// before the window is shown, the components have no size, so calculations fail.  This
	//  function retrieves the size of the component in the window theme cache, since it is
	//  always loaded (thanks to load delay) before calculating window sizes
	this._getComponentSize = function(component)
	{
		var cacheId = this._themeCachePrefix() + "-" + component;
		return Controls.getSize(doc.getElementById(cacheId));			
	}
	
	//when the window is anchored, the pointer is drawn to indicate the windows' relationship to the anchor
	//	for each quadrant of the document, this pointer may be positioned and sized differently
	//  this information comes from the theme data, by looking at the size of the image in the 
	//  theme cache. quad : (0=topleft, 1=topright, 2=bottomleft, 3=bottomright)
	this._getPointerSize = function(quad)
	{
		return this._getComponentSize("p"+quad);
	}

	//load the anchorOffsets by parsing through the stylesheet rules
	this._loadAnchorOffsetsManually = function(cssRules)
	{
		var themeId=this._cachePrefix() + theme.getId()
		var rules = {
						0: "body div#" + id + " div.anchorGhost .TopLeft",
						1: "body div#" + id + " div.anchorGhost .TopRight",
						2: "body div#" + id + " div.anchorGhost .BottomLeft",
						3: "body div#" + id + " div.anchorGhost .BottomRight"
					};

		var trigger = "body div#" + id + " div.anchorGhost";
		var found = 0;
		var triggerFound = false;

		for (var i=0; i<cssRules.length; i++)
		{
			//the trigger rules will come before the 4 anchorGhost rules
			if (triggerFound) {
				for(var j=0; j<4; j++)
				{
					if (cssRules[i].selectorText.toUpperCase() === rules[j].toUpperCase())
					{ 	// parse
						anchorOffsets[j] = Controls.getStylePosition(cssRules[i]);
						found++;
						if (found >= 4)
						{
							return true;
						}
					}
				}			
			} else {
				if (cssRules[i].selectorText.toUpperCase() === trigger.toUpperCase())
					triggerFound = true;
			}						
		}
		return false; //failed to find them rules
	}

	//get the offset (from the anchor qhost in the stylesheet) of the anchor from the window
	// offset is relative to the quadrant's corner (0=topleft, 1=topright, 2=bottomleft, 3=bottomright)
	this._getAnchorOffset = function(quad)
	{
		return anchorOffsets[quad];
	}

	//when dragging by the anchor, adjust the position so that the window isn't placed offscreen
	this._anchorMoveAdjust = function(position)
	{
		var quadrant = this._getQuadrant();
		if (quadrant === -1) { return false; }

		var anchorOffset = this._getAnchorOffset(quadrant);
		if (!anchorOffset) { return false; }

		if ((position.y-anchorOffset.y)<0)
		{
			position.y = anchorOffset.y;
		}
		
		return position;
	}

	this._getAnchor = function() { return quadrantAnchor; }

	this._setAnchor = function(elem)			
	{
		if (!loadDelayComplete) //wait for window to be created
		{
			loadDelayCallbacks.push(bind(function() 
				{
					this._setAnchor(elem);
				},this));
			return true;
		}

		if (elem != quadrantAnchor) // new anchor element
		{
			_lastQuadrant  = -2;
		}

		quadrantAnchor = elem; 

		if (quadrantAnchor)
		{ 
			_setAnchorWaitCount = 0;

			var quadrant = this._getQuadrant();
			if (quadrant === -1)		//wait for anchorOffset to be calculated before doing this
			{			
				return true;
			}

			var anchorPos = Controls.getPosition(elem);
			var anchorOffset = this._getAnchorOffset(quadrant);	
		} else { 
			quadrant = -1;
		}

		switch (quadrant)
		{
			case 0:
				this._setPosition(anchorPos.x - anchorOffset.x,anchorPos.y-anchorOffset.y);
				break;
			case 1:
				this._setPosition(anchorPos.x - anchorOffset.x - windowSize.x,anchorPos.y - anchorOffset.y);
				break;
			case 2:
				this._setPosition(anchorPos.x - anchorOffset.x,anchorPos.y - anchorOffset.y - windowSize.y);
				break;
			case 3:
				this._setPosition(anchorPos.x - anchorOffset.x - windowSize.x,anchorPos.y - anchorOffset.y - windowSize.y);
				break;						
			default:
				this._updateLayout(); 
				break;
		}
	}

	this._anchorPosition = function()			
	{
		var anchorPos = {x:0,y:0};	
		if (!quadrantAnchor) { return anchorPos; }

		var quadrant = this._getQuadrant();
		if (quadrant===-1) { return false; }

		var anchorOffset = this._getAnchorOffset(quadrant);
		if (!anchorOffset) { return false; }

		switch (quadrant)
		{
			case 0:
				anchorPos.y = windowPosition.y
				anchorPos.x = windowPosition.x;
				break;
			case 1:
				anchorPos.y = windowPosition.y
				anchorPos.x = windowPosition.x + windowSize.x ;
				break;
			case 2:
				anchorPos.y = windowPosition.y + windowSize.y;
				anchorPos.x = windowPosition.x;
				break;
			case 3:
				anchorPos.y = windowPosition.y + windowSize.y;
				anchorPos.x = windowPosition.x + windowSize.x;
				break;						
		}
		anchorPos.x += anchorOffset.x;
		anchorPos.y += anchorOffset.y;
		return anchorPos;
	}


	this._setMinSize = function(x,y) { minSize.x=x; minSize.y=y; }
	this._setMaxSize = function(x,y) { maxSize.x=x; maxSize.y=y; }
	
	this._setSize = function(x,y) { 
		var profilerId = profiler.start("windowBase.setSize");

		if (!loadDelayComplete) //wait for window to be created
		{
			loadDelayCallbacks.push(bind(function() 
				{
					this._setSize(x,y);
				},this));
			return true;
		} 

		if (!theme.isResizable()) { return; }

		//constrain to max & min size
		x = Math.min(maxSize.x,Math.max(minSize.x,x));
		y = Math.min(maxSize.y,Math.max(minSize.y,y));

		windowSize.x=x; windowSize.y=y;

		Controls.setSize(container,x,y);
		
		var mTopLeft = Controls.getSize(corners[0]);
		var mBottomRight = Controls.getSize(corners[3]);

		var innerWidth = (x-mTopLeft.x-mBottomRight.x);
		var innerHeight = (y-mTopLeft.y-mBottomRight.y);

		var bTopLeft = mTopLeft;
		var bBottomRight = mBottomRight;
		
		if (Controls.detectIE()) //IE Hack
		{
			function noPX(prop)
			{
				return Number(prop.substring(0,prop.length-2));
			}

			var cs = body.currentStyle;
			bTopLeft.y = noPX(cs.top);
			bTopLeft.x = noPX(cs.left);
			bBottomRight.y = noPX(cs.marginBottom);
			bBottomRight.x = noPX(cs.marginRight);
		}

		if (isNaN(bTopLeft.y)) {bTopLeft.y=0;}
		if (isNaN(bTopLeft.x)) {bTopLeft.x=0;}

		var bInnerWidth = (x-bTopLeft.x-bBottomRight.x);
		var bInnerHeight = (y-bTopLeft.y-bBottomRight.y);

		if (Controls.detectIE()) //IE Hack
		{				
			Controls.setSize(background,x,y);
			Controls.setSize(content,innerWidth,innerHeight);

			repeats[1].style.height = innerHeight+"px";
			repeats[2].style.height = innerHeight+"px";
			repeats[0].style.width= innerWidth+"px";
			repeats[3].style.width= innerWidth+"px";

			if (scrollLayer)
			{
				scrollLayer.setSize(innerWidth,innerHeight);
				body.style.width = bInnerWidth+"px";
				this._updateScrollbars();
			} else {
				Controls.setSize(body,bInnerWidth,bInnerHeight);
			}

		} else {
			if (scrollLayer)
			{
				this._updateScrollbars();
			}
		}

		this._moveDrawArea();

		if (isShown)
			if (resizeCallbacks.size)
				{ resizeCallbacks.size(x,y,bInnerWidth,bInnerHeight); }

		profiler.stop(profilerId);
	}

	this._setPosition = function(x,y) { 
		
		if ( (typeof(x) === "undefined") ||
			 (typeof(y) === "undefined") ||
			 (x===NaN) || (y===NaN) )
		{
			Log.debug("window._setPosition - Not a number");
			return;
		}

		if (!loadDelayComplete) //wait for window to be created
		{
			loadDelayCallbacks.push(bind(function() 
				{
					this._setPosition(x,y);
				},this));
			return true;
		}
		
		if (x<0) { x=0; }
		if (y<0) { y=0; }

		windowPosition.x=x;
		windowPosition.y=y;

		if (isShown)
		{
			Controls.setPosition(container,x,y);
			this._updateLayout();
		}
	};
	
	this._getParent = function() { return parent; }

	// getQuadrant - return which quarter of the window the element is in
	this._getQuadrant = function() {
		var pos;
		var q;

		if (quadrantAnchor)
			pos = Controls.getPosition(quadrantAnchor)
		else
			pos = this._getPosition();

		var anchorPos = this._getAnchorOffset(3);
		if (!anchorPos) { return -1; }
		
		var tSize = Utils.getBrowserWindowSize(doc);

		var tSize = 	{
							x:Math.max(windowSize.x,windowSize.x + anchorPos.x),
							y:Math.max(windowSize.y,windowSize.y - anchorPos.y)
						}

		var scroll = Utils.scrollOffsets(doc);
		if ( (pos.y - scroll[1]) <tSize.y)
		{
			if ( (pos.x - scroll[0]) < tSize.x)
			{
				q = 0;
			} else {
				q = 1;
			}
		} else {
			if ( (pos.x - scroll[0]) < tSize.x)
			{
				q = 2;
			} else {
				q = 3;
			}
		}
		
		if (quadrantOverride)
		{
			q = quadrantOverride(this,q);
		}
		
		return q;

	};
	
	this._setQuadrantOverride = function(func)
	{
		quadrantOverride = func;
	}

	this._hide = function()	
	{
		if (!loadDelayComplete)	//wait for window to be created
		{
			loadDelayCallbacks.push(bind(function()
				{
					this._hide();
				},this)
			);
			return true;
		}

		if (isSticky || isFrozen || inDrag || inResize)
		{ 
			return false; 
		}

		if (!isShown)
		{
			if (callback) callback();
			return; 
		}

		isShown = false;
		
		try
		{
			function whenHidden()
			{
				//hide the draw areas
				if (this._hideDrawArea)
					this._hideDrawArea();					
			}

			if (typeof(Controls.Effect) === "undefined")
			{
				container.style.display="none";
				bind(whenHidden,this)();
			} else {
				new Controls.Effect.Fade(container,bind(whenHidden,this));
	        }


	    } catch (ex) { ; } //sink IE page change error
		return true;
	}
	
	this._showing = function()
	{
		return isShown;
	}
	
	this._show = function(callback)
	{
		if (!loadDelayComplete)	//wait for window to be created
		{
			loadDelayCallbacks.push(bind(function()
				{
					this._show(callback);
				},this)
			);
			return true;
		}

		if (isShown) 
		{
			if (callback) callback();
			return; 
		}

		isShown = true;

		var whenShown = bind(function ()
		{
			this._showDrawArea();
			if (callback) callback();
		},this)
		
		if (typeof(Controls.Effect) === "undefined")
		{
			container.style.display="block";
			whenShown();
		} else {
			new Controls.Effect.Appear(container,whenShown)
		}

		// give the browser a moment to calculate sizes of elements so that setSize works
		wait(function pause()
		{
			if (quadrantAnchor)
			{ self._setAnchor(quadrantAnchor); }
			else
			{ self._setPosition(windowPosition.x,windowPosition.y); }
		},1,"window-show");

	}
	
	this._destroy = function()
	{
		if (secondPhaseWatchId)
			clearWait(secondPhaseWatchId);
		secondPhaseWatchId = 0;

		if (!loadComplete)
			this._loadFailed();
	
		if (loadWaitElementArray)
		{
			Utils.map(loadWaitElementArray,function(elmt)
			{
				if (elmt.loadWatchId)
					clearWait(elmt.loadWatchId);
				Controls.clearEvents(elmt);
			});

			loadWaitElementArray = null;
		}

		if (_drawArea)
			_drawArea.destroy();
		_drawArea = null;

		if (_pointerDrawArea)
			_pointerDrawArea.destroy();
		_pointerDrawArea = null;

		//clear memory references
		Controls.clearEvents(themeCacheElement)
		themeCacheElement = null;		
		if (cacheDiv)
		{
			for (var i=0; i<cacheDiv.childNodes.length; i++)
			{
				Controls.clearEvents(cacheDiv.childNodes[i]);
			}
			Controls.clearEvents(cacheDiv);
			cacheDiv = null;
		}

		Controls.clearEvents(anchor);
		anchor = null;
		Controls.clearEvents(background);
		background = null;
		Controls.clearEvents(content);
	    content = null;
		Controls.clearEvents(body);
	    body = null;
		Controls.clearEvents(title);
		title = null;
		Controls.clearEvents(status);
		status = null;		

		quadrantAnchor	= null;
		Controls.clearEvents(pointerElement);
		pointerElement	= null;

		for (var i=0; i<4; i++)
		{
			Controls.clearEvents(resizers[i]);
			resizers[i] = null;
			Controls.clearEvents(pointers[i]);
			pointers[i] = null;
			Controls.clearEvents(repeats[i]);
			repeats[i] = null;
			Controls.clearEvents(corners[i]);
			corners[i] = null;
		}

		if (scrollLayer)
		{
			scrollLayer.destroy();
			scrollLayer = null;
		}

		quadrantOverride = null;
		dragCallbacks = null;
		resizeCallbacks = null;
		mouseCallbacks = null;
		loadDelayCallbacks = null;

		if (container)
		{
			Controls.clearEvents(container);	

			if (container.parentNode)
				container.parentNode.removeChild(container);

			container = null;
		}

		doc = null;
		this._cleanup();
	}

	// _buildWindow() - build the structure of the window
	this._buildWindow = function()
	{
		var profilerId = profiler.start("windowBase.buildWindow");

		container = Controls.createDiv(doc,null,id,"psWindow");

		background = Controls.createChildDiv(container,id,"bg","bgdiv");
	    content = Controls.createChildDiv(background,id,"content","windowContent");

		for (var i=0; i<4; i++)
		{
			repeats[i] = Controls.createChildDiv(background,id,"repeat-"+r[i],"repeat"+r[i]);
		}

		for (var i=0; i<4; i++)
		{
			corners[i] = Controls.createChildDiv(background,id,"corner-"+c[i],"corner"+c[i]);
		}

		for (var i=0; i<4; i++)
		{
			pointers[i] = Controls.createChildDiv(background,id,"pointer-"+c[i],"pointer"+c[i]);
		}

		bodyClass = "windowBody" + (Controls.detectIE() ? " ie" : " ff");

	    body = Controls.createChildDiv(container,id,"body",bodyClass);

		for (var i=0; i<4; i++)
		{
			resizers[i] = Controls.createChildDiv(container,id,"resize-"+c[i],"resize"+c[i]);
		}

		title  = Controls.createChildDiv(repeats[0],id,"title","windowTitle");
		status  = Controls.createChildDiv(repeats[3],id,"status","windowStatus");
		repeats[1].innerHTML = "&nbsp;";

		Controls.setPosition(container,-1000,-1000); 
		//container.onselectstart = function() { return false; }
		Controls.addHandler(container,'selectstart',function() { return false});

		profiler.stop(profilerId);
	};

	//_setTheme(theme) - set the CSS rules related to the current theme
	this._setTheme = function(theme)
	{
		this._insertStyle(theme);
	};
	
	//_updateLayout() - when the window is resized/moved, the quadrant of the window may change
	//					update the layout of the bubble accordingly

	this._updateLayout = function()
	{
		if (inResize || !isShown) { return; }

		if (!loadDelayComplete) //wait for window to be created
		{
			loadDelayCallbacks.push(bind(function() 
				{
					this._updateLayout();
				},this));
			return true;
		}

		var q = this._getQuadrant();

		if ((q === _lastQuadrant) || (!isShown))
		{
			this._moveDrawArea();
			return; // hasn't changed, no need to update
		}
		
		_lastQuadrant = q;

		var invQ = 3-q;

		var pStyle = { display:"block" };		
		var rStyle = { display:"block" };

		pointerElement = null;		
		_pointerDrawArea.hide();

		for (var i=0; i<4; i++)
		{
			if (!quadrantAnchor)
			{
				Controls.setStyle (pointers[i], noStyle );
				if (theme.isResizable() && canResize) 
				{
					Controls.setStyle (resizers[i], rStyle);
				} else {
					Controls.setStyle (resizers[i], noStyle);
				}
			} else if (i === q) {
				Controls.setStyle (resizers[i], noStyle);	
				Controls.setStyle (pointers[i], pStyle );
				pointerElement = pointers[i];
				_pointerDrawArea.setElement(pointerElement);
				_pointerDrawArea.setMode("drag",inDrag);
				if (isShown)
					_pointerDrawArea.show();
			} else if (i === invQ) {
				Controls.setStyle (pointers[i], noStyle);
				if (theme.isResizable() && canResize) 
				{
					Controls.setStyle (resizers[i], rStyle);
				} else {
					Controls.setStyle (resizers[i], noStyle);
				}
			} else {
				Controls.setStyle (pointers[i], noStyle);
				Controls.setStyle (resizers[i], noStyle);
			}
		}
		
		this._moveDrawArea();
	};
	
	//_insertStyle - insert the style sheet for this window + theme into the head of the document
	this._insertStyle = function(theme)
	{
		var themeElem = theme.create(page,id);
		if (themeElem)
		{
			this._loadWait(1,themeElem);
			return theme.insert(themeElem,page);
		}

		return false;
	}

	this._enableScrollbars = function(enable)
	{
		if (!loadDelayComplete) //wait for window to be created
		{
			loadDelayCallbacks.push(bind(function() 
				{
					this._enableScrollbars(enable);
				},this));
			return;
		}

		if (theme.isResizable())	
			scrollLayer=new Scrollbar(page,body,theme,id+"-body","scroll",container);
		else
			scrollLayer=new Scrollbar(page,body,theme,id+"-body","scroll");
	};
	
	this._enableDrag = function (enable) 
	{
		canDrag = enable;
		if (enable)
			container.style.cursor="pointer";
	}

	this._enableResize = function (enable) 
	{
		canResize = enable;
		this._updateLayout();
	}

	this._setMode = function(mode,fModeValue)
	{
		if (!loadDelayComplete) //wait for window to be created
		{
			loadDelayCallbacks.push(bind(function() 
				{
					this._setMode(mode,fModeValue);
				},this));
			return true;
		} 

		_drawArea.setMode(mode,fModeValue);
		if (pointerElement)
			_pointerDrawArea.setMode(mode,fModeValue);		
	}

	this._showDrawArea = function(fShow)
	{
		_drawArea.show(fShow);
		if (pointerElement)
			_pointerDrawArea.show(fShow);
	}
	
	this._moveDrawArea = function()
	{
		_drawArea.move();
		if (pointerElement)
			_pointerDrawArea.move();
	}

	this._updateDrawArea= function()
	{
		_drawArea.update();
		if (pointerElement)
			_pointerDrawArea.update();
	}

	this._hideDrawArea = function()
	{
		_drawArea.hide();
		_pointerDrawArea.hide();		
	}

	this._setInResize = function(fResize)
	{
		inResize = fResize;

		_drawArea.setMode("mode",fResize);
		if (pointerElement)
			_pointerDrawArea.setMode("drag",fResize);

		return inResize;
	}

	this._setInDrag = function(fDrag)
	{
		if (!loadDelayComplete) //wait for window to be created
		{
			loadDelayCallbacks.push(bind(function() 
				{
					this._setInDrag(fDrag);
				},this));
			return;
		}

		inDrag = fDrag;

		_drawArea.setMode("drag",fDrag);
		if (pointerElement)
			_pointerDrawArea.setMode("drag",fDrag);

		return inDrag;
	}
	
	this._updateScrollbars = function()
	{
		if (!loadDelayComplete) //wait for window to be created
		{
			loadDelayCallbacks.push(bind(function() 
				{
					this._updateScrollbars();
				},this));
			return;
		}
		
		if (scrollLayer)
			scrollLayer.update(true);
	};

	/***
	 * Window Event Handlers
	 **/ 	
	this._windowEvents = function()
	{
	
		/**
		 * onDrag(event) - event handler for dragging a window
		 **/
		function onDrag(e)
		{
			if (!canDrag) { return; }
			if (isFrozen) { return; }
			var event = page.event(e); if (!event) {return;}
			var evtOrigPos = Utils.eventToBodyCoordinates(event);
			var target = event.target;
			var offsets = {x:0,y:0};
			var movedEnough = false;
			var left, top;
			var dragDoc = doc;
			
			function doDrag(e)
			{
				var event = page.event(e); if (!event) {return;}
				var evtPos = Utils.eventToBodyCoordinates(event);

				var movedAmt = Math.sqrt(
						Math.pow(evtPos.x - evtOrigPos.x,2)+
						Math.pow(evtPos.y - evtOrigPos.y,2));
				if (movedAmt > 10)
					movedEnough = true;
					
				if (!movedEnough) { return; }
	
				if (!inDrag)
				{
					self._setInDrag(true);
			
					if (dragCallbacks.start)
						{ dragCallbacks.start(e,self,quadrantAnchor,self._anchorPosition());}
				}
	
				left=Math.max(evtPos.x+offsets.x,0);
				top=Math.max(evtPos.y+offsets.y,0);

				self._setPosition(left,top);

				//get the current anchor position
				var anchorPos = self._anchorPosition();
				var offscreen = false;
				if (quadrantAnchor) //make sure we're not dragging the anchor off the screen
				{
					// if the anchor is offscreen (in either axis),
					// change the amount we'd move the window
					if (anchorPos.x < 0)
					{
						left = left - anchorPos.x;
						offscreen = true;
					}

					if (anchorPos.y < 0)
					{
						top = top - anchorPos.y;
						offscreen = true;
					}

					if (offscreen)
					{
						self._setPosition(left,top);
						anchorPos = self._anchorPosition();
					}
				}

				if (dragCallbacks.drag)
				{
						dragCallbacks.drag(e,self,quadrantAnchor,anchorPos);
				}				
				
				Controls.eventDone(event);
				return false;
			}

			function sink(e) { return false; }
			
			function endDrag(e)
			{
				if (!dragDoc)
					return ;
				Controls.removeHandler(dragDoc,"mousemove",doDrag);			
				Controls.removeHandler(dragDoc,"mouseup",endDrag);
				Controls.removeHandler(dragDoc,"selectstart",sink);

				if (Controls.detectIE())
				{
					Controls.removeHandler(dragDoc.body,"mouseleave",mouseOut);
				} else {
					Controls.removeHandler(dragDoc,"mouseout",mouseOut);
				}

				dragDoc = null;
				
				if (movedEnough)
				{
					if (self._setInDrag) //make sure we haven't been destroyed
					{
						self._setInDrag(false);
						if (dragCallbacks.end)
							{dragCallbacks.end(e,self,quadrantAnchor,self._anchorPosition());}
					}
				}
					
				Controls.eventDone(event);
				return false;
			}	

			mouseOut = bind(function(event)
			{
				var event = page.event(event); if (!event) {return;}
				var relTarg = (event.relatedTarget) ? event.relatedTarget : event.toElement;
				if (!relTarg)
					return endDrag.call(this,event);			
			},this);	

			var pos = self._getPosition();
			offsets.x = pos.x-evtOrigPos.x;
			offsets.y = pos.y-evtOrigPos.y;

			if (!inDrag)
			{
				Controls.addHandler(dragDoc,"mousemove",doDrag);			
				Controls.addHandler(dragDoc,"mouseup",endDrag);
				Controls.addHandler(dragDoc,"selectstart",sink);

				if (Controls.detectIE())
				{
					Controls.addHandler(dragDoc.body,"mouseleave",mouseOut);
				} else {
					Controls.addHandler(dragDoc,"mouseout",mouseOut);
				}
			}

			Controls.eventDone(event);
			return false;
		}; //onDrag

		/**
		 * onResize(event) - event handler for resizing a window
		 */
		function onResize(e) {
			if (!canResize) { return; }
			// BUG #2227 - cannot resize mark while in comment edit mode
			// allow resizing when a window is frozen
			//if (isFrozen) { return; }
			if (inResize) { return; }
			var event = page.event(e); if (!event) {return;}
			var target = event.target;
			var offsets = {x:0,y:0};
			var origPos;
			var origEvtPos;
			var origSize;
			var q;

			// IE has an "interesting" rounding error during the resize process
			//	and one of two fixes need to be used depending on whether the
			//	starting window size is even or odd.  ieFlub is set to 1 or 0 
			//	for each dimension to indicate which method to use
			var ieFlub	= {x:0,y:0};

			function doResize(e)
			{

				var event = page.event(e); if (!event) {return;}
				var evtPos = Utils.eventToBodyCoordinates(event);
				var target = event.target;
				var newPos = { x:0, y:0 }
					newPos.x = origPos.x;
					newPos.y = origPos.y;
				var delta = { x:0, y:0 }
				var sizeX, sizeY;
				var newX, newY;
				var deltaX, deltaY;
				
				switch (q)
				{
					case 0://resize top left to lower right
						sizeX = origSize.x + (evtPos.x-origEvtPos.x);
						sizeY = origSize.y + (evtPos.y-origEvtPos.y);

						newX = Math.max(Math.min(sizeX,maxSize.x),minSize.x);
						newY = Math.max(Math.min(sizeY,maxSize.y),minSize.y);

						self._setSize(newX,newY);

						deltaX = newX - origSize.x;
						deltaY = newY - origSize.y;

						delta.x = deltaX;
						delta.y = deltaY;
						break;
					case 1://resize top right to lower left
						sizeX = origSize.x + (origEvtPos.x-evtPos.x);
						sizeY = origSize.y + (evtPos.y-origEvtPos.y);

						newX = Math.max(Math.min(sizeX,maxSize.x),minSize.x);
						newY = Math.max(Math.min(sizeY,maxSize.y),minSize.y);

						self._setSize(newX,newY);

						//resizing in this direction means repositioning the window's horizontal position
						deltaX = newX - origSize.x;
						deltaY = newY - origSize.y;

						delta.x = deltaX;
						delta.y = deltaY;
						
						if (Controls.detectIE()) //compensate for IE rounding error
						{
							if (!ieFlub.x)	// starting width was even
								deltaX = Math.floor(deltaX/2)*2; 	//fix one, round down odd numbers
							else			// starting width was odd
								deltaX = Math.floor((deltaX+1)/2)*2; //fix two, round up odd numbers
						}

						newPos.x = origPos.x - deltaX;
						self._setPosition(newPos.x,newPos.y);
						break;
					case 2: //resize lower left to top right
						sizeX = origSize.x + (evtPos.x-origEvtPos.x);
						sizeY = origSize.y - (evtPos.y-origEvtPos.y);
						newX = Math.max(Math.min(sizeX,maxSize.x),minSize.x);
						newY = Math.max(Math.min(sizeY,maxSize.y),minSize.y);

						self._setSize(newX,newY);

						//resizing in this direction means repositioning the window's vertical position
						deltaX = newX - origSize.x;
						deltaY = newY - origSize.y;

						delta.x = deltaX;
						delta.y = deltaY;

						if (Controls.detectIE()) //compensate for IE rounding error
						{
							if (!ieFlub.y)	// starting width was even
								deltaY = Math.floor(deltaY/2)*2;	//fix one, round down odd numbers
							else			// starting width was odd
								deltaY = Math.floor((deltaY+1)/2)*2;//fix two, round up odd numbers
						}
						
						newPos.y = origPos.y-deltaY;
						self._setPosition(newPos.x,newPos.y);
						break;
					case 3:
						sizeX = origSize.x - (evtPos.x-origEvtPos.x);
						sizeY = origSize.y - (evtPos.y-origEvtPos.y);

						newX = Math.max(Math.min(sizeX,maxSize.x),minSize.x);
						newY = Math.max(Math.min(sizeY,maxSize.y),minSize.y);

						self._setSize(newX,newY);

						//resizing in this direction means repositioning the window's horizontal & vertical positions
						deltaX = newX - origSize.x;
						deltaY = newY - origSize.y;

						delta.x = deltaX;
						delta.y = deltaY;
						
						if (Controls.detectIE()) //compensate for IE rounding error
						{
							if (!ieFlub.x)	// starting width was even
								deltaX = Math.floor(deltaX/2)*2; 	//fix one, round down odd numbers
							else			// starting width was odd
								deltaX = Math.floor((deltaX+1)/2)*2; //fix two, round up odd numbers

							if (!ieFlub.y)	// starting width was even
								deltaY = Math.floor(deltaY/2)*2;	//fix one, round down odd numbers
							else			// starting width was odd
								deltaY = Math.floor((deltaY+1)/2)*2;//fix two, round up odd numbers
						}
						
						newPos.x = origPos.x - deltaX;
						newPos.y = origPos.y - deltaY;

						self._setPosition(newPos.x,newPos.y);
						break;
				}

				if (resizeCallbacks.resize)
					{ resizeCallbacks.resize(e,delta); }

				Controls.eventDone(event);
				return true;
			}
			
			function sink(e) { return false; }

			function endResize(e)
			{
				var event = page.event(e); if (!event) {return;}
				var evtPos = Utils.eventToBodyCoordinates(event);
				var target = event.target;
	
				Controls.removeHandler(doc,"mousemove",doResize);			
				Controls.removeHandler(doc,"mouseup",endResize);
				Controls.removeHandler(doc,"selectstart",sink);
			
				self._setInResize(false);

				if (resizeCallbacks.end)
					{ resizeCallbacks.end(e); }

				Controls.eventDone(event);
				return true;
			}
						
			try 
				{ q = target.quadrant; 		}
			catch(ex) 
				{ q = self._getQuadrant();	}

			origSize = self._getSize();
			origEvtPos = Utils.eventToBodyCoordinates(event);
			origPos = self._getPosition();
			offsets.x = origPos.x-origEvtPos.x;
			offsets.y = origPos.y-origEvtPos.y;

			if (Controls.detectIE()) //compensate for IE rounding error
			{
				// determine if starting height and width are odd or even
				ieFlub.x = origSize.x - Math.floor(origSize.x/2)*2;
				ieFlub.y = origSize.y - Math.floor(origSize.y/2)*2;
			}

			Controls.addHandler(doc,"mousemove",doResize);			
			Controls.addHandler(doc,"mouseup",endResize);
			Controls.addHandler(doc,"selectstart",sink);

			self._setInResize(true);
						
			if (resizeCallbacks.start)
				{ resizeCallbacks.start(e); }

			Controls.eventDone(event);
		}; //onResize

		function onMouseOver(e)
		{
			var event = page.event(e); if (!event) {return;}

			var fromElem;
			if (event.fromElement)
			{
				fromElem = event.fromElement;
			} else {
				fromElem = event.relatedTarget;
			}
				
			if ((!inResize) && (!inDrag))
				if (!Utils.isChildOf(fromElem,container))	
				{
					if (mouseCallbacks.over)
						{ mouseCallbacks.over(e,self); }

					Controls.eventDone(event);
				}
		};

		function onMouseOut(e)
		{
			var event = page.event(e); if (!event) {return;}

			var toElem;

			if (event.toElement)
			{
				toElem = event.toElement;
			} else {
				toElem = event.relatedTarget;
			}

			if ((!inResize) && (!inDrag))
				if (!Utils.isChildOf(toElem,container))	
				{
					if (mouseCallbacks.out)
						{ mouseCallbacks.out(e,self); }

					Controls.eventDone(event);
				}
		};
		
		//container.onmouseover = onMouseOver;
		Controls.addHandler(container,'mouseover',onMouseOver);
		
		//container.onmouseout  = onMouseOut;
		Controls.addHandler(container,'mouseout',onMouseOut);

		//content.onmousedown = onDrag;
		Controls.addHandler(content,'mousedown',onDrag);
		
		//body.onmousedown = onDrag;
		Controls.addHandler(body,'mousedown',onDrag);
		
		for (var i=0; i<4; i++)
		{
			//pointers[i].onmousedown = onDrag;
			Controls.addHandler(pointers[i],'mousedown',onDrag);
			
			//pointers[i].onmouseover = onMouseOver;
			Controls.addHandler(pointers[i],'mouseover',onMouseOver);
			
			//pointers[i].onmouseout  = onMouseOut;
			Controls.addHandler(pointers[i],'mouseout',onMouseOut);

			//corners[i].onmousedown = onDrag;
			Controls.addHandler(corners[i],'mousedown',onDrag);
			
			//repeats[i].onmousedown =  onDrag;
			Controls.addHandler(repeats[i],'mousedown',onDrag);
			
			//resizers[i].onmousedown = onResize;
			Controls.addHandler(resizers[i],'mousedown',onResize);
			
			resizers[i].quadrant = 3-i;
		}
	};//_windowEvents

	/* function zIndexOnTop(elem) - set the elem's zIndex to be above the windowBase */
	this._zIndexOnTop = function(elem)
	{
		//force above the mark window with z-index
		try
		{
			elem.style.zIndex='6440999999';
		} catch (ex) { //fails in IE sometimes, get max zindex from window
			var z=Controls.getZIndex(container);
			try
			{
				elem.style.zIndex=z+1;
			} catch (ex) { //can't be bigger than max, place lower in DOM to be above
				elem.style.zIndex=z;
			}
		}
	}

	this._init();	
};//WindowBase

/***
 * public accessors for WindowBase()
 ***/
Utils.implement(WindowBase,ClassBase);

// WindowBase.setX - set the innerHTML of the window part
WindowBase.prototype.setTitle = function(title){ return this._setTitle(title); }
WindowBase.prototype.setContent = function(content){ return this._setContent(content); }
WindowBase.prototype.setStatusText = function(message){ return this._setStatusText(message); }

// WindowBase.getXElement() - returns the DHTML element of the window part
WindowBase.prototype.getContentElement = function() { return this._getContentElement(); }
WindowBase.prototype.getStatusElement = function() { return this._getStatusElement(); }
WindowBase.prototype.getTitleElement = function() { return this._getTitleElement(); }
WindowBase.prototype.getContainerElement = function() { return this._getContainerElement(); }
// Window states
WindowBase.prototype.show = function(callback)		{ return this._show(callback); 		}
WindowBase.prototype.hide = function()		{ return this._hide(); 		}
WindowBase.prototype.destroy = function()	{ return this._destroy(); 	}
WindowBase.prototype.showing= function() 	{ return this._showing();	}
WindowBase.prototype.stick = function(callback) 	{ return this._stick(callback); 	};
WindowBase.prototype.unstick = function() 	{ return this._unstick();	};
WindowBase.prototype.stuck = function() 	{ return this._stuck();		};

WindowBase.prototype.freeze = function() 	{ return this._freeze();	};
WindowBase.prototype.thaw = function() 		{ return this._thaw();		};
WindowBase.prototype.frozen = function() 	{ return this._frozen();	};

// callbacks for mouseover & mouseout events
WindowBase.prototype.setMouseCallbacks  = function(onMouseOver,onMouseOut) { return this._setMouseCallbacks(onMouseOver,onMouseOut); }

// WindowBase.enableDrag(enable) - set dragging function on or off
WindowBase.prototype.enableDrag = function (enable) 	{ return this._enableDrag(enable); }
// drag callbacks 
WindowBase.prototype.setDragCallbacks = function(onDragStart, onDrag, onDragEnd){ this._setDragCallbacks(onDragStart,onDrag,onDragEnd); }
// manually place the window in drag mode
WindowBase.prototype.setInDrag = function(fDrag) { return this._setInDrag(fDrag); }
WindowBase.prototype.isInDrag = function()  { return this._isInDrag();  };

// WindowBase.enableResize(enable) - set resize functions on or off
WindowBase.prototype.enableResize = function (enable)	{ return this._enableResize(enable); }
// resize callback functions
WindowBase.prototype.setResizeCallbacks = function(onResizeStart, onResize, onResizeEnd, onSize) { this._setResizeCallbacks(onResizeStart,onResize,onResizeEnd,onSize); }
WindowBase.prototype.isInResize = function()  { return this._isInResize();  };

// WindowBase.enableScrollbars(enable) - set scrolbar function on or off
WindowBase.prototype.enableScrollbars = function(enable){ return this._enableScrollbars(enable); }
											
// WindowBase.setPosition(x,y) - move the window to x,y (in pixels)
WindowBase.prototype.setPosition = function(x,y){ this._setPosition(x,y); }

// WindowBase.getPosition(x,y) - get the window position
WindowBase.prototype.getPosition = function(){ return this._getPosition(); }

// WindowBase.setSize(x,y) - set the window size (x,y in pixels)
WindowBase.prototype.setSize = function(x,y){ this._setSize(x,y); }
WindowBase.prototype.setMinSize = function(x,y){ this._setMinSize(x,y); }
WindowBase.prototype.setMaxSize = function(x,y){ this._setMaxSize(x,y); }

// WindowBase.getSize() - returns the window size
WindowBase.prototype.getSize = function(){ return this._getSize(); }

//drawAreaMode - drawArea manages to keep elements on top of other elements, 
//				but that depends on certain modes that must be reported to the drawAreas.
WindowBase.prototype.setMode = function(mode,bModeValue) { return this._setMode(mode,bModeValue); }

/* anchor functions */
// WindowBase.setAnchor(elem) - set the element that the pointer points to (decides the quadrant),
WindowBase.prototype.setAnchor	= function(elem) { return this._setAnchor(elem); }
WindowBase.prototype.getAnchor	= function()	 { return this._getAnchor();	 }

// WindowBase.anchorPosition() - when the window is dragged, return the position that the anchor should be
WindowBase.prototype.anchorPosition = function() {return this._anchorPosition(); }
WindowBase.prototype.anchorMoveAdjust = function(position) { return this._anchorMoveAdjust(position); }

WindowBase.prototype.setQuadrantOverride = function(func) { return this._setQuadrantOverride(func); }
/* misc functions */
WindowBase.prototype.setClass	= function(className) { return this._setClass(className); }
WindowBase.prototype.setTitleClass	= function(className) { return this._setTitleClass(className); }

/* caching functions */
WindowBase.prototype.cachePrefix = function()    { return this._cachePrefix();     }
WindowBase.prototype.themeCachePrefix = function()    { return this._themeCachePrefix();     }
WindowBase.prototype.themeCacheDivId = function()    { return this._themeCacheDivId();  }

// WindowBase.getThemeButtonSize() - returns the size of a window component in the current theme caching div
WindowBase.prototype.getThemeButtonSize = function(button)		{ return this._getComponentSize(button); }
WindowBase.prototype.getComponentSize = function(item){ return this._getComponentSize(item); }

/* the load delay mechanism */

// loadDelayedCallback - add a function to be invoke once the window has finished loading
WindowBase.prototype.loadDelayedCallback = function(callback) { return this._loadDelayedCallback(callback); }

// loadErrorCallback - add a function to be invoke if the window does not load
WindowBase.prototype.loadErrorCallback = function(callback) { return this._loadErrorCallback(callback); }

// loadWait(1 or -1) - increment or decrement a loading lock.  Once the lock reaches 0, the
// window is considered loaded.  Used to stop the window from displaying before all components
// are loaded. 
WindowBase.prototype.loadWait = function(delta,elmt) { this._loadWait(delta,elmt); }

/* untested functions */

// WindowBase.setTheme(theme) - change the theme of the window
WindowBase.prototype.setTheme = function(theme){ return this._setTheme(theme); }

// WindowBase.getParent() - get the window parent
WindowBase.prototype.getParent = function(){ return this._getParent(); }
WindowBase.prototype.zIndexOnTop = function(elem) { return this._zIndexOnTop(elem); }


											


// /var/www/trailfire.com-release/content/client/lib/window/dialog-window.js 

/*
 * DHTML Dialog Window Library 
 * Copyright 2006 Pipestone
 */
 

function DialogWindow(page,id,theme,title,message,buttons)
{
	ClassBase.call(this,"DialogWindow");

	var win = new WindowBase(page,id,theme);
	var content = win.getContentElement();
	var body = Controls.createChildDiv(content,id,"dialog-body","psDialog-Body");
	var toolbar = Controls.createChildDiv(content,id,"dialog-buttons","psDialog-Buttons"); 	
	win.setClass("psDialog");
	win.setTitleClass("dialogTitle");
	win.setTitle(title);

	win.enableDrag(false);
	win.enableResize(false);

	win.setSize(180,160);

	body.innerHTML=message;
	
	for (i=0; i<buttons.length; i++)
	{
		buttons[i].setParent(toolbar);
	}
	toolbar.style.textAlign="center";
	toolbar.style.margin = "10px";
	
	this._getWindow=function() {
		return win;
	}

	this._destroy = function()
	{
		for (i=0; i<buttons.length; i++)
		{
			buttons[i].destroy();
			delete buttons[i];
			buttons[i] = null;
		}
		button = null;
		body = null;
		content = null;
		toolbar = null;
		win.destroy();
		delete win;	
		this._cleanup();
	}
	
	this._hide = function()
	{
		win.hide();
	}
	
	this._show = function(x,y)
	{
		win.setPosition(x,y);
		win.show();
		var size = win.getSize();
		Controls.scrollToView(page.getDocument(),x,y,size.x,size.y);
	}
	
	this._showAnchored = function(anchor)
	{
		win.setAnchor(anchor);
		win.show();
	}
	
	this._setSize = function(x,y) { win.setSize(x,y); }
	
	this._getBodyElement = function() { return body; }

	this._getDOMElement = function()
	{
		return win.getContainerElement();
	}
	this._getToolbarElement = function()
	{
		return toolbar;
	}
	
	// Increases dialog height so that removes scrollbar.
	this._optimizeHeight = function()
	{
		Log.debug("_optimizeHeight");
	
		var bodySize = Controls.getSize(body); // message content div
		var dlgWindowSize = Controls.getSize(win); // dialog window

		if (bodySize[0] > dlgWindowSize[0]) 
		{
			var newHeight = bodySize[0]+10
			Log.debug("_optimizeHeight: resetting height to " + newHeight);
		
			this._setSize(dlgWindowSize[1],newHeight); // x, y 			
		}	
	} 
}


Utils.implement(DialogWindow,ClassBase);

DialogWindow.prototype.show	= 	function(x,y) { this._show(x,y); }
DialogWindow.prototype.showAnchored	= 	function(anchor) { this._showAnchored(anchor); }
DialogWindow.prototype.setSize = function(x,y) { this._setSize(x,y); }
DialogWindow.prototype.hide = function()	{ this._hide(); }
DialogWindow.prototype.destroy = function() { this._destroy(); }
DialogWindow.prototype.getWindow = function() { return this._getWindow(); }
DialogWindow.prototype.getBodyElement = function() { return this._getBodyElement(); }
DialogWindow.prototype.getDOMElement = function() { return this._getDOMElement(); }
DialogWindow.prototype.getToolbarElement = function() { return this._getToolbarElement(); }
DialogWindow.prototype.optimizeHeight=function() { this._optimizeHeight(); }


// /var/www/trailfire.com-release/content/client/model/user-model.js 

/* User Model 
	
	loginState
	username
	trails
	
	autoLogin
	login
	logout
	setPollEnabled
	pollNow	
	lookupTrails
	
	createMark(url,trail,title,content,offset,mimeType,charSet)
		add to traillist
		mark proxy call
		
	getTrailById	
	getMarks(page)

	listen to trail changes	
*/

function UserModel(browser)
{
	ModelBase.call(this,"UserModel");

	eval(Utils.declare("UserModel",{
		token:null,
		trails:null,
		createMarkInProgress:false 	// indicates we are in process of creating a new mark.
	}));

	var _marksCache = Utils.newArray(); //cache of marks by id
		
	var userName = null;
	var theme = null;

	var isLoggedIn = false;
	var isLoggingIn = false;
	var isLoggingOut = false;
	var isLoginAttempted = false;

	var isAdmin	= false;
	var isBetaTester = false;
	var isPowerUser = false;

	var loadQueue = new Array();
	
	var trailsLoaded = false;
	var trailsToBeLoaded = false;
	var _preloadMarks = false;
	var _preloadMarksComplete = false;

	var _pollEnabled = false;

	var _lookupTrailInProgress = 0;	
	var markDataSet = false;
	
	var tempIdMap = Utils.newArray();
	
	var UPDATE_INTERVAL = 60000; // how often to update our interest from server
    var _pollChangesId = null;
	var _timestamp = -1; 
	var _pollActivitiesTimestamp = -1;
	
	var _eventCount = -1;
	
	var _hintAction = 'profile';
	var _hintMarkup = '';

	this._getId = function()
	{
		if (isLoggedIn)
			return userName
		else
			return 'anonymous'
	}

	// Handle trail events 
	this.trailDispatch=bind(function(event) 
	{
		switch (event.name) { 
		    case "destroy":
		    	this._removeTrail(event.trail,true, false);
	    break;
	    case "trail-active":
	    	this.fire(event);  // pass it on
		    break;
		case "mark-added":
				this._addMarkCache(event.mark);
			break;
		case "mark-removed:":
				this._removeMarkCache(event.mark);
			break;
		case "listener-removed":
			// if we are the only listener remaining for this trail, then remove trail from us 
			// Garbage collecting...
			if (event.model.getListenerCount() == 1 &&
				event.model.containsListener(this.trailDispatch))
			{
				// Don't remove if trail if its editable
				if (event.model.getEditable && event.model.getEditable())
					break;
					
				Log.debug("UserModel is last listener...destroying trail")
				wait(bind(function(){ this._removeTrail.call(this, event.model, false, true);}, this),20000);
			}
		}
	}, this); 

	var markListener = bind(function(event)
	{
		switch (event.name) {
			case "destroy":
				this._removeMarkCache(event.mark);
			break;
		}				
	}, this);
	
	var browserListener = bind(function(event)
	{
		switch (event.name) {
			case "invoke":
				if (event.action == _hintAction)
					this._getNextHint();
			break;
			case "quit":
				browser.ignore(browserListener);
			break;
		}		
	}, this);
	browser.listen(browserListener);

	this._load = function(profile,auto)
	{
		if (profile)
		{
			userName = profile["screenname"];
	
			if (theme)
				theme.setId(profile["theme"]);
			else
				theme = new Theme(profile["theme"],browser.getImageServer(),browser.getVersion());
	
			if (profile["admin"] != undefined)
				isAdmin = profile["admin"];
			
			if (profile["powerUser"] != undefined)
				isPowerUser = profile["powerUser"];
				
			if (profile["betaTester"] != undefined)			
				isBetaTester = profile["powerUser"];

			isLoggedIn = true;
			
			this._fire({name:"login", auto:auto});
		} else {
			isLoginAttempted = true;
			isLoggingIn = false;
			isLoggedIn = false;
			userName = null;
		}
	}
	
	this._login = function(username,password)
	{
		if (isLoggingIn)	{ return; }
		
		function success(response)
		{
			isLoggingIn = false;
			
			this._load(response["profile"],false);
			this._getNextHint();
		}
		
		function failure(response)
		{
			called = true;
			isLoggingIn = false;
			isLoggedIn = false;
			var reasonMsg = Utils.errorToString(response);

			this._fire({name:"login-failed",reason:reasonMsg});
		}

		isLoggingIn = true;
		isLoginAttempted = true;
		browser.login(username,password,true,bind(success,this),bind(failure,this));
		this._fire({name:"login-progress"});
	}

	this._autoLogin = function(progress)
	{	
		if (isLoggingIn)	{ return; }
		var wasLoggedIn = isLoggedIn;
		var wasUsername = userName;

		function s(response)
		{
			isLoggingIn = false;

			if (wasLoggedIn && userName == response["profile"]["screenname"]) 
			{  
				//still logged in, check for theme change
				if (theme && (response["profile"]["theme"] != theme.getId()))
				{
					theme.setId(response["profile"]["theme"]);
				}
			} else {
				this._load(response["profile"],true);
			}
		}
		
		function f(response)
		{
			isLoggingIn = false;
			isLoggedIn = false;
			

			if ( !response.rpc ) 
				this._fire({name:"login-failed", reason:Utils.errorToString(response)})
			else
				this._fire({name:"login-failed", auto:true})
				
		}
		
		isLoggingIn = true;
		isLoginAttempted = true;

		browser.autoLogin(bind(s,this),bind(f,this));

		if (progress)
			this._fire({name:"login-progress"})
	}

	// trigger the loading of trails for the user from the server
	this._lookupTrails = function()
	{
		if (trailsLoaded) { return; }
		if (isLoggingIn)  { trailsToBeLoaded = true; return; }
		if (!isLoggedIn)  { return; } //can't load trails if you aren't signed in
		trailsLoaded = true;

		function success(response)
		{
			_timestamp = response["timestamp"];
			this._loadTrailData(response["interests"], true);
		}
		
		function failure(response)
		{
			// browser model will tell us if we need to update our login state
			// no need to do it here
			trailsLoaded = false;
			this._fire({name:"trails-failed", error:Utils.errorToString(response)});
		}
		
		browser.profile(bind(success,this), bind(failure,this));
	}

	this._notifyLoggedOut = function(auto)
	{
		if (!isLoggedIn)
			return;

		if ((!isLoggedIn) && (!isLoggingIn)) { return; } //already logged out
		
		ajaxRequestCleanup();
		
		isLoggingIn = false;
		userName = null;
		_pollActivitiesTimestamp = -1;
		_eventCount = -1;
	    this._fire({name:"EventCountChanged",count:_eventCount});

		if (isLoggedIn)
			this._clear();

		isLoggedIn = false;

		this._cancelPolling();
		this._fire({name:'hint', markup:'', action:'login'});
		this._fire({name:"logout", auto:auto});		
	}

	this._logout = function()
	{
		function success(response)
		{
			isLoggingOut = false;
			this._notifyLoggedOut(false);
		}

		function failure(response)
		{
			//???
			isLoggingOut = false;
		}

		isLoggingOut = true;
		this._cancelPolling();		
		browser.logout(bind(success,this),bind(failure,this));
	}

	this._setPollEnabled = function(fEnabled)
	{
		if (fEnabled == _pollEnabled) { return; }
		_pollEnabled = fEnabled;
		
		if (!_pollEnabled)
		{
			this._cancelPolling();
		}
	}

	// getMarks(page) - return any marks in memory that match the page
	//				 - trigger a server side lookup of any more marks that match
	this._getMarks = function(page)
	{
		var marks = new Array();
		
 		if (_trails ) 
 		{
			for (var i in _trails)
			{
				var trailMarks = _trails[i].getMarks();
				for (var j = 0; j<trailMarks.length; j++)
				{
					if (trailMarks[j].isOnPage(page))
						marks.push(trailMarks[j]);
				}
			}
		}
		
		// trigger a server side lookup of any more marks that match
		this._lookupMarks(page);
		
		return marks;
	}	

	// createMark - creates a new mark model, returns null to indicate failure in state (already waiting to create a mark)
	this._createMark = function(page,callback,url,trailLabel,subject,content,offset,mimeType,charSet)
	{
		//check that the user is logged in
		if (!isLoggedIn){
			page.alert("Log In","You must be signed in to create marks.  Please sign in using the form in the Trailfire sidebar.",null);
		 	toggleSidebar('viewPipestoneSidebar', true); 
		 	callback(null);
		 	return;
		}

		if (!browser.getOption("pageDecorations.showBubbles"))
		{
			browser.confirm("Show Marks Disabled","Cannot create mark while the 'Show trail marks' option is off.  <br><br>Would you like to enable 'Show Trail Marks' now?",
			bind(function(fEnable)
			{
				if (fEnable)
				{
					browser.setOption("pageDecorations.showBubbles",true);
					this._createMark(page,callback,url,trailLabel,subject,content,offset,mimeType,charSet);
				} else { callback(null); }
			},this),null);
			return;
		}
		
		//block the load function from looking up new bubbles until we have completed this bubble creation
		this._blockLoad = true;

	    // Create a new bubble in memory... then add to the database asynch.
	    var newMark = new MarkModel(browser);
	    var now = new Date();
	    var markId = Math.floor(now.getTime() / 1000);
	    newMark.setTempId(markId); // use a temporary ID (use seconds)
	    newMark.setUrl(url);
	    newMark.setOwner(userName);
	    newMark.setQueryUrl(url);
		newMark.setMimeType(mimeType);
		newMark.setCharSet(charSet);
		var canvas = page.getCanvas();

		if (canvas && canvas.getDocument() && (typeof(offset) == "object")) //find the closest element, set the path & offsets
		{
			var attachTo=canvas.findClosestElem	( offset, [ "img", "a", "div", "form", "input"] );
			var path=canvas.computeElementPath(attachTo);
			var elementOffset=Controls.getPosition(attachTo);

			newMark.setElementPath(path);
			newMark.setOffsets({x:(offset.x - elementOffset.x),
								y:(offset.y - elementOffset.y)});
		} else {	//use the default window attachement
		    newMark.setElementPath("#"); 

		    if (!offset || (typeof(offset) == "object"))
		    	offset = 50;

		    newMark.setOffsets({x:0,y:Math.max(offset,50)});
		}

	    newMark.setSubject(subject);
	    newMark.setContent(content);
	    newMark.setThemeId(this.getTheme().getId());

		var tTrail = this._getTrailByLabel(trailLabel,userName)
		if (tTrail)
		{
			newMark.setTrailIndex(tTrail.getLength()+1);
			tTrail.addMark(newMark);
		} else {			
			newMark.setTrailIndex(1);
			var isPublic = !browser.getOption("trails.defaultPrivate");
			var tempTrail = this._addTrail({id:markId, label:trailLabel, owner:userName, trailLength:1, publicFlag:isPublic},false);
			tempTrail.addMark(newMark);
		}
		callback(newMark);
		
		/**
		 * Save.success(response) : a successful mark creation response
		 */
		function success(response) {
	        newMark.load(response);
 			this._unblockLoad();	//let mark loading commence
			this._fire({name:"new-mark", mark:newMark, page:page});
	    };
		    
		/**
		 * Save.failure(response) : a failed mark creation response
		 */
	    function failure(error) {
			this._unblockLoad();	//let bubble loading commence
			this._fire({name:"new-mark-failed", mark:newMark, page:page, error:error});
			newMark.destroy();
			var errorMsg = Utils.errorToString(error);
			page.alert("Server Error","Cannot mark this page in Trailfire. Error: "+errorMsg);
	    };
		
		var priv = 
		browser.annotate(
		    0,
		    newMark.getTempId(),   
		    newMark.getUrl(), 		
			newMark.getMimeType(),
		    newMark.getElementPath(), 	
		    newMark.getOffsets(), 	
		    newMark.getSubject(), 
		    newMark.getTrailOwner(), 	
		    newMark.getContent(), 
		    [ trailLabel ],
		    newMark.getCharSet(),
		    !browser.getOption("trails.defaultPrivate"), 
		    bind(success,this), bind(failure,this));	
		    
	};
	
	this._preloadMarks = function()
	{
		if (_preloadMarks) return;
		_preloadMarks = true;

		var trails = new Array();
		if (_trails)
			for (var i in _trails)
				trails.push(_trails[i]);

		if (trails.length > 0)
			this._lookupTrailMarks(trails);
	}

	/* unblocks loading of mark data */
	this._unblockLoad=function()
	{
		this._blockLoad = false;
		if (loadQueue.length>0)
		{
			Log.debug("unblock Load called - loadQueue = "+loadQueue.length);
			this._loadMarks(loadQueue.pop(), false); // todo: we don't really know what type of load this is...
		}
	}
	
	this._getTheme = function()
	{
		if (!theme)
			theme = new Theme(110,browser.getImageServer());

		return theme;
	}
	
	this._getUserName = function()
	{
		if (isLoggedIn)
			return userName
		else
			return ""
	}
	
	this._getUserPage = function()
	{
		return browser.getServer() + userName;
	}

	this._mapTempId = function(id,tempId)
	{
		tempIdMap[id]=tempId;
	}
	
	this._getTempId = function(id)
	{
		if (tempIdMap[id])
			return tempIdMap[id]
		else
			return false;
	}
	
	this._isLoggedIn = function()		{ return isLoggedIn;					}
	this._isAdmin = function() 			{ return isAdmin; 						}
	this._isPowerUser = function() 		{ return isPowerUser; 					}
	this._isBetaTester = function() 	{ return isBetaTester; 					}
	this._isLoginInProgress = function(){ return isLoggingIn || isLoggingOut;	}
	this._isLoginAttempted = function()	{ return isLoginAttempted;				}
	
	this._getTrailById = function(id)
	{
		if (_trails)
			return _trails[id]
		else
			return null;
	}

	this._getTrailByLabel = function(label,owner)
	{
		if (_trails === null)
			_trails = Utils.newArray();

		for (var i in _trails)			
		{
			if (_trails[i] && _trails[i].getLabel) 
				if (_trails[i].getLabel().toUpperCase() === label.toUpperCase())
					if (_trails[i].getOwner().toUpperCase() === owner.toUpperCase())
						return (_trails[i]);
		}
	}

	this._addTrail=function( trailParams, batch )
	{
		if (_trails === null)
			_trails = Utils.newArray();

		var id = Number(trailParams["id"]);

		Utils.assert(id > 0,function()
		{
			return "Trail params must have an id";
		});	
		
		if (id > 0)
		{
			var newTrail = new TrailModel(browser);
			newTrail.load(trailParams);
			newTrail.listen(this.trailDispatch);
	
			_trails[newTrail.getId()] = newTrail;
	
			var marks = newTrail.getMarks();
			for (var i in marks)
				this._addMarkCache(marks[i]);

			if (!batch)
				this.fire({name:"trail-added", trail:newTrail});

			return newTrail;
		}
	}
	
	this._activateNextTrail = function()
	{
		var latestInactiveTrail = null;
		for (var i in _trails)
		{
			var tTrail = _trails[i];
			if (!tTrail.isActive())
			{
				if (latestInactiveTrail)
				{
					if (tTrail.getTimestamp() > latestInactiveTrail.getTimestamp())
						latestInactiveTrail = tTrail;
				} else {
					latestInactiveTrail = tTrail;
				}
			}
		}
		if (latestInactiveTrail)
			latestInactiveTrail.setActive(true);
	}

	this._removeTrail = function(trail,alreadyDestroyed, garbageCollected) 
	{ 
		// If this trail is being garbage collected, then make sure no other views are 
		// listening to the trail... if so, ignore garbage collection.
		if (garbageCollected)
		{
			if (trail.getListenerCount() > 1)
				return;
		}

		if (_trails[trail.getId()])
		{
			var marks = trail.getMarks();
			for (var i in marks)
				this._removeMarkCache(marks[i]);

			trail.ignore(this.trailDispatch);
			delete _trails[trail.getId()];

			if (trail.isActive())
			{
				this._activateNextTrail();
			}

			if (!alreadyDestroyed)
				trail.destroy();
		}	
    };
    
    // Removes trails from memory 
	this._destroyTrails=function(trailIds)
	{
		for (var i in trailIds)
		{
			var trail = this.getTrailById(trailIds[i]);
			if (trail)
				this._removeTrail(trail,false,false);
		}
	}

	this._destroyAllTrails=function()
	{
		if (_trails)
			for (var i in _trails)
			{
				_trails[i].ignore(this.trailDispatch);
				_trails[i].destroy();
			}

		_trails = Utils.newArray();
		_marksCache = Utils.newArray();
	}
	    
	this._addMarkCache = function(mark)
	{
		if (mark)
		{
			if (_marksCache)
				_marksCache[mark.getId()]=mark;
			mark.listen(markListener);
		}
	}

	this._removeMarkCache = function(mark)
	{
		if (mark)
		{
			mark.ignore(markListener);
			if (_marksCache)
				delete _marksCache[mark.getId()];			
		}
	}

    this._addMark=function(markData)
    {
		var id = markData["id"];

		// find the bubble in either trail or page list
		var mark = this._getMarkById(id)
		
		if (!mark)	// create the bubble, listen to events
		{
	    	mark = new MarkModel(browser);
			mark.load(markData); 			
		} 
		else 
		{		// update the mark data
			mark.load(markData);
		}
    	return mark;
    }
    
	this._loadTrailData=function(trails, initialLoad, batchSize) 
	{
 		var activeCount = 0; // Keep track of how many of my trails are shown/cached
		var newTrails = new Array();
		var newTrailIds = new Array();
		if (!batchSize) { batchSize = 10; }	
			
		function addInSmallBatches(trails, offset, batchSize) 
		{
			if (!this._isLoggedIn())
				return;
				
			var limit = Math.min(offset + batchSize,trails.length);

			Log.debug("addInSmallBatches " + offset + " through "+ limit );
		    for (var i = offset; i < limit; ++i) {
								
				// Set the first 20 loaded trails to active, or any added after the 
				// initial load.
				trailLength = Number(trails[i]["trailLength"]);

	           	var active = true;
	           	if (initialLoad && trailLength > 0) {
	            	if (activeCount++ > 19) {
	            		active = false;
	            	}
	            }

	            var id = Number(trails[i]["id"]);

	            var existing = this._getTrailById(id);
	            if (!existing)
	            {
					existing = this._addTrail(trails[i], true);
					newTrails.push(existing);
					newTrailIds.push(existing.getId());
	            } else {
					existing.load(trails[i]);
	            }

	            existing.setActive(active);
	    	}
	    	
	    	// Check for termination: 
	    	if (trails.length > limit)
	    	{
    			addInSmallBatches.call(this,trails, limit, batchSize);
    			//wait(bind(function() {addInSmallBatches.call(this,trails, limit, batchSize); },this), 5, "add-trails");
			}
			else // we're done, notify listeners
			{
				this._startPolling(UPDATE_INTERVAL); 
				this._fire({name:"trails-loaded", trails:newTrails});
				if (_preloadMarks)
					this._lookupTrailMarks(newTrails);
			}
	   	};

    	addInSmallBatches.call(this, trails, 0, batchSize);
    };

    this._loadMarks=function(marksData, isTrailMarks) 
	{
		if (this._blockLoad)
		{
			loadQueue.push(marksData); 
			Log.debug("Blocking Load! loadQueue = "+loadQueue.length);
			return null;
		}
		
		// Figure out which trails(s) are going to get their marks updated. Tell the trail
		// that their marks are going to be updated/refreshed so that they can cleanup when we tell
		// the trail their marks have been refreshed.
		var trailsToUpdate = new Array();
		var i;
		if (isTrailMarks)
		{
			for (i=0; i < marksData.length; i++) 
			{		
				var trailId = marksData[i]["interests"][0][4];
				var existingTrail = this._getTrailById(trailId);
				if (existingTrail)
				{
					trailsToUpdate[existingTrail.getId()] = existingTrail;
				}
			}

			for (var i in trailsToUpdate)
				trailsToUpdate[i].setMarksDirty(true);
		}

		var loaded = new Array();
		for (i = 0; i < marksData.length; i++) 
		{
			var mark = this._addMark(marksData[i]);
			loaded.push(mark);
			//var trail = mark.getTrail();
		}
		
		// Tell our trails that their marks have been refreshed.
		for (var i in trailsToUpdate)
			trailsToUpdate[i].setMarksDirty(false);
		
		this._fire({ name:"marks-load", marks:loaded });
		
		if (_trails)
		{
			for (var i in _trails)
				if (!_trails[i].isMarksLoaded())
					return loaded;

			_preloadMarksComplete = true;
		}
		return loaded;
    }
    
    this._getMarkById=function(id) 
    {
		if (tempIdMap[id])
			id = tempIdMap[id];			

		if (_marksCache && typeof(_marksCache[id])!="undefined")
			return _marksCache[id];

/*
 		if (_trails === null)
			return null;

		for (var i in _trails)
		{
			var mark = _trails[i].getMarkById(id);
			if (mark)
				return (mark);
		}
*/

		return null;
    }
    
    this._urlToMarkId = function(url)
    {
		if (!browser.urlAtServer(url)) { return false; }

		refPath = url.substring(browser.getServer().length);
		pathArray = refPath.split('/');

		//check for mark url
		if ((pathArray.length > 2) && 
			(pathArray[1] === "marks"))
		{
			return pathArray[2];
		}

		return false;
    }

    this._urlToToken = function(url)
    {
		if (!browser.urlAtServer(url)) { return false; }

		refPath = url.substring(browser.getServer().length);
		pathArray = refPath.split('/');

		//check for mark url
		if ((pathArray.length > 3) && 
			(pathArray[1] === "marks") || (pathArray[1] === "trails"))
		{
			return pathArray[3];
		}

		return false;
    }    

    this._urlToTrailId = function(url)
    {
		if (!browser.urlAtServer(url)) { return false; }

		//check for trailhead url, return id of first bubble in memory
		refPath = url.substring(browser.getServer().length);
		pathArray = refPath.split('/');

		if ((pathArray.length > 2) && 
			(pathArray[1] === "trails"))
		{
			return pathArray[2];
		}

		return false;
    }

    this._setMarksData = function(marksData)
    {
    	markDataSet = true;
    	this._loadMarks(marksData,false);
    }
    
	/* lookup a mark from a known id */
	this._lookupMarkById = function(markId,token,success,failure)
	{
		var mark = this._getMarkById(markId);
		if (mark)
		{
			if (this._sync)
				success(mark)
			else
				wait(function(){success(mark);},0,"lookupMarkById callback");
		}	

		function _success(response)
		{
            this._loadMarks(response["bubbles"], false); 		
			var mark = this._getMarkById(markId);
			if (mark)
				success(mark);
			else
				failure(response);
		}
		
		function _failure(response)
		{
			if (failure)
				failure(response);
		}

		browser.lookupByBubbleId(markId, true, bind(_success,this),bind(_failure,this), token);
	}

	this._isValidProtocol = function(url)
	{
    	//check protocol of page url, only lookup on http/https
		var p = Utils.parseUrl(url);
		if (p['scheme'])
		{
			var proto = p['scheme'];
	
			switch (p['scheme'])
			{
				case "http":
				case "https":
					return true;		
			}
		}
		return false;
	}
 	/**
	 * _lookupMarks(page) - lookup all the marks for a URL
	 */
    this._lookupMarks = function(page) 
    {
    	if (markDataSet) { return; } //mark data was manually set, don't lookup
    	
    	// check page protcol (don't allow file:/chrome:/c: etc...)
    	if (!this._isValidProtocol(page.getUrl()))
    		return; 	
    		
        function success(response) {
            this._loadMarks(response["bubbles"], false); 
         };

        function failure(response) {
            Log.error("UserModel._lookupMarksByXXX.failure: " + Utils.errorToString(response));
        };
		
		var token = "";
		// we no longer use tokens
		/*
		var token = this._urlToToken(page.getReferrer());
		if (token)
		{
			if (!_token)
				_token = token;
		} else {
			if (_token)
				token = _token;
		}
		*/
		var showPublic = browser.getOption("pageDecorations.showAllBubbles");
		var showMarks = browser.getOption("pageDecorations.showBubbles");
   		
		var selectedMark = page.getSelectedMark();
		var markId = selectedMark ? selectedMark.getId() : 0;
		var trailId = false;

		if (markId <= 0)
		{
			markId = this._urlToMarkId(page.getReferrer());
			trailId = this._urlToTrailId(page.getReferrer());
		}

		if (trailId)
		{
			browser.lookupByTrailId(trailId, showPublic, bind(success,this),bind(failure,this), token);
		} else if (markId) {
			browser.lookupByBubbleId(markId, showPublic, bind(success,this),bind(failure,this), token);
		} else if ((showPublic && showMarks)||(isLoggedIn && !_preloadMarksComplete)) {
			browser.lookupByURL(page.getUrl(), showPublic, bind(success,this),bind(failure,this), token);
		}
    }; 
   
	this._getTrails = function()
	{
		var trails = new Array()
		this._lookupTrails();

		if (_trails)
			for (var i in _trails)
			{
				trails.push(_trails[i]);
			}

		return trails;
	}
	   
	this._getOwnTrails = function()
	{
		var ownTrails = new Array();
		this._lookupTrails();
		
		if (_trails)
			for (var i in _trails)
			{
				if (_trails[i].isUsers())
					ownTrails.push(_trails[i]);
			}

		return ownTrails;
	}

	this._getEditableTrails = function()
	{
		var editableTrails = new Array();
		this._lookupTrails();
		
		if (_trails)
			for (var i in _trails)
			{
				if (_trails[i].getEditable())
					editableTrails.push(_trails[i]);
			}

		return editableTrails;
	}

	this._destroy = function()
	{		
		this._fire({name:"destroy",user:this});
		tempIdMap = Utils.newArray();
		_marksCache = null;
		this._clear();		
		this._destroyModel();
	}
	
	this._clear = function()
	{
		this._destroyAllTrails();
		_trails = null;

		if (theme)
		{
			theme.destroy()
			theme = null;
		}
		
		trailsLoaded = false;
		loadQueue = new Array();
	}
	
	/* Returns active trails */
	this._getActiveTrails=function()
	{
		var activeTrails = new Array();
		var trail; 
		
		for (var i in _trails)
		{
			trail = _trails[i]
			if (trail.isActive()) 
			{
				activeTrails.push(trail);
			}
		}
		return (activeTrails);
	}
		
	this._lookupTrailMarks=function(trails)
	{			
   		Log.debug("_lookupTrailMarks()");
      	_lookupTrailInProgress++;
        
        var self = this;
  	    var MAX_BATCH = 5; //max number of trail to load marks at a time
		var MAX_MARKS_BATCH = 10; //max number of marks to load at a time

	    var startingIndex = 0;
	    var requestingUser = this._getUserName();
	    
		function lookupInBatches() 
		{
			// Terminate lookup if the user is no longer logged in (or different user)
			if (requestingUser != this._getUserName())
				return _lookupTrailInProgress--;
						
	        function callback() {       
				// Call self again to get the next batch
				wait(bind(lookupInBatches, self), 5 , "lookup-trail"); // get next batch, if necessary 
	        };   

			// Build the batch of trail ids... 
	    	var batchTrails = new Array();
	    	var j = startingIndex;
	    	var lastIndex = Math.min(trails.length, startingIndex+MAX_BATCH);
			var markCount = 0;
			for ( ; (j < lastIndex) && (markCount < MAX_MARKS_BATCH); j++)
			{
				var ltrail = trails[j];
				if (!ltrail.isMarksLoaded()) // only load trails whose marks aren't already loaded
				{
					markCount += trails[j].getLength();
					batchTrails.push(trails[j]);
				}
				startingIndex++;
			}
			
			if (batchTrails.length > 0) {

				//put in some delay between these calls
				var delay = 2000; //shorter delay for the active trails 
				if (!batchTrails[0].isActive())
					delay = 5000; //longer delay for inactive trails

				wait(bind(function()
				{	
					if (requestingUser === this._getUserName())
						self._loadMarksForTrails(batchTrails, callback);	
				},self),delay,"load-marks-for-trails");

			} else { // we're finished loading
			 	_lookupTrailInProgress--;
			}
		}
		wait(bind(lookupInBatches, self), 5, "lookup-trail"); 
	}
	
	this._loadMarksForTrails = function(trails,callback)
	{
	    var requestingUser = this._getUserName();

		function s(response)
		{
			if (requestingUser != this._getUserName())
				return;

			this._loadMarks(response["bubbles"], true);

			callback();
		} 
		
		function f(error)
		{
			callback();
		}
		
		var trailIds = new Array();
		for (var i in trails)
			if (!trails[i].isMarksLoaded())
				trailIds.push(trails[i].getId())

		browser.lookupByInterest(trailIds,bind(s,this),bind(f,this));
	}

	this._pollNow=function()
	{
		this._poll(true);
	}
	
    this._poll=function(unscheduled) {
		if (!this._isLoggedIn()) { return; }
		
		// if we haven't retrieve trails, retrieve them before we start polling
		if (_timestamp == -1) 
		{
			this._lookupTrails();
			return;
		}
		
  		Log.debug("UserModel._poll() previous Time: " + _timestamp);     	
 		// if we're doing an unscheduled update... cancel the scheduled update.
		var restart = !unscheduled;
  		if (unscheduled && _pollChangesId)
  		{
  			this._cancelPolling();
 			if (_pollEnabled)
 				restart = true;
  		}
  			
		function nextPoll()
		{
			if (restart)
			{
	   			_pollChangesId = wait(bind(this._poll, this), UPDATE_INTERVAL,"interest-poll");
	   			Log.debug("UserModel._poll(): pollId =  " + _pollChangesId ); 
	   		}
		}

    	var success = bind( function (response) {
 			bind(nextPoll,this)(); // reschedule this one first in case of exception
    		this._processChanges(response);
         }, this);
         
       	var failure = bind(function failure(response) {
       		Log.error("UserModel._poll(): Failed to get trail changes " + response.toString());
			bind(nextPoll,this)();
       	}, this);  
       	

		var getActivityCount = false; 
		var dateNow = new Date();
		var now = dateNow.getTime();
		// if its been 10 minutes since last 
		if (_pollActivitiesTimestamp == -1 || 
			( now - _pollActivitiesTimestamp) > 600000)
		{	
			getActivityCount = true;
			_pollActivitiesTimestamp = now;
		}													
       	
    	browser.lookupInterestChanges(_timestamp,getActivityCount, success,failure);
    }
    
    this._cancelPolling=function()
    {
    	 if (_pollChangesId)
    	 {
  			clearWait(_pollChangesId);
  			_pollChangesId = null;
  		} 
    }
   
    this._startPolling=function(when)
    {
    	 if (_pollChangesId)
    	 {
  			clearWait(_pollChangesId);
  			_pollChangesId = null;
  		} 

  		if (_pollEnabled && isLoggedIn && !isLoggingIn && !isLoggingOut)
  			_pollChangesId = wait(bind(this._poll, this),when,"interestPolling");
    }
    
    this._processChanges = function (changes) 
    {
      	var timestamp = changes["timestamp"];
  		_timestamp = timestamp;
    	Log.debug("UserModel._processChanges: timestamp: " + timestamp);

     	if (!changes)
    		return;
		
 		if (changes["new"])	
 		{
    		Log.debug("UserModel._processChanges: new trail(s)" );
			this._loadTrailData(changes["new"]);
 		}

		if (changes["subscribed"])
		{
   			Log.debug("UserModel._processChanges: subscribed trail(s)" );
			this._loadTrailData(changes["subscribed"]);
		}
			
		// Marks changed for these trails
		if (changes["contentChanged"])
		{		
   			Log.debug("UserModel._processChanges: contentChanged trail(s)" );
 			var reloadTrails = new Array(); // List of trail ids to have their marks reloaded.

			this._loadTrailData(changes["contentChanged"]);

			// Add to reloadTrailIds list for any existing trails
			var contentChangedTrails = changes["contentChanged"];
			for (var i = 0; i < contentChangedTrails.length; i++)
			{	
				var id = Number(contentChangedTrails[i]["id"]);
	            var existing = this._getTrailById(id);
	            if (existing)
	            {
	            	existing.setMarksLoaded(false);	
	            	reloadTrails.push(existing);
	            }	
			}
			if (reloadTrails.length > 0)
 				this._lookupTrailMarks(reloadTrails); 
		}
				    
     	var deleted = changes["deleted"];
  		if (deleted) {
  			Log.debug("UserModel._processChanges: deleted trail(s)" );
      		var values = Utils.arrayToString(deleted);
     		Log.debug("._processChanges(): deleted: " + values);

    		this._destroyTrails(deleted);
	    }

	    deleted = changes["unsubscribed"];
  		if (deleted) {
     		var values = Utils.arrayToString(deleted);
     		Log.debug("._processChanges(): unsubscribed: " + values);

   			this._destroyTrails(deleted);
	    }
     		    
		var interestsChanged = changes["propertyChanged"];
		if (interestsChanged) {
  			Log.debug("UserModel._processChanges: propertyChanged trail(s)" );
    		var values = Utils.arrayToString(interestsChanged);
     		Log.debug("_processChanges(): propertyChanged: " + values);
     		this._loadTrailData(interestsChanged);
 	    }	
	    
 	    var eventCount = changes["EventCount"];
 	    if (eventCount != null && 
 	    	eventCount != _eventCount)
 	    {
    		Log.debug("_processChanges(): EventCount: " + eventCount);
 	    	_eventCount = eventCount;
 	    	this._fire({name:"EventCountChanged", count:eventCount});
 	    }
 	    
 	    var themeId = changes["theme"];
		if (themeId)
		{
			if (theme && theme.getId() != themeId)
			{
				theme.setId(themeId);
			}
		}
    }
    
	this._getHint = function()
	{
		return _hintMarkup;		
	}

    this._getNextHint = function()
    {
		//returns a hint from the server for user education
		//target is the place that the hint will be shown
		// - 'sidebar'
		// - 'site'		
		function success(response)
		{
			_hintAction = response.action;
			_hintMarkup = response.markup;
			this._fire({name:'hint', markup:response.markup, action:response.action});			
		}
		
		function failure(response)
		{
			_hintAction = '';
			_hintMarkup = '';
			this._fire({name:'hint', markup:'', action:''});			
		}

		browser.getUserHint(bind(success,this),bind(failure,this))
    }
}

Utils.implement(UserModel,ModelBase);


eval(Utils.declarePublic(
	"UserModel",
	{
		getUserName			:0,
		getTheme			:0,
		setToken			:1,
		autoLogin			:1,
		login				:2,
		logout				:0,
		isLoggedIn			:0,
		isAdmin				:0,
		isBetaTester		:0,
		isPowerUser			:0,
		isLoginInProgress	:0,
		isLoginAttempted	:0,
		isValidProtocol		:1,
		getTheme			:0,
		getUserPage			:0,
		createMark			:9,
		getMarks			:1,
		getTrails			:0,
		getOwnTrails		:0,
		getEditableTrails	:0,
		setPollEnabled		:1,
		pollNow				:0,
		mapTempId			:2,
		getTempId			:1,
		getTrailById		:1,
		getTrailByLabel		:2,
		addTrail			:1,
		getMarkById			:1,
		notifyLoggedOut		:1,
		loadMarksForTrails  :2,
		loadMarks			:2,
		setMarksData		:1,
		preloadMarks		:0,
		lookupMarkById		:4,
		getHint				:0,
		urlToMarkId			:1
	}
));

// /var/www/trailfire.com-release/content/client/model/trail-model.js 

/* Trail Model 

	state (not loaded, loaded, partially loaded)
	owner
	subscribed
	id
	label
	length
	active 	// indicates whether trail marks are to be pre-cached
	timestamp
	
	load marks
	load mark data
	getMarkById

	listens to his marks
	sends and trail changes (mark removed)

	updateMarks(markData)
		- delete marks
		- add new marks
		- send reorder mark event
	
	events
		"mark-added"
		"mark-removed"
		"reordered"
		"rename"

*/
function TrailModel(browser)
{
	ModelBase.call(this,"TrailModel");
		
	var _id = 0;
	this._getId = function _TrailModel_getId() { return _id; }
	this._setId = function _TrailModel_setId(val) { return _id = val; }
	
	var _label = "";
	this._getLabel = function _TrailModel_getLabel() { return _label; }
	this._setLabel = function _TrailModel_setLabel(val) { return _label = val; }

	var _category = "";
	this._getCategory = function _TrailModel_getCategory() { return _category; }
	this._setCategory = function _TrailModel_setCategory(val) { return _category = val; }

	var _description = "";
	this._getDescription = function _TrailModel_getDescription() { return _description; }
	this._setDescription = function _TrailModel_setDescription(val) { return _description = val; }
	
	var _length = 0;
	this._getLength = function _TrailModel_getLength() { return _length; }
	this._setLength = function _TrailModel_setLength(val) { return _length = val; }
	
	var _owner = "";
	this._getOwner = function _TrailModel_getOwner() { return _owner; }
	this._setOwner = function _TrailModel_setOwner(val) {return _owner = val; }
	
	var _timestamp = 0;
	this._getTimestamp = function _TrailModel_getTimestamp() { return _timestamp; }
	this._setTimestamp = function _TrailModel_setTimestamp(val) 
	{ 
		if (Number(val) > _timestamp)
		{
			_timestamp = Number(val);
			this._fire({name:"updated", trail:trail});
		}
		return _timestamp;
	}
		
	var _publicFlag = !browser.getOption("trails.defaultPrivate");	
	this._getPublicFlag = function _TrailModel_getPublicFlag() { return _publicFlag; }
	this._setPublicFlag = function _TrailModel_setPublicFlag(val) { 
		_publicFlag = Number(val);
	}

	var _privacyMode = browser.getOption("trails.defaultPrivate")?0:1;
	this._getPrivacyMode = function _TrailModel_getPrivacyMode() { return _privacyMode; }
	this._setPrivacyMode = function _TrailModel_setPrivacyMode(val) { return _privacyMode = val; }
	
	var _active = false;
	this._getActive = function _TrailModel_getActive() { return _active; }
	this._setActive = function _TrailModel_setActive(val) { return _active = val; }
	
	var _editable = false;
	this._getEditable = function _TrailModel_getEditable() { return _editable; }
	this._setEditable = function _TrailModel_setEditable(val) { return _editable = val; }

	var _favoriteFlag = false;
	this._getFavoriteFlag = function _TrailModel_getFavoriteFlag() { return _favoriteFlag; }
	this._setFavoriteFlag = function _TrailModel_setFavoriteFlag(val) { return _favoriteFlag = val; }
	
	var _marks = null;
	this._getMarks = function _TrailModel_getMarks(ordered) 
	{
		if (ordered)
		{
			function sortOnIndex(m1, m2)
			{
				return (m1.getTrailIndex() - m2.getTrailIndex());
			}
			_marks.sort(sortOnIndex);
		} 
		return _marks; 
	}
	this._setMarks = function _TrailModel_setMarks(val) { return _marks = val; }
	
	var _marksLength = 0;
	this._getMarksLength = function _TrailModel_getMarksLength() { return _marksLength; }
	this._setMarksLength = function _TrailModel_setMarksLength(val) { return _marksLength = val; }
	
	var _indexToMarkMap = null;
	this._getIndexToMarkMap = function _TrailModel_getIndexToMarkMap() { return _indexToMarkMap; }
	this._setIndexToMarkMap = function _TrailModel_setIndexToMarkMap(val) { return _indexToMarkMap = val; }
	
	var _marksLoaded = false;
	this._getMarksLoaded = function _TrailModel_getMarksLoaded() { return _marksLoaded; }
	this._setMarksLoaded = function _TrailModel_setMarksLoaded(val) { return _marksLoaded = val; }
	
	var _marksDirty = false;
	this._getMarksDirty = function _TrailModel_getMarksDirty() { return _marksDirty; }
	this._setMarksDirty = function _TrailModel_setMarksDirty(val) { return _marksDirty = val; }
	
	var _marksPartial = false;
	this._getMarksPartial = function _TrailModel_getMarksPartial() { return _marksPartial; }
	this._setMarksPartial = function _TrailModel_setMarksPartial(val) { return _marksPartial = val; }
	
	var _marksLoading = false;
	var _marksLoadingQueue = Utils.newArray();

	this.markDispatch = bind(function(event)
	{
	    switch (event.name) {
	    case "destroy": 
	        this._removeMark(event.mark);
	    	break;
	    case "modified":
				var id = event.mark.getId();
				var index = event.mark.getTrailIndex();
				if (_marks[id] != _indexToMarkMap[index])
					this._buildIndexMap();
	    	break;
	    }		
	},this);
	
	this._updateLoadedState = function()
	{
		// Update load state(s), if we have loaded all of our trail length
		if (!_marks)
		{
			_marksPartial = false;
			_marksLoaded = false;
		} else if ( _marksLength >= _length) {
			_marksPartial = false;
			_marksLoaded = true;
			_length = _marksLength;
		} else {
			_marksPartial = true;
			_marksLoaded = false;
		}
	}

	this._load = function(trailParams)
	{
		Utils.assert(typeof(trailParams.id) != "undefined",function()
		{
			return "Trail params should have an id";
		});	

		var newId = Number(trailParams["id"]);

		if (_id) Utils.assert(newId == _id,function()
		{
			return "May not change ID on trail";
		});	

		Utils.assert(newId > 0,function()
		{
			return "Trail id must be greater than 0";
		});	

		if (newId)
	        _id = newId;
		
         _label = trailParams["label"];
        _length = Number(trailParams["trailLength"]);
        _owner = trailParams["owner"];

		//public is a reserved keyword, so we use publicFlag in mark-model.js
		if (typeof(trailParams["publicFlag"]) != "undefined")
			trailParams["public"] = trailParams["publicFlag"];
			
		if (typeof(trailParams["public"]) != "undefined")
		{
			this.setPublicFlag(trailParams["public"]);
			if (typeof(trailParams["publicEdit"]) != "undefined")
			{
				if (trailParams["public"] == "1")
				{
					if (trailParams["publicEdit"] == "1")
					{
						this._setPrivacyMode(2);						
					} else {
						this._setPrivacyMode(1);
					}
				} else 
					this._setPrivacyMode(0);
			}
		}
			
		if (trailParams["categories"] && trailParams["categories"].length > 0)
			_category = trailParams["categories"][0];

		if (trailParams["description"])
			_description = trailParams["description"];

		if (typeof(trailParams["editable"])!= "undefined")
		{
			_editable = trailParams["editable"];
		}
		else if (browser.getUser().isLoggedIn())
			_editable = (_owner == browser.getUser().getUserName())

		if (trailParams["timestamp"])
			if (Number(trailParams["timestamp"]) > _timestamp)
				_timestamp = Number(trailParams["timestamp"]);
		else
			if (!_timestamp)
				_timestamp = Math.floor((new Date()).getTime() / 1000);

		if (typeof(trailParams["editable"]) != "undefined")
		{
			this._setEditable(trailParams["editable"]);
		}

		if (typeof(trailParams["favorite"]) != "undefined")
		{
			this._setFavoriteFlag(trailParams["favorite"]);
		}

		this._updateLoadedState();
		this.fire({name:"updated", trail:this});
	}

	this._setLength = function(length)
	{
		_length = length;
		this._updateLoadedState();
	}

	this._getMarkById=function(id)
	{
		if (_marks == null) {return null;}
		return _marks[id];
	}

	this._getMarkByIndex=function(index)
	{	
		if (_indexToMarkMap == null) { return null; }
		return _indexToMarkMap[index];
	}
	
	this._loadMarks = function(callback) //trigger server lookup of marks for this trail
	{
		if (_marksLoaded) { callback(); return; }

		if (_marksLoading) 
		{
			if (callback)
				_marksLoadingQueue.push(callback);
			
			return;
		}
		_marksLoading = true;

		function marksLoadCallback() { 
			_marksLoading = false;

			if (callback) { callback() } 

			for (var i = 0; i< _marksLoadingQueue.length; i++)
				_marksLoadingQueue[i]();

			_marksLoadingQueue = Utils.newArray();
		};

		browser.getUser().loadMarksForTrails([this],marksLoadCallback);
	}

	this._buildIndexMap = function()
	{
		if (this.log)
			Log.debug("rebuilding index map");

		if (!_marks) { return; }
		_indexToMarkMap = Utils.newArray();
		for (var i in _marks)
		{
			var mark = _marks[i];
			_indexToMarkMap[mark.getTrailIndex()] = mark;
		}
	}

	this._addMark=function(mark)
	{
		// Make sure we don't have this mark in our list
		if (this._getMarkById(mark.getId())) { return; }
			
		if (!_marks)
			_marks = Utils.newArray();

		if (!_indexToMarkMap)
			_indexToMarkMap = Utils.newArray();
		
		
		// just insert the mark... we'll find next ordinal mark when requested.
		mark.setTrail(this);

		_marks[mark.getId()] = mark;
		_marksLength++;
		_indexToMarkMap[mark.getTrailIndex()] = mark;
		 
		mark.listen(this.markDispatch);
	
		this._updateLoadedState();
	
		this._fire({name:"mark-added", trail:this, mark:mark});	
	}

	this._discardMark = function(mark)
	{
		if (!_marks) { return; } //may have been destroyed

		var index = mark.getTrailIndex();
		_length--;

		this._removeMark(mark);
		if (!_marks) return; //remove mark may have destroyed us

		//adjust the trailIndex of all the marks greater than the discarded mark
		for (var i in _indexToMarkMap)
		{
			if (i > index)
			{
				var reindexMark = _indexToMarkMap[i];

				//ignore so we don't get the "modified" event 
				reindexMark.ignore(this.markDispatch);

				reindexMark.setTrailIndex(i - 1);

				//listen again
				reindexMark.listen(this.markDispatch);
			}
		}

		this._buildIndexMap();
	}

	this._removeMark=function(mark)
	{
		if (_marks && _marks[mark.getId()])
		{
			mark.ignore(this.markDispatch);
			_marksLength--;
			delete _marks[mark.getId()];
			delete _indexToMarkMap[mark.getTrailIndex()];

			this.fire({name:"mark-removed", trail:this, mark:mark});

			if (_marksLength == 0)
			{
				this._destroy();
				return;
			}
		}

		this._updateLoadedState();
	}

	this._setActive=function(active)
	{
		if (_active != active)
		{
			_active = active;
			this._fire({ name: "trail-active", trail: this});	
		}
	}
	
	this._getMarks=function(ordered)
	{
		if (!_indexToMarkMap) { return Utils.newArray(); }
		var marks = new Array();
		for (var i in _indexToMarkMap)
			marks.push(_indexToMarkMap[i]);
		
		if (ordered)
		{
			function sortOnIndex(m1, m2)
			{
				return (m1.getTrailIndex() - m2.getTrailIndex());
			}			
			marks.sort(sortOnIndex);
		} 
		
		return marks;
	}

	this._destroy = function()
	{
		this._fire({name:"destroy", trail: this});	

		if (_marks)
			for (var i in _marks)
			{
				var mark = _marks[i];
				mark.ignore(this.markDispatch);	
				mark.destroy();
			}
		_marks = null;
		_indexToMarkMap=null;
		
		this._destroyModel();
	}
	
	this._getLength=function()
	{
		// Return the length or our marks array if we are fully loaded
		if (_marksLoaded)
			return _marksLength;
		
		//Otherwise, return the trail length that was sent from server
		return _length;
	}
	
	this.toString=function() {
		if (!this.isUsers()) 
		{
			return  _label + " [" + _owner + "]";
		}
		else return _label; 
	}
	this.toLowerCase = function() { return _label.toLowerCase(); }
	this.toUpperCase = function() { return _label.toUpperCase(); }
	
	this._isUsers = function() { return _owner == browser.getUser().getUserName(); }
	this._isActive = function() { return _active; }

	// returns the trail url
	this._getUrl = function() 
	{
		var url = browser.getServer() + _owner + "/trails/" + _id + "/" + Utils.urlEncode(_label);
		
		return (url); 
	}
	this.getPermalink = function() 
	{
		return this._getUrl();
	}
	
	// returns the trail properties page url
	this._getPropertiesUrl = function(inEdit) 
	{
		var url = browser.getServer() + _owner + "/trailview/" + _id;
		if (inEdit && this._isUsers())
			url += "?mode=edit";
		return url;
	}
	this._isMarksLoaded = function()
	{
		return (_marksLoaded);
	}
	this._isMarksPartiallyLoaded = function()
	{
		return (_marksPartial);
	}
	this._isMarksDirty = function()
	{
		return (_marksDirty);
	}

	this._setMarksDirty = function(dirty)
	{
		if (_marksDirty == dirty)
			return;
		if (!_marks) // nothing to do when we don't have any marks.
			return;
		 
		for (var i in _marks)
		{
			if (dirty)
			{
				// Tell our marks they need to be refreshed
				_marks[i].setDirty(dirty);
			}
			else 
			{
				// We're now clean... if any of our marks are still dirty (i.e. didn't get refreshed), 
				// they must be deleted.
				if (_marks[i].isDirty())
					_marks[i].destroy();
			}
		}
		_marksDirty = dirty;
		
		if (!_marksDirty)
		{
			// Rebuild our trailIndex to marks map in case our marks have been re-ordered
			this._buildIndexMap();
			
			// Assume that our marks have been reordered... in future, we could keep track of
			// old marks and new mark order before sending this out.
			this._fire({name:"reorder", trail: this});	
		}
	}
	
	this._setMarksLoaded = function(loaded)
	{
		_marksLoaded = loaded;
	}
	
	this._discard = function(success,failure)
	{
		function s(response)
		{
			if (success)
				success(response);

			this._destroy();
		}
		
		function f(response)
		{
			if (failure)
				failure(response)
			else
				browser.alert("Error",Utils.errorToString(response));
		}

	    if (browser.getUser().isLoggedIn())
	    {
			if (this._isUsers())
				browser.destroyInterest(this, bind(s,this),bind(f,this));
			else
				browser.unsubscribeInterest(this,bind(s,this),bind(f,this));
		} else
			this._destroy();
	}	

	// Changes public/private for this trail on the server and notifies of change.
	this._togglePublic = function()
	{
		var newValue = 0;
		
		function s(response)
		{
			_publicFlag = newValue;
			this._fire({name:"updated", trail:this});
		}
		
		function f(response)
		{
			browser.alert("Error",Utils.errorToString(response));
		}

		if (_publicFlag)
		{
			newValue = 0;
			browser.makePrivate(this.getId(), bind(s,this),bind(f,this));
		}
		else
		{
			newValue = 1;
			browser.makePublic(this.getId(),bind(s,this),bind(f,this));
		}
	}	

	// Changes public/private for this trail on the server and notifies of change.
	this._updatePrivacyMode = function(_privacyMode,success,failure)
	{
		function s(response)
		{
			this._setPrivacyMode(_privacyMode);
			this._fire({name:"updated", trail:this});
			if (success)
				success();
		}
		
		function f(response)
		{
			if (failure)
				failure()
			else
				browser.alert("Error",Utils.errorToString(response));
		}

		browser.setTrailPermission(this.getId(), _privacyMode, false, bind(s,this),bind(f,this));
	}	

	// Changes public/private for this trail on the server and notifies of change.
	this._updateFavoriteFlag = function(_favoriteValue,success,failure)
	{
		function s(response)
		{
			this._setFavoriteFlag(_favoriteValue);
			this._fire({name:"updated", trail:this});
			if (success)
				success();
		}
		
		function f(response)
		{
			if (failure)
				failure()
			else
				browser.alert("Error",Utils.errorToString(response));
		}

		if (_favoriteValue)
			browser.subscribeInterest(_id, bind(s,this),bind(f,this));
		else
			browser.unsubscribeInterestId(_id, bind(s,this),bind(f,this));
	}	

	this._share = function(page)
	{		
		var id = this._getId();
		
		if (!id)
			return;
						
		var url = browser.getServer() + 'pages/share.php?id=' + id;	
		
		page.open(url, '_blank', 'menubar=no,location=no,resizable=yes,scrollbars=no,status=no,height=540,width=640');
	}
}

Utils.implement(TrailModel,ModelBase);
eval(Utils.declarePublic("TrailModel",
{
	getLabel:0,
	getDescription:0,
	getCategory:0,
	getLength:0,  
	getOwner:0,
	getState:0,
	getActive:0,
	getEditable:0,
	getTimestamp:0,
	getPublicFlag:0,
	getPrivacyMode:0,
	getFavoriteFlag:0,
	getMarkById:1,
	getMarkByIndex:1,
	getMarks:1,
	getUrl:0,			// returns the trail url
	getPropertiesUrl:1,	// returns the trail properties page url
	getPermalink:0,     // returns the trail url
	
	setId:1,
	setLabel:1,
	setDescription:1,
	setCategory:1,
	setLength:1,
	setOwner:1,
	setState:1,
	setActive:1,
	setEditable:1,
	setTimestamp:1,
	setPublicFlag:1,
	setPrivacyMode:1,
	setFavoriteFlag:1,
	setMarksDirty:1,
	setMarksLoaded:1,
	
	isActive:0,
	isMarksLoaded:0,
	isMarksPartiallyLoaded:0,

	addMark:1,
	load:1,
	loadMarks:1,
	discardMark:1,
	isUsers:0,
	discard:2,
	togglePublic:0,
	updatePrivacyMode:3,
	updateFavoriteFlag:3,
	share:1
}));



// /var/www/trailfire.com-release/content/client/model/mark-base-model.js 

/* Mark Base Model - represents a mark for a page, which can be a trail-mark (i.e. mark) or a bookmark from
 * some other system. 

*/

function MarkBaseModel(browser)
{
	ModelBase.call(this,"MarkBaseModel");

	// define private variables, and getter and setter functions
	var _id = 0;
	this._getId = function _MarkBaseModel_getId() { return _id; }
	this._setId = function _MarkBaseModel_setId(val) { return _id = val; }
	this._getRealId = function _MarkBaseModel_getRealId() { return _id; }
	
	var _url = ""; //The url stored in the DB for this mark
	this._getUrl = function _MarkBaseModel_getUrl() { return _url; }
	this._setUrl = function _MarkBaseModel_setUrl(val) { return _url = val; }

	var _queryUrl = ""; //The url that was used to retrieve this mark 
	this._getQueryUrl = function _MarkBaseModel_getUrl() { return _queryUrl; }
	this._setQueryUrl = function _MarkBaseModel_setUrl(val) { return _queryUrl = val; }
	
	var _owner = "";
	this._getOwner = function _MarkBaseModel_getOwner() { return _owner; }
	this._setOwner = function _MarkBaseModel_setOwner(val) { return _owner = val; }
		
	var _displayUrl = "";  // The url used to lookup this mark (what's in the browser url bar)
	this._getDisplayUrl = function _MarkBaseModel_getDisplayUrl() { return _displayUrl; }
	this._setDisplayUrl = function _MarkBaseModel_setDisplayUrl(val) { return _displayUrl = val; }
	
	var _image = 0;
	this._getImage = function _MarkBaseModel_getImage() { return _image; }
	this._setImage = function _MarkBaseModel_setImage(val) { return _image = val; }
	
	var _subject = "";
	this._getSubject = function _MarkBaseModel_getSubject() { return _subject; }
	this._setSubject = function _MarkBaseModel_setSubject(val) { return _subject = val; }
	
	var _content = "";
	this._getContent = function _MarkBaseModel_getContent() { return _content; }
	this._setContent = function _MarkBaseModel_setContent(val) { return _content = val; }
	
	var _date = 0;
	this._getDate = function _MarkBaseModel_getDate() { return _date; }
	this._setDate = function _MarkBaseModel_setDate(val) { return _date = val; }
	
	var _timestamp = 0;
	this._getTimestamp = function _MarkBaseModel_getTimestamp() { return _timestamp; }
	this._setTimestamp = function _MarkBaseModel_setTimestamp(val) { return _timestamp = val; }
	
	var _trail = null;
	this._getTrail = function _MarkBaseModel_getTrail() { return _trail; }
	this._setTrail = function _MarkBaseModel_setTrail(val) { return _trail = val; }
	
	var _trailIndex = 0;
	this._getTrailIndex = function _MarkBaseModel_getTrailIndex() { return _trailIndex; }
	this._setTrailIndex = function _MarkBaseModel_setTrailIndex(val) { return _trailIndex = val; }
	
	var _permalink = "";
	this._getPermalink = function _MarkBaseModel_getPermalink() { return _permalink; }
	this._setPermalink = function _MarkBaseModel_setPermalink(val) { return _permalink = val; }
	
	var _trailLabel = "";
	var _trailOwner = "";
	this._getTrailOwner = function  _MarkBaseModel_getTrailOwner() { return _trailOwner; }
	this._getTrailLabel = function _MarkBaseModel_getTrailLabel() { return _trailLabel; }
	this._setTrailLabel = function _MarkBaseModel_setTrailLabel(val) { 

		_trailLabel = val;
		
		// check the label for an embedded username. If embedded username, strip out the
		// username from the label, and change our local copy of the trail owner to the username.
		// 
		if ( browser.getUser())
		{
			var currentUser = browser.getUser().getUserName();
			// if trail label contains a username, use that username
			var label = val;	
			var reg = /(.+)\[([^\]].+)]$/;
	 		var ar = reg.exec(val);
	 		if (ar && ar.index >=0)
	 		{
	  			var label = ar[1].replace(/^\s+|\s+$/, '');
	 			_trailOwner = ar[2];  // update the local trail owner  
				_trailLabel = label;
	 		}
		}
		return; 
	}
	
	var _publicFlag = 0;
	this._getPublicFlag = function _MarkBaseModel_getPublicFlag() { return _publicFlag; }
	this._setPublicFlag = function _MarkBaseModel_setPublicFlag(val) { return _publicFlag = val; }
	
	var _similar = false; // inicates similarity to the current page.
	
	this._setSimilar=function(sim)
	{
		if (sim != _similar)
		{
			_similar = sim;
			this._fire({name:"modified", mark:this});
		}
	}
	this._getSimilar=function()
	{
		return _similar;
	}
	this._getTrailLink = function()
	{
		if (!_trail) { return ""; }
		return browser.getServer() + _trail.getOwner() + "/trails/" + _trail.getId();
	}

	this._getPermalink = function()
	{
		if (!this._getId()) { return ""; }
		return browser.getServer() + _trail.getOwner() + "/marks/" + this._getRealId();
	}
	
	this._isFollowing = function(page)
	{ 
		//is this marked specified as being followed by the url/referrer combo
		var referrer = page.getReferrer();
		var selectedMark = page.getSelectedMark();
		var markId = selectedMark ? selectedMark.getId() : 0;

		if (markId == _id) { return true; }

		if (!referrer) { return false; }
		var markPermalink = this.getPermalink();

		function isPermalink(url)
		{
			if (url.toLowerCase().indexOf(browser.getServer().toLowerCase())==0)
			{
				if (url.toLowerCase().indexOf("/marks/")>0)
					return true;
				if (url.toLowerCase().indexOf("/trails/")>0)
					return true;
			}
			return false;
		}

		function matches(baseUrl,longUrl)
		{
			if (baseUrl.toLowerCase() == longUrl.toLowerCase())
				return true;
			
			var slashUrl = baseUrl +'/';
			if (longUrl.toLowerCase().indexOf(slashUrl.toLowerCase())==0)
				return true;
			
			return false;
		}

		if (isPermalink(referrer))
		{
			if (matches(markPermalink,referrer))
				return true;
		
			if (_trailIndex==1) // could be a /trails/ link
			{
				var trailLink = this.getTrailLink();
				if (matches(trailLink,referrer))
					return true;
			}
		}

		return false;
	}
	
	this._setTrailIndex = function(newIndex)
	{
		if (_trailIndex != newIndex)
		{
			_trailIndex = newIndex;
			this._fire({name:"modified", mark:this});
		}
	}
	/**
	 * _load(data): sets the mark data.
	 */		
	this._load = function (data) 
	{
		var isNew = false;

		if (!this._getId())
			isNew = true;

 		this._setId(data["id"]);

		if (data["query"])
  			_queryURL = data["query"];

		if (data["url"])
			this._setUrl(data["url"]);

		if (data["owner"])
			_owner = 	data["owner"];

		// Set Display URL
		var link = this._getUrl().replace('http://',"").replace(/^www\./,'');
		if (link[link.length-1]=='/')
			link = link.substring(0,link.length-1);
		_displayURL 	= link;

		if (data["subject"])
			_subject 		= data["subject"]; 	
		if (data["content"])
			_content 		= data["content"]; 
		if (data["date"])
			_date			= data["date"]; 
		if (data["timestamp"])
			_timestamp		= data["timestamp"]; 

		var label = "";
		var trailLength = 0;
		var trailId = 0;

		if (data["interests"])
		{
			if (data["interestId"])
				trailId = data["interestId"];
			else
				trailId  = data["interests"][0][4];

			label = decodeURIComponent(data["interests"][0][0]);
			trailLength = data["interests"][0][3];

			var trailOwner = data["trailOwner"] ? data["trailOwner"] : data["owner"];

			_nextLink=decodeURIComponent(data["interests"][0][1]);
			_prevLink=decodeURIComponent(data["interests"][0][2]);
		

			var trailDetails =	
							{
								id:trailId, 
								label:label, 
								owner:data["trailOwner"], 
								trailLength:trailLength, 
								timestamp:data["timestamp"], 
								publicFlag:data["public"], 
								publicEdit:data["publicEdit"],
								editable:data["trailEditable"],
								favorite:data["trailFavorite"]
							};

			if (_trail)
			{
				if (_trail.getId() == trailId) //same trail as I had, update data
				{
					_trail._load(trailDetails);
				} else {
					_trail.discardMark(this); //remove from existing trail
					_trail = null;
				}
			}

			if (data["ordinal"])
				_trailIndex 	=	Number(data["ordinal"]);

			if (!_trail) //add to new trail
			{
				var user = browser.getUser();

				_trail = user.getTrailById(trailId); 

				if (_trail) //update trail data
				{
					_trail._load(trailDetails);
				} else { 	//create new trail
					_trail = user.addTrail(trailDetails, false);
				}

				_trailLabel = null;
				_trail.addMark(this);
			}
		}
		
		// send out an modified event
		if (!isNew)
			this._fire({ name:"modified", mark:this });
	}
	
	this._destroy = function()
	{
	
		this._fire({ name:"destroy", mark:this });

		this._destroyModel();
	}
	
	this._isAttached = function()
	{
		return false;
	}

	this._hasNext = function()
	{
		return false;
	}
	
	this._hasPrev = function()
	{
		return false;
	}

	this._canEdit = function()
	{
		return false;
	}

	this._discard = function(success,failure)
	{
		function s(response)
		{
			if (success)
				success(response);

			if (_trail)
				_trail.discardMark(this);

			this._destroy();
		}
		
		function f(response)
		{
			if (failure)
				failure(resonse)
			else
				browser.alert("Error",Utils.errorToString(response));
		}

		browser.deleteBubble(this._getRealId(),bind(s,this),bind(f,this));
	}

	this._share = function(page)
	{
		var id = this._getRealId();
		
		if (!id)
			return;
						
		var url = browser.getServer() + 'pipestone/share.php?mark=' + id;		
		page.open(url, '_blank', 'menubar=no,location=no,resizable=yes,scrollbars=no,status=no,height=540,width=840');
	}
}

Utils.implement(MarkBaseModel,ModelBase);

eval(Utils.declarePublic(
	"MarkBaseModel",
	{
		load:1,
		isFollowing:1,
		hasNext:0,
		hasPrev:0,
		canEdit:0,
		isAttached:0,
		
		discard:2,
		getId			:0,
		getQueryUrl		:0,
		getUrl			:0,
		getDisplayUrl	:0,
		getImage		:0,
		getSubject		:0,
		getContent		:0,
		getOwner		:0,
		getTrailOwner   :0,
		getDate			:0,
		getTimestamp	:0,
		getTrail		:0,
		getTrailIndex	:0,
		getTrailLink	:0,
		getPermalink	:0,
		getTrailLabel	:0,
		getSimilar      :0,
		
		setId			:1,
		setQueryUrl		:1,
		setUrl			:1,
		setDisplayUrl	:1,
		setImage		:1,
		setSubject		:1,
		setContent		:1,
		setOwner		:1,
		setDate			:1,
		setTimestamp	:1,
		setTrail		:1,
		setTrailIndex	:1,
		setPublicFlag   :1,
		setTrailLabel	:1,
		setSimilar      :1,
		
		share			:1
	}
));

// /var/www/trailfire.com-release/content/client/model/mark-model.js 

/* Mark Model 
		
getNext
getPrev

isOwnedBy
canEdit
isAttached

matchesUrl

save
saveMin
*/

function MarkModel(browser)
{
	MarkBaseModel.call(this, browser);

	// define private variables, and getter and setter functions
	var _realId = 0;
	this._getRealId = function _MarkModel_getRealId() { return _realId; }
	this._setRealId = function _MarkModel_setRealId(val) { return _realId = val; }

	var _tempId;
	this._setTempId = function(tempId){
		this._setId(tempId);		
		_tempId = tempId;
	}
	this._getTempId = function(){return _tempId;}
	
	var _mimeType = "";
	this._getMimeType = function _MarkModel_getMimeType() { return _mimeType; }
	this._setMimeType = function _MarkModel_setMimeType(val) { return _mimeType = val; }
	
	var _charSet = "";
	this._getCharSet = function _MarkModel_getCharSet() { return _charSet; }
	this._setCharSet = function _MarkModel_setCharSet(val) { return _charSet = val; }
	
	var _image = 0;
	this._getImage = function _MarkModel_getImage() { return _image; }
	this._setImage = function _MarkModel_setImage(val) { return _image = val; }
	
	var _elementPath = "#";
	this._getElementPath = function _MarkModel_getElementPath() { return _elementPath; }
	this._setElementPath = function _MarkModel_setElementPath(val) { return _elementPath = val; }
	
	var _offsets = {x:0, y:0};
	this._getOffsets = function _MarkModel_getOffsets() { return _offsets; }
	this._setOffsets = function _MarkModel_setOffsets(val) { return _offsets = val; }
	
	var _size = {x:400, y:280 };
	this._getSize = function _MarkModel_getSize() { return _size; }
	this._setSize = function _MarkModel_setSize(val) { return _size = val; }
	
	var _theme = null;
	this._getTheme = function _MarkModel_getTheme() { return _theme; }
	this._setTheme = function _MarkModel_setTheme(val) { return _theme = val; }
	
	var _themeId = 1;
	this._getThemeId = function _MarkModel_getThemeId() { return _themeId; }
	this._setThemeId = function _MarkModel_setThemeId(val) { return _themeId = val; }
	
	var _prevLink = "";
	this._getPrevLink = function _MarkModel_getPrevLink() { return _prevLink; }
	this._setPrevLink = function _MarkModel_setPrevLink(val) { return _prevLink = val; }
	
	var _nextLink = "";
	this._getNextLink = function _MarkModel_getNextLink() { return _nextLink; }
	this._setNextLink = function _MarkModel_setNextLink(val) { return _nextLink = val; }
	
	var _publicFlag = 0;
	this._getPublicFlag = function _MarkModel_getPublicFlag() { return _publicFlag; }
	this._setPublicFlag = function _MarkModel_setPublicFlag(val) { return _publicFlag = val; }
	
	var _dirty = false;
	this._getDirty = function _MarkModel_getDirty() { return _dirty; }
	this._setDirty = function _MarkModel_setDirty(val) { return _dirty = val; }

	var _commentCount = 0;
	this._getCommentCount = function _MarkModel_getCommentCount() 	 { return _commentCount; }
	this._setCommentCount = function _MarkModel_setCommentCount(val) { return _commentCount = val; }

	var _comments = Utils.newArray();
	var _commentsLoaded = false;
	var _commentsLoading = false;
	var _commentsLoadingCallbacks = Utils.newArray();

	var _canComment = true;
	this._getCanComment = function _MarkModel_getCanComment() { return _canComment; }
	this._setCanComment = function _MarkModel_setCanComment(val) { return _canComment = val; }

	// to prevent the mark from auto-showing in views after the user has 
	// chosen to close it
	var _hidden = false;
	this._isHidden  = function _MarkModel_isHidden() { 	return _hidden; }
	this._hide 		= function _MarkModel_hide(val)  {	_hidden = val;  }
		
	var _similar = false; // inicates similarity to the current page.
	
	this._setSimilar=function(sim)
	{
		if (sim != _similar)
		{
			_similar = sim;
			this._fire({name:"modified", mark:this});
		}
	}
	this._getSimilar=function()
	{
		return _similar;
	}
	this._isOnPage = function(page)
	{ //is this mark on this page
		if (this._isFollowing(page)) { return true; }

		url = page.getStandardizedUrl();

		var ret = false;
		var alturl = url;
		var pos;
		
		if (url.toLowerCase().indexOf('://www.')==-1) { 	//alt url should have www
			pos = url.indexOf('://');
			alturl = url.substring(0,pos)+'://www.'+url.substring(pos+3);
		} else {								//strip out www for alturl
			pos = url.toLowerCase()+url.toLowerCase().indexOf('://www.');
			alturl = url.substring(0,pos+3)+url.substring(pos+7);
		}
			
	    var cmp = this.getUrl();
		if (cmp == url)				{ ret = true; }
		if (cmp + "/" == url)		{ ret = true; }
		if (cmp == alturl)			{ ret = true; }
		if (cmp + "/" == alturl)	{ ret = true; }	

	    var query = this.getQueryUrl();
		if (query == url)			{ ret = true; }
		
		return ret;
	}
	
	this._getTrailLink = function()
	{
		if (!this.getTrail()) { return ""; }
		return browser.getServer() + this.getTrail().getOwner() + "/trails/" + this.getTrail().getId();
	}

	this._getPermalink = function()
	{
		if (!_realId) { return ""; }
		return browser.getServer() + this.getTrail().getOwner() + "/marks/" + _realId;
	}

	this._getTheme = function()
	{
		if (this.getTrail() && this.getTrail().isUsers())
			return browser.getUser().getTheme();

		if (!_theme)
			_theme = new Theme(_themeId,browser.getImageServer(),browser.getVersion());
			
		return _theme;
	}
	
	this._isFollowing = function(page)
	{ 
		//is this marked specified as being followed by the url/referrer combo
		var referrer = page.getReferrer();
		var selectedMark = page.getSelectedMark();
		var markId = selectedMark ? selectedMark.getId() : 0;

		if (markId == _realId) { return true; }

		if (!referrer) { return false; }
		var markPermalink = this.getPermalink();

		function isPermalink(url)
		{
			if (url.toLowerCase().indexOf(browser.getServer().toLowerCase())==0)
			{
				if (url.toLowerCase().indexOf("/marks/")>0)
					return true;
				if (url.toLowerCase().indexOf("/trails/")>0)
					return true;
			}
			return false;
		}

		function matches(baseUrl,longUrl)
		{
			if (baseUrl.toLowerCase() == longUrl.toLowerCase())
				return true;
			
			var slashUrl = baseUrl +'/';
			if (longUrl.toLowerCase().indexOf(slashUrl.toLowerCase())==0)
				return true;
			
			return false;
		}

		if (isPermalink(referrer))
		{
			if (matches(markPermalink,referrer))
				return true;
		
			if (this._getTrailIndex()==1) // could be a /trails/ link
			{
				var trailLink = this.getTrailLink();
				if (matches(trailLink,referrer))
					return true;
			}
		}

		return false;
	}

	/**
	 * _load(data): sets the mark data.
	 */		
	this._load = function (data) 
	{
		var isNew = false;

		if (!this._getId())
			isNew = true;

		if (data["tempId"] ) 
		{
 			_tempId = data["tempId"];
			this._setId(data["tempId"]);
 			if (data["tempId"] != data["id"])
 				browser.getUser().mapTempId(data["id"],data["tempId"]);	
 		} else if ( browser.getUser().getTempId(data["id"]) ) {
 			_tempId =  browser.getUser().getTempId(data["id"]);
			this._setId(_tempId);
 		} else {
 			_tempId = data["id"];
 			 this._setId(data["id"]);
		}

		if (data["id"])
			this._setRealId(data["id"]);

		if (typeof(data["image"]) != "undefined") 
		{
 			_image = Number(data["image"]);
 		}

		if (data["query"])
  			this._setQueryUrl(data["query"]);

		if (data["url"])
			this._setUrl (data["url"]);

		if (data["owner"])
			this._setOwner(data["owner"]);

		// Set Display URL
		var link = this._getUrl().replace('http://',"").replace(/^www\./,'');
		if (link[link.length-1]=='/')
			link = link.substring(0,link.length-1);
		this._setDisplayUrl(link);

		if (data["subject"])
			this._setSubject(data["subject"]); 	
		if (data["content"])
			this._setContent(data["content"]); 
		if (data["date"])
			this._setDate(data["date"]); 
		if (data["mimeType"])
			_mimeType 		= data["mimeType"];		
		if (data["timestamp"])
			this._setTimestamp(data["timestamp"]); 
		if (data["element"])
			_elementPath	= data["element"];
		if (data["x"] && data["y"])
			_offsets		=	{x:Number(data["x"]), y:Number(data["y"])};
		else
			if (!_offsets)
				_offsets 	=   {x:0,y:0}

		if (data["w"] && (data["w"] != "0") && data["h"] && (data["h"] != "0"))
			_size			= 	{x:Number(data["w"]), y:Number(data["h"])};

		if (data["theme"] && (data["theme"] != _themeId))
		{
			_themeId			= data["theme"];
			if (_theme)
			{
				_theme.setId(_themeid);
			} 
		}

		var label = "";
		var trailLength = 0;
		var trailId = 0;

		if (data["interests"])
		{
			if (data["interestId"])
				trailId = data["interestId"];
			else
				trailId  = data["interests"][0][4];

			label = decodeURIComponent(data["interests"][0][0]);
			trailLength = data["interests"][0][3];

			var trailOwner = data["trailOwner"] ? data["trailOwner"] : data["owner"];

			_nextLink=decodeURIComponent(data["interests"][0][1]);
			_prevLink=decodeURIComponent(data["interests"][0][2]);
		

			var trailDetails =	
							{
								id:trailId, 
								label:label, 
								owner:data["trailOwner"], 
								trailLength:trailLength, 
								timestamp:data["timestamp"], 
								publicFlag:data["public"], 
								publicEdit:data["publicEdit"],
								editable:data["trailEditable"],
								favorite:data["trailFavorite"]
							};

			if (this.getTrail())
			{
				if (this.getTrail().getId() == trailId) //same trail as I had, update data
				{
					this.getTrail()._load(trailDetails);
				} else {
					this.getTrail().discardMark(this); //remove from existing trail
					this.setTrail(null);
				}
			}

			if (data["ordinal"])
				this._setTrailIndex(parseInt(data["ordinal"]));

			if (!this._getTrail()) //add to new trail
			{
				var user = browser.getUser();
				this._setTrail(user.getTrailById(trailId)); 

				if (this._getTrail()) //update trail data
				{
					this._getTrail()._load(trailDetails);
				} else { 	//create new trail
					this._setTrail(user.addTrail(trailDetails, false));
				}

				this._setTrailLabel("");
				this._getTrail().addMark(this);
			}
		}
			
		if (data["comments"])
		{
			this._loadComments(data["comments"]);
			_commentCount = data["comments"].length;
		}

		if (data["noComments"])
			_canComment = false;

		if (data["commentCount"])
			_commentCount = data["commentCount"]

		_dirty = false; // we're not out of date.
		
		// send out an modified event
		if (!isNew)
			this._fire({ name:"modified", mark:this });
	}
	
	this._destroy = function()
	{
	
		this._fire({ name:"destroy", mark:this });

		if (_comments)
		{
			for (var i in _comments)
			{
				_comments[i].destroy();
			}
			_comments = null;
		}

		if (_theme)
		{
			_theme.destroy();
			_theme = null;
		}

		this._destroyModel();
	}
	
	this._isAttached = function()
	{
		return _elementPath == "#";
	}

	this._hasNext = function()
	{
		try
		{
			if (this._getTrail())
			{
				if (this._getTrail().getMarkByIndex(this._getTrailIndex()+1))
					return true;
			}
		} catch (ex) {
			;//if anything fails, we have a fallback
		}
		if (_nextLink)
			return true;
			
		return false;
	}
	
	this._hasPrev = function()
	{
		try
		{
			if (this._getTrail())
			{
				if (this._getTrail().getMarkByIndex(this._getTrailIndex()-1))
					return true;
			}
		} catch (ex) {	
			;//if anything fails, we have a fallback
		}
		
		if (_prevLink)
			return true;
			
		return false;
	}

	this._navigateNext = function(page)
	{
		try
		{
			var nextMark = null;
			if (this._getTrail())
			{
				if (nextMark = this._getTrail().getMarkByIndex(this._getTrailIndex()+1))
				{
					nextMark.hide(false);
					return page.selectMark(nextMark);
				}
			}
		} catch (ex) {
			;//if anything fails, we have a fallback
		}
		
		if (_nextLink)
			page.navigate(_nextLink);
	}
	
	this._navigatePrev = function(page)
	{
		try
		{
			var prevMark = null;
			if (this._getTrail())
			{
				if (prevMark = this._getTrail().getMarkByIndex(this._getTrailIndex()-1))
				{
					prevMark.hide(false);
					return page.selectMark(prevMark);
				}
			}
		} catch (ex) {	
			;//if anything fails, we have a fallback
		}
		
		if (_prevLink)
			page.navigate(_prevLink);
	}

	this._canEdit = function()
	{
		// the owner of the mark or of the trail can edit the mark
		return this._getTrail().isUsers() || this._getOwner() == browser.getUser().getUserName();
	}

	this._save = function(page,success,failure)
	{
		if (!this._canEdit()) { failure("You do not have permission to edit this mark."); return; }

		function s(response)
		{
			this._load(response);

			this._getTrail().setActive(true);

			if (success)
				success(response);
		
			this._fire({name:"save", mark:this });
		}
		
		function f(response)
		{
			if (failure)
				failure(response)
			else
				browser.alert("Save Failed",Utils.errorToString(response));
		}

		var trailLabel = this._getTrailLabel(); 
		var isPublic = !browser.getOption("trails.defaultPrivate");

		if (!trailLabel) //use the existing trail
		{
			if (this._getTrail())
			{
				trailLabel = this._getTrail().getLabel();
				isPublic = this._getTrail().getPublicFlag();
			} else {
				throw new Error("Saving a mark with no trail label");
			}
		}

	    browser.annotate(
	        this._getRealId(),
	        this._getId(),
	        this._getUrl(),
	        page.getMimeType(), 
	        _elementPath, 
	        _offsets, 
	       	this._getSubject(), 
	        this._getContent(), 
	        [ trailLabel ],
	        this._getTrailOwner(),
	        page.getCharSet(),
	        isPublic, 
	        bind(s,this), 
			bind(f,this));
	}
	
	this._savePosition = function(success,failure)
	{		
		if (!this._canEdit()) { return; }

		function s(response)
		{
			if (success)
				success(response);
		}
		
		function f(response)
		{
			if (failure)
				failure(response)
			else
				browser.alert("Save Failed",Utils.errorToString(response));
		}

		browser.annotateSaveMin(
			_realId,
			_elementPath,
			_offsets,
			_size,
	        bind(s,this), 
			bind(f,this)
		);
	}
	
	this._discard = function(success,failure)
	{
		function s(response)
		{
			if (success)
				success(response);

			if (this._getTrail())
				this._getTrail().discardMark(this);

			this._destroy();
		}
		
		function f(response)
		{
			if (failure)
				failure(resonse)
			else
				browser.alert("Error",Utils.errorToString(response));
		}

		browser.deleteBubble(_realId,bind(s,this),bind(f,this));
	}

	this._isDirty		= function(){return _dirty;}

	/********
			* Mark comments
		    ****************/

	// create a new comment for this mark on the server
	this._createComment = function(text,success,failure)
	{
		function _success(response)
		{
			if (response.comment)
			{
				var comment = new MarkCommentModel(browser,this);
				comment.load(response.comment);

				_commentCount++;

				this._addComment(comment);
	
	
				if (success)
					success(comment)
			} else {
				if (failure)
					failure(response)
			}
		}
		
		function _failure(response)
		{
			if (failure)
				failure(response)
		}

		browser.addMarkComment(_realId,text,0,bind(_success,this),bind(_failure,this));
	}


	this._getComments = function()	
	{
		var comments=Utils.newArray();
		for (var i in _comments)
		{
			comments.push(_comments[i]);
		}

		this._lookupComments();

		return comments;
	}

	this._lookupComments = function(success,failure)
	{
		if (_commentsLoaded || (_commentCount == 0))
		{
			if (success)
				success();
			return;
		}

		if (_commentsLoading) 
		{
			if (success || failure)
				_commentsLoadingCallbacks.push({s:success,f:failure});

			return;
		}
		_commentsLoading = true;
		
		function _success(response)
		{
			_commentsLoading = false;
			_commentsLoaded = true;

			this._loadComments(response.comments);

			var callbacks;
			while (callbacks = _commentsLoadingCallbacks.shift())
			{
				if (callbacks.s)
					callbacks.s();
			}

			if (success)
				success(response);
		}
		
		function _failure(response)
		{
			_commentsLoading = false;
			_commentsLoaded = false;
			
			var callbacks;
			while (callbacks = _commentsLoadingCallbacks.shift())
			{
				if (callbacks.f)
					callbacks.f();
			}

			if (failure)
				failure(response);
		}
		
		browser.lookupMarkComments(_realId,bind(_success,this),bind(_failure,this));
	}	

	this._addComment = function(comment)
	{
		if (!_comments[comment.getId()])
		{
			_comments[comment.getId()]=comment;
			this._fire({name:"comment-added", mark:this, comment:comment});
		}	
	}

	this._removeComment = function(comment)
	{
		if (_comments[comment.getId()])
		{
			delete _comments[comment.getId()]			
		}
	}

	this._loadComments = function(commentsParam)
	{
		for (var i = 0; i < commentsParam.length; i++)
		{
			if (commentsParam[i].id)
			{
				var comment = _comments[commentsParam[i].id];
				if (!comment)
				{
					comment = new MarkCommentModel(browser,this);
				}
				comment.load(commentsParam[i]);
				this._addComment(comment);
			}
		}

		_commentsLoaded = true;
	}

	this._share = function(page)
	{
		var id = this._getRealId();
		
		if (!id)
			return;
						
		var url = browser.getServer() + 'pipestone/share.php?mark=' + id;		
		page.open(url, '_blank', 'menubar=no,location=no,resizable=yes,scrollbars=no,status=no,height=540,width=840');
	}
}

Utils.implement(MarkModel,MarkBaseModel);

eval(Utils.declarePublic(
	"MarkModel",
	{
		load:1,

		isOnPage:1,
		isFollowing:1,

		hasNext:0,
		hasPrev:0,
		navigateNext:1,
		navigatePrev:1,
		canEdit:0,
		isAttached:0,
		
		save:3,
		savePosition:2,
		discard:2,

		getRealId		:0,
		getTempId		:0,
		getUrl			:0,
		getDisplayUrl	:0,
		getImage		:0,
		getSubject		:0,
		getContent		:0,
		getOwner		:0,
		getTrailOwner   :0,
		getDate			:0,
		getTimestamp	:0,
		getElementPath	:0,
		getOffsets		:0,
		getSize			:0,
		getTrail		:0,
		getTrailIndex	:0,
		getTheme		:0,
		getThemeId		:0,
		getMimeType		:0,
		getCharSet		:0,		
		getTrailLink	:0,
		getPermalink	:0,
		getTrailLabel	:0,
		getSimilar      :0,
		getCanComment	:0,
		
		setId			:1,
		setRealId		:1,
		setTempId		:1,
		setQueryUrl		:1,
		setUrl			:1,
		setDisplayUrl	:1,
		setImage		:1,
		setSubject		:1,
		setContent		:1,
		setOwner		:1,
		setDate			:1,
		setTimestamp	:1,
		setElementPath	:1,
		setOffsets		:1,
		setSize			:1,
		setTrail		:1,
		setMimeType	    :1,
		setCharSet		:1,
		setTrailIndex	:1,
		setTheme		:1,
		setThemeId		:1,
		setPublicFlag   :1,
		setTrailLabel	:1,
		setDirty		:1, 
		setSimilar      :1,
		setCanComment	:1,

		isDirty			:0,
				
		createComment	:3,
		getComments		:0,
		getCommentCount	:0,
		setCommentCount :1,
		removeComment	:1,
		share			:1,
		
		hide			:1,
		isHidden		:0		
	}
));

// /var/www/trailfire.com-release/content/client/model/mark-comment-model.js 

/* Mark Comment Model - model to hold comments for marks */

function MarkCommentModel(browser,mark)
{
	ModelBase.call(this,"MarkCommentModel");
	var _id = 0;
	var _owner = null;
	var _text = null;
	var _created = null;
	var _modified = null;
	
	this._getMark = function() { return mark; }
	this._getId = function() { return _id; }
	this._getOwner = function() { return _owner; }
	this._getCreated = function() { return _created; }
	this._getModified = function() { return _modified; }
	this._getText = function() { return _text; }
	this._setText = function(text) { return _text = text; }	
	
	this._getOwnerPage = function() 
	{
		return browser.getServer() + _owner;
	}

	this._load = function(commentParams)
	{
		if (commentParams["id"])
			_id = commentParams["id"];

		if (commentParams["authorName"])
			_owner = commentParams["authorName"];

		if (commentParams["content"])
			_text= commentParams["content"];

		if (commentParams["postedTS"])
			_created = commentParams["postedTS"];

		if (commentParams["savedTS"])
			_modified = commentParams["savedTS"];

		this.fire({name:"updated", comment:this});		
	}
	
	this._save = function(success,failure)
	{
		function _success(response)
		{
			if (response.comment)
			{
				this._load(response.comment);
	
				if (success)
					success(response);
			} else {
				if (failure)
					failure(response);
			}
		}
		
		function _failure(response)
		{
			if (failure)
				failure(response);
		}		

		browser.editMarkComment(mark.getRealId(),_id,_text,bind(_success,this),bind(_failure,this));
	}
	
	this._discard = function(success,failure)
	{
		function _success(response)
		{
			if (success)
				success(response);

			mark.removeComment(this);
			mark.setCommentCount(mark.getCommentCount() - 1);

			this._destroy();
		}
		
		function _failure(response)
		{
			if (failure)
				failure(response);
		}
		
		browser.deleteMarkComment(mark.getRealId(),_id,bind(_success,this),bind(_failure,this));		
	}
	
	this._destroy = function()
	{
		this._fire({name:"destroy",comment:this});
		this._cleanup();
	}
}

Utils.implement(MarkCommentModel,ModelBase);
eval(Utils.declarePublic(
	"MarkCommentModel",
	{
		load:1,
		getOwner:0,
		getOwnerPage:0,
		getText:0,
		setText:1,
		getCreated:0,
		getModified:0,
		getMark:0,
		save:2,
		discard:2
	}
));

// /var/www/trailfire.com-release/content/client/page/trail-tags-dialog.js 

/*
	Trail Category Dialog - select a category for a trail
*/

function TrailTagsDialog(browser,page,trailId,anchorElem,pageMarkWindow,success,failure)
{
	ClassBase.call(this,"TrailTagsDialog");

	var dlog;
	var theme = browser.getUser().getTheme();
	var tagInput = null;

	var trailTags = null;
	var trailTagElements = null;

	var tagSelectWrapper = null;

	var userTags = null;
	var scrollLayer = null;
	
	this._destroy = bind(function()
	{
		Controls.clearEvents(tagInput);
	
		if (scrollLayer)
			scrollLayer.destroy()
		scrollLayer = null;

		if (trailTagElements);
		for (var i in trailTagElements)
		{
			Controls.clearEvents(trailTagElements[i]);
		}
		trailTagElements = null;

		Controls.clearEvents(tagSelectWrapper);
		tagSelectWrapper = null;

		tagInput = null;
		userTags = null;
		theme = null;

		if (dlog)
			dlog.destroy();
		dlog = null;

		this._cleanup();
	},this);

	this._onTagClick = bind(function(event)
	{
		event = page.event(event); if (!event) return;
		var tagElem = event.target;	

		var tag = tagElem.innerHTML;
		trailTags[tag] = !trailTags[tag];
		tagElem.className = trailTags[tag] ? 'tagSelected' : 'tag';
		this._updateTagInput();
	},this);

	this._updateTagInput = function()
	{
		var tagString = "";
		for (var tag in trailTags)
		{
			if (trailTags[tag])
			{
				if (tagString != "")
					tagString += ", ";
				tagString += tag;
			}
		}
		
		tagInput.value = tagString;
	}

	this._onTagInputChange = function()
	{
		//parse the tag input, select any user tags
		for (var i in trailTags)
		{
			trailTags[i] = false;
		}

		var selectedTags = tagInput.value.split(",");
		for (var i in selectedTags)
		{
			var tag = Utils.trim(selectedTags[i]);
			trailTags[tag] = true;
		}
		
		this._updateTagElements();
	}

	this._updateTagElements = function()
	{
		for (var tag in trailTags)
		{
			if (trailTagElements[tag])
			{
				trailTagElements[tag].className = trailTags[tag] ? 'tagSelected' : 'tag';
			}
		}		
	}
	
	this._show = function()
	{
		function sink(e)
		{
			var event = page.event(e);
			Controls.eventDone(event);
			return true;
		}
		
		var buttons = [ 
			new TextButton(page,bind(this._onSave, this),"Save"),
			new TextButton(page,bind(this._onCancel, this),"Cancel")
		 ];
		
		var msg = "<br><br><b>Enter tags for your trail:</b><br>";
	
		var val = trailTags;

		dlog = new DialogWindow(page,
										"trail-category-dialog",	// id 
										theme,				// theme
										"Trail Tags", 	// title
										msg,  				// message
										buttons);
	
		var content = dlog.getBodyElement();
		var dlogElem = dlog.getDOMElement();
	
		tagInput = Controls.createChildElem("INPUT",content,dlogElem.id,"trail-tag-input","tagInput");
		tagInput.value = val;
	
		tagSelectWrapper = Controls.createChildDiv(content,dlogElem.id,"tag-select-wrapper","tagSelectWrapper");
		
		// sink mouse downto prevent selection
		Controls.addHandler(tagSelectWrapper,"mousedown", sink);

		var tagSelect = Controls.createChildDiv(tagSelectWrapper,dlogElem.id,"tag-select","tagSelect");
	
		trailTagElements = Utils.newArray();
		for (var i in userTags)
		{
			var tagElem = Controls.createChildDiv(tagSelect,dlogElem.id,"tag-select-"+i,"tag");
			tagElem.innerHTML = userTags[i];
			Controls.addHandler(tagElem, "click", this._onTagClick);
			trailTagElements[userTags[i]] = tagElem;
		}
		this._updateTagInput();
		this._updateTagElements();

		scrollLayer=new Scrollbar(page,tagSelect,theme,tagSelect.id,"trailTagScroll",tagSelectWrapper);

		//force above the status dialog with z-index
		if (pageMarkWindow) pageMarkWindow.zIndexOnTop(dlogElem);
	
		dropZIndex = dlogElem.style.zIndex;
	
		dlog.setSize(250,100);
		dlog.showAnchored(anchorElem);
		
		dlog.getWindow().setMode("input",true);
		dlog.getWindow().loadDelayedCallback(bind(function()
		{
			scrollLayer.setSize(190,68);

			wait(bind(function()
			{
				Controls.addHandler(tagInput,"keyup", this._onChange);

				Controls.addHandler(tagInput, "selectstart", sink);
				Controls.addHandler(tagInput, "mousedown", sink);

				tagInput.focus();
				tagInput.select();
			},this),250,"tag-input-focus");
		},this));
	}

	this._onSave = function()
	{		
		var newTags = tagInput.value;

		var savePos = Controls.getPosition(tagInput);
		var baseWindowId = 'bubble';
		if (pageMarkWindow) baseWindowId = "bubble-" + pageMarkWindow.getId();
		var statusDialog = new SimpleDialog(page,page.getDocument(),baseWindowId,"Saving",
			"Saving your tags ",savePos.x,savePos.y,null,null,false,false,theme);

		function _success(response)
		{
			if (success)
				success(response);

			statusDialog.destroy();
			statusDialog = null;

			this._destroy();
		}
		
		function _failure(response)
		{
			if (failure)
				failure(response)
			else
				page.alert("Error Tagging Trail",Utils.errorToString(response),anchorElem);

			statusDialog.destroy();
			statusDialog = null;

			this._destroy();
		}
	
		browser.setTrailTags(trailId,newTags,bind(_success,this),bind(_failure,this));
	}
	
	this._onCancel = function()
	{
		if (success)
			success();

		this._destroy()
	}

	this._loadTrailTags = function()
	{
		function _success(response)
		{
			for (var i=0; i<response.tags.length; i++)
			{
				trailTags[response.tags[i]] = true;
			}

			this._show();	
		}
		
		function _failure(response)
		{
			if (failure)
				failure(response);
			else
				page.alert("Error Loading Tags",Utils.errorToString(response),anchorElem);
		}

		browser.lookupTrailTags(trailId,bind(_success,this),bind(_failure,this));
	}
		
	this._loadUserTags = function() 
	{
		function success(response)
		{
			userTags = Utils.newArray();
			trailTags = Utils.newArray();
			for (var i=0; i<response.tags.length; i++)
			{
				userTags[i] = response.tags[i];
				trailTags[response.tags[i]] = false;
			}

			this._loadTrailTags();
		}
		
		function failure(response)
		{
			if (failure)
				failure(response)
			else
				page.alert("Error loading tags",Utils.errorToString(response));
		}

		browser.lookupUserTags(false,bind(success,this),bind(failure,this))

		return false;
	}

	this._onChange = bind(function(event) 
	{
		event=page.event(event); if (!event) { return; }

		if (Controls.eventKey(event) === 13) //enter key
		{
			this._onSave();
			return false;
		}

		this._onTagInputChange();
	},this);

	this._loadUserTags();
}

Utils.implement(TrailTagsDialog,ClassBase);


// /var/www/trailfire.com-release/content/client/site/site-page.js 

/* Page View 

	creates and manages pageMarkViews on the page, responds to events
*/

function SitePageView(browser,page)
{
	ClassBase.call(this,"SitePageView");
	
	var dlog = null;
	var currentCallback = null;
	var theme = new Theme(105,browser.getImageServer(),browser.getVersion());

	var pageListener = bind(function(event)
	{
		switch(event.name)
		{
			case "ready":
				this._onLoad();
			break;

			case "close":
				this._destroy();
			break;

			case "alert":
				this._onAlert(event.title,event.message,event.element);
			break;

			case "confirm":
				this._onConfirm(event.title,event.message,event.element,event.callback);
			break;
		}
	},this);
	page.listen(pageListener);

	this._destroy = function()
	{
		this._destroyDlog(false);
		page.ignore(pageListener);

		if (theme)
			theme.destroy();
		theme=null;

		this._cleanup();
	}
	
	//called when the page has finished loading
	this._onLoad = function()
	{
		var d = page.getDocument();
		var elem;
		// find any hint elements on the page and attach a site-hint-view to them
		for (var i =0; elem =  d.getElementsByTagName("DIV")[i]; i++)
		{
			if (elem.className == "userHint")
			{
				new SiteHintView(browser,page,theme,elem);
			}
		}

		if (Controls.detectIE6())
		{
			var img;
			// find any hint elements on the page and attach a site-hint-view to them
			for (var i =0; img=  d.getElementsByTagName("IMG")[i]; i++)
			{
				if (img.src && (img.src.toUpperCase().indexOf('.PNG')!=-1))
				{
					Controls.imageFix(img,page);
				}
			}
		}
	}
	
	/* page.alert and page.confirm display functions */	

	this._destroyDlog = function(callbackVal)
	{
		if (dlog)
			dlog.destroy();

		if (currentCallback)
			currentCallback(callbackVal);

		dlog = null;
		currentCallback = null;
	}

	this._onAlert = function(title,message,element)
	{
		if (!element)
		{
			return browser.browserAlert(message);
		}

		this._destroyDlog(false);
		theme.add(page,"alert-dialog");

		function onOk() { this._destroyDlog(true); }

		var buttons = [ 
			new TextButton(page,bind(onOk, this),"Ok")
	 	];
							
		dlog = new DialogWindow(	page,		//(page,id,theme,title,message,buttons)
									"alert-dialog",
									theme,
									title,
									message,
									buttons);

		var height = Math.max(120,Math.round(22 * message.length / 20));
		dlog.getWindow().setMinSize(160,height);
		dlog.setSize(160,height);
	    dlog.showAnchored(element);

	}
	
	this._onConfirm = function(title,message,element,callback)
	{
		if (!element)
			return browser.browserConfirm(message,callback);

		this._destroyDlog(false);
		currentCallback = callback;

		function onOk()
		{
			this._destroyDlog(true);
		}
		
		function onCancel()
		{
			this._destroyDlog(false);
		}

		theme.add(page,"confirm-dialog");
		
		var buttons = [ 
			new TextButton(page,bind(onOk, this),"Ok"),
			new TextButton(page,bind(onCancel, this),"Cancel")
	 	];
							
		dlog = new DialogWindow(	page,		//(page,id,theme,title,message,buttons)
									"confirm-dialog",
									theme,
									title,
									message,
									buttons);

		var height = Math.max(110,Math.round(20 * message.length / 22));
		dlog.getWindow().setMinSize(160,height);
		dlog.setSize(160,height);
	    dlog.showAnchored(element);
	}
}

Utils.implement(SitePageView,ClassBase);


// /var/www/trailfire.com-release/content/client/site/site-hint.js 

/* Site Hint 

	An on site hint that can be shown/hidden
*/

function SiteHintView(browser,page,theme,DOMElement)
{
	ClassBase.call(this,"SiteHintView");

	var hintButton;
	var hideButton;

	var pageListener = bind(function(event)
	{
		switch(event.name)
		{
			case "close":
				this._destroy();
			break;
		}
	},this);
	page.listen(pageListener);

	this._destroy = function()
	{
		if (hintButton)
			hintButton.destroy();
		hintButton = null;
		
		if (hideButton)
			hideButton.destroy();
		hideButton = null;

		page.ignore(pageListener);
		this._cleanup();
	}

	this._getHintBodyElement = function()
	{
		var elem;
		// find any hint elements on the page and attach a site-hint-view to them
		for (var i =0; elem =  DOMElement.getElementsByTagName("DIV")[i]; i++)
		{
			if (elem.className == "userhint-body")
			{
				return elem;
			}
		}		
	}

	this._onHide = function()
	{
		browser.setOption("hints.site",false);
		hideButton.hide();
		hintButton.show();
		Controls.hide(DOMElement.getElementsByTagName("DIV")[0]);
	}
	
	this._onHint = function()
	{
		browser.setOption("hints.site",true);
		hintButton.hide();
		hideButton.show();
		Controls.show(DOMElement.getElementsByTagName("DIV")[0]);
	}

	this._init = function()
	{
		var hintBody = this._getHintBodyElement();

		//create the hint and hide buttons
		hideButton = new ImageButton(page,this._onHide,theme,"close","Hide hints",null,"userhint-hide");
		hideButton.setParent(hintBody,hintBody.childNodes[0]);

		hintButton = new ImageButton(page,this._onHint,theme,"open-tiny","Show hints",null,"userhint-show");
		hintButton.setParent(DOMElement);

		if (browser.getOption("hints.site"))
		{
			this._onHint();
		} else {
			this._onHide();
		}
	}
	
	this._init();
}

Utils.implement(SitePageView,ClassBase);


// /var/www/trailfire.com-release/content/client/browser/site/site-browser.js 

/* Site Browser Control
	for drawing marks on a webpage (ie filmstrip)
*/
function SiteBrowserModel(server,version,jsWindow,imageServer)
{
	BrowserModel.call(this,server,version,"site",imageServer)
	
	this._onPageLoad = function(page,jsWindow)
	{
		new SitePageView(this,page);

		var unloadHandler = function()
		{
			Controls.removeHandler(jsWindow,"unload",unloadHandler)

			if (Controls.detectIE())
				ajaxRequestCleanup();

			clearWaitQueue();

			browser.quit();
		}
		Controls.addHandler(jsWindow,"unload",unloadHandler);
		
		function onWinReady()
		{
			Utils.anchorsTargetTop(jsWindow.document.body);						
		
			if (typeof(searchKeywords) == 'object')
			{
				Controls.highlightTerms(searchKeywords, jsWindow.document.body);
			}
		}
		//because people might mark our pages in the proxy, 
		//  we're going to bulk fix all of the anchors on the site
		//  to target=_top to break out of the frameset
		if (jsWindow.document.body)
		{
			onWinReady();
		} else {
			var fixOnLoad = function()
			{
				Controls.removeHandler(jsWindow,"load",fixOnLoad);
				onWinReady();
			};
			Controls.addHandler(jsWindow,"load",fixOnLoad);
		}
	}

	this._optionalUpgrade = function(message)
	{
       
	}	
	
	this._mandatoryUpgrade = function(message)
	{

	}
	
	this._isSite = function()
	{
		return true;
	}
}
Utils.implement(SiteBrowserModel,BrowserModel);
eval(Utils.declarePublic("SiteBrowserModel",
	{
		onPageLoad:4
	}
));

// /var/www/trailfire.com-release/content/client/model/site-page-model.js 

/*
	SitePageModel - page for using controls on the website
 */
 
function SitePageModel(server,version,browser)
{

	PageModel.call(this,browser,"SitePageModel");

	this._getWindow=function()			 	 { return window; }
	this._isDemo = function() 				 { return false; }
	this._open = function(url,name,features) { return window.open(url,name,features); }
	this._navigate = function(url)			 { return window.location.href=url; }
	this._getServer = function()			 { return server; }
	this._useShim = function()				 { return false; }
	this._event=function (event) 		 
	{
		return browser.event(window,event);
	}

	this._onLoad = bind(function()
	{
		Controls.removeHandler(window,"load",this._onLoad);
		this._fire({name:"ready",page:this});
	},this);	
	Controls.addHandler(window,"load",this._onLoad);
}

Utils.implement(SitePageModel,PageModel);


// /var/www/trailfire.com-release/content/pages/js/site.js 

function updateCharCount(ta, targetId) { // "ta" is a text area field
	var count = ta.value.length;
	if (count > 255)
		ta.value = ta.value.substring(0, 255);
	document.getElementById(targetId).innerHTML = "" + ta.value.length;
}
function showLoginForm(anchor) {
	document.getElementById('sidebarSignIn').style.display = 'block';
	anchor.style.display = 'none';
	document.getElementById('tfUsernameField').focus();
}
function submitIfReturn(ev) {
	var keyCode;
	if (null != window.event)
		keyCode = window.event.keyCode;
	else	
		keyCode = ev.which;
	if (keyCode == 13)
		document.loginForm.submit();
}

function featurePopup(popupId) {
	var popup = document.getElementById(popupId);
	popup.style.display = 'block';
}
function featurePopupClose(popupId) {
	var popup = document.getElementById(popupId);
	popup.style.display = 'none';
}
function menuPopup(popupId) {
	var popup = document.getElementById(popupId);
	popup.style.display = 'block';
}
function menuPopupClose(popupId) {
	var popup = document.getElementById(popupId);
	popup.style.display = 'none';
}
function pagePopup(popupId) {
	var popup = document.getElementById(popupId);
	popup.style.display = 'block';
}
function pagePopupClose(popupId) {
	if (typeof(event) != 'undefined') {
		if (!event.fromElement.contains(event.toElement) && 
			!document.getElementById(popupId).contains(event.toElement)) {
				var popup = document.getElementById(popupId);
				popup.style.display = 'none';
		}
	} else {
		var popup = document.getElementById(popupId);
		popup.style.display = 'none';
	}
}

function contextEditShow() {
	var editArea = document.getElementById('contextEdit');
	editArea.style.display = 'block';
	var displayArea = document.getElementById('contextDisplay');
	displayArea.style.display = 'none';
	var editLink = document.getElementById('contextEditLink');
	editLink.innerHTML = 'cancel edit';
	editLink.onclick = contextEditCancel;
	return false;
}
function contextEditCancel() {
	var editArea = document.getElementById('contextEdit');
	editArea.style.display = 'none';
	var displayArea = document.getElementById('contextDisplay');
	displayArea.style.display = 'block';
	var editLink = document.getElementById('contextEditLink');
	editLink.innerHTML = 'edit trail';
	editLink.onclick = contextEditShow;
	return false;
}
function trailEditShow() {
	var editArea = document.getElementById('trailInfoEdit');
	editArea.style.display = 'block';
	var displayArea = document.getElementById('trailInfoDisplay');
	displayArea.style.display = 'none';
	var editLink = document.getElementById('trailEditLink');
	editLink.innerHTML = 'cancel edit';
	editLink.onclick = trailEditCancel;
	initText = document.getElementById('trail-desc-initial-value').innerHTML;
	initEditor('trail-desc-wysiwyg-iframe');
	return false;
}
function trailEditCancel() {
	var editArea = document.getElementById('trailInfoEdit');
	editArea.style.display = 'none';
	var displayArea = document.getElementById('trailInfoDisplay');
	displayArea.style.display = 'block';
	var editLink = document.getElementById('trailEditLink');
	editLink.innerHTML = 'edit trail';
	editLink.onclick = trailEditShow;
	return false;
}

function aboutMeEditShow() {
	var editArea = document.getElementById('aboutMeEdit');
	editArea.style.display = 'block';
	var displayArea = document.getElementById('aboutMeDisplay');
	displayArea.style.display = 'none';
	var editLink = document.getElementById('aboutMeEditLink');
	editLink.innerHTML = 'cancel edit';
	editLink.onclick = aboutMeEditCancel;
	initText = document.getElementById('blurb-initial-value').innerHTML;
	initEditor('blurb-wysiwyg-iframe');
	return false;
}
function aboutMeEditCancel() {
	var editArea = document.getElementById('aboutMeEdit');
	editArea.style.display = 'none';
	var displayArea = document.getElementById('aboutMeDisplay');
	displayArea.style.display = 'block';
	var editLink = document.getElementById('aboutMeEditLink');
	editLink.innerHTML = 'edit user info';
	editLink.onclick = aboutMeEditShow;
	return false;
}
function addToFavorites(url,title) { 
	if (window.external) { 
		window.external.AddFavorite(url,title) 
	//} else if (window.sidebar) {
		// Firefox will only create bookmarks that open in sidebar! wtf
		//window.sidebar.addPanel(title, url, "");
	} else { 
		alert("Sorry! Your browser doesn't support this function.");
	}
}
function checkAll(type, formElement) {
	var rBoxes = formElement.getElementsByTagName('input');
	for (var i = 0; i < rBoxes.length; ++i)
		if (rBoxes[i].className == type + '-box')
			rBoxes[i].checked = true;
}
function showUrlList(type) {
	document.getElementById(type + '-list').style.display = "block";
}
function hideUrlList(type) {
	document.getElementById(type + '-list').style.display = "none";
}

function addFavorite(userId, favId, favType, favName, divId) {
	new ActionProxy().addFavorite(userId, favId, favType, favName, addFavSucceeded, addFavFailed);
	var favDiv = document.getElementById(divId);
	var str = "deleteFavorite(" + userId + ", " + favId + ", " + favType + ", '" + favName + "', '" + divId + "'); return false";
	favDiv.setAttribute('onclick', str);
	if (favDiv.innerHTML == 'add') favDiv.innerHTML = 'remove';
	else favDiv.innerHTML = 'remove from favorites';
}
function addFavSucceeded() {}
function addFavFailed() {}

function deleteFavorite(userId, favId, favType, favName, divId) {
	new ActionProxy().deleteFavorite(userId, favId, favType, deleteFavSucceeded, deleteFavFailed);
	var favDiv = document.getElementById(divId);
	var str = "addFavorite(" + userId + ", " + favId + ", " + favType + ", '" + favName + "', '" + divId + "'); return false";
	favDiv.setAttribute('onclick', str);
	if (favDiv.innerHTML == 'remove') favDiv.innerHTML = 'add';
	else favDiv.innerHTML = 'add to favorites';
}
function deleteFavSucceeded() {}
function deleteFavFailed() {}


function updateNotificationPrefs(method, evnt, cbox) {
	var value = cbox.checked;
	var statusArea = page.find('statusMessageArea');
	statusArea.innerHTML = 'Saving Preferences (please wait)';
	
	function success(response)
	{
		if (response['method'] == 'mystuff') {
			statusArea.innerHTML = 'Reloading...';
			page.refresh();
		} else {
			statusArea.innerHTML = 'Preferences Saved';
			setTimeout(function()
			{
				statusArea.innerHTML = '&nbsp;';
			},3000);
		}		
	}
	
	function failure(response)
	{
		page.alert("Error",Utils.errorToString(response),elem)					
	}

	new ActionProxy().updateNotifications(method, evnt, value, success, failure);
}

function activityUpdateFilter(form) {
	var checks = form.filters;
	var filters = new Array();

	for (var i=0; i<checks.length; i++)
		if (checks[i].checked) filters[checks[i].value] = 1;
		else filters[checks[i].value] = 0;

	function success(response) { page.refresh(); }
	function failure(response) { page.alert("Error",Utils.errorToString(response),form); }
	
	new ActionProxy().updateNotifications(filters, success, failure);
}

function scrollDivTo(divId,selIndex,totalItems) {
	div = document.getElementById(divId);
	if (div)
		div.scrollTop = div.scrollHeight * (selIndex-4) / totalItems;
}



// /var/www/trailfire.com-release/content/pages/js/tabs.js 

function tabSelect(tabDiv, tabName, category, page, limit) {
	if (!tabDiv.id)
		tabDiv = document.getElementById(tabDiv);
	showTab(tabDiv, tabName, category, page, limit);

	// "prepare" the content div
	var td = document.getElementById('tab-' + tabName);
	td.innerHTML = 'loading content...';
	
	// update the link in the tab (so you get back to the same page next time
	// you click this tab)
	var links = tabDiv.getElementsByTagName('a');
	var tabLink = links[0];
	tabLink.onclick = function() { showTab(tabDiv, tabName, category, page, limit) };

	function success(response)
	{
		td.innerHTML = ret[0];
	}
	
	function failure(response)
	{
		page.alert("Error",Utils.errorToString(response),td);
		td.innerHTML = "";
	}

	// get the tab content
	if (tabName.match(/Trails$/))
		new ActionProxy().getTrailTabContent(tabName, category, page, limit, success, failure);
	else if (tabName.match(/Users$/))
		new ActionProxy().getUserTabContent(tabName, category, page, limit, success, failure);
//	else
//		alert("I don't know what to do about that tab!");
}

function showTab(tabDiv, tabName, category, page, limit) {
	var tBar = document.getElementById('tabsetBar');
	var children = tBar.getElementsByTagName('div');

	// if tab is selected, unselect it
	for (var i = 0; i < children.length; ++i)
		if (children[i].className == 'tabsetTab tabFirstSelected')
			children[i].className = 'tabsetTab tabFirst';
		else if (children[i].className == 'tabsetTab tabSelected')
			children[i].className = 'tabsetTab';

	// hide/show appropriate content
	var tBody = document.getElementById('tabsetBody');
	children = tBody.getElementsByTagName('div');
	for (var i = 0; i < children.length; ++i)
		if (children[i].id == 'tab-' + tabName) {
			children[i].style.display = 'inline';
		}
		else if (children[i].className == 'tabsetTabContent')
			children[i].style.display = 'none';

	// make the correct tab "active"
	if (tabDiv.className == 'tabsetTab')
		tabDiv.className = "tabsetTab tabSelected";
	else if (tabDiv.className == 'tabsetTab tabFirst')
		tabDiv.className = "tabsetTab tabFirstSelected";
	
	// reset page numbers
	resetPageNumbers(tabDiv, tabName, category, page, limit);
}

function resetPageNumbers(tabDiv, tabName, category, page, limit) {
	if (tabPageCount < 2)
		return;
	// update page number links: the "prev" link
	var prev = document.getElementById('tabBarPageNumbersPrev');
	if (page > 1) {
		prev.innerHTML = '';
		var prevLink = document.createElement('a');
		prevLink.innerHTML = 'Prev';
		prevLink.href = '#';
		prevLink.setAttribute("pageNumber",  parseInt(page) - 1);
		prevLink.onclick = function() { tabSelect(tabDiv.id, tabName, category, this.getAttribute("pageNumber"), limit); return false; };
		prev.appendChild(prevLink);
	} else {
		prev.innerHTML = 'Prev';
	}

	// update page number links
	if (page <= 5) {
		pageMin = 1;
		pageMax = Math.min(10, tabPageCount);
	} else if (page + 5 > tabPageCount) {
		pageMax = tabPageCount;
		if (pageMax - 10 < 1)
			pageMin = 1;
		else
			pageMin = pageMax - 10;
	} else {
		pageMin = page - 4;
		pageMax = page + 5;
	}
	for (var currentPage = pageMin, i = 1; i <= 10; ++i, ++currentPage) {
		var pageNum = document.getElementById('pageNumber' + i);
		if (currentPage > pageMax) {
			pageNum.style.display = 'none';
			continue;
		}
		pageNum.style.display = 'inline';
		var pageLink = pageNum.firstChild;
		if (currentPage == page) {
			pageLink.innerHTML = '[' + currentPage + ']';
			pageLink.href = "";
			pageLink.onclick = "";
		} else {
			pageLink.innerHTML = currentPage;
			pageLink.href = "#";
			pageLink.setAttribute("pageNumber",  parseInt(currentPage));
			pageLink.onclick = function() { tabSelect(tabDiv.id, tabName, category, this.getAttribute("pageNumber"), limit); return false; };
		}
	}

	// update page number links: the "next" link
	var next = document.getElementById('tabBarPageNumbersNext');
	if (page < tabPageCount) { // tabPageCount is defined in a script tag in the template
		next.innerHTML = '';
		var nextLink = document.createElement('a');
		nextLink.innerHTML = 'Next';
		nextLink.href = '#';
		nextLink.setAttribute("pageNumber",  parseInt(page) + 1);
		nextLink.onclick = function() { tabSelect(tabDiv.id, tabName, category, this.getAttribute("pageNumber"), limit); return false; };
		next.appendChild(nextLink);
	} else {
		next.innerHTML = 'Next';
	}
}
tabPageCount = 0;


// /var/www/trailfire.com-release/content/pages/js/wysiwyg.js 

function getEditorDoc(elemId) {
	return document.getElementById(elemId).contentWindow.document;
}
function initEditor(iframeId) {
	var wysiwygDoc = getEditorDoc(iframeId);
	if (wysiwygDoc.designMode.toLowerCase() != 'on') {
		wysiwygDoc.designMode = 'On';

		// if you don't do this, Firefox won't let you delete or backspace
		// over any text before you insert something
		wysiwygDoc.designMode = 'Off';
		wysiwygDoc.designMode = 'On';

		// this is a FF-only command; IE doesn't use CSS by default
		if (!Controls.detectIE()) 
				wysiwygDoc.execCommand("useCSS", false, true);

		try {
			// sometimes there is an exception in ie here
			wysiwygDoc.body.style.fontFamily = 'sans-serif';
			wysiwygDoc.body.style.fontSize = '9pt';
		} catch (ex) { }
	}
	if (initText)
		setTimeout("setIFrameText('" + iframeId + "')", 250);
}
function setIFrameText(id) {
	getEditorDoc(id).body.innerHTML = initText;
	initText = '';
}
function editorCommand(editorFrameId, name, ui, data) {
	getEditorDoc(editorFrameId).execCommand(name, ui, data);
}
function toggleEditorView(containerId, editorFrameId, sourceAreaId, button) {
	var wysiwygDoc = getEditorDoc(editorFrameId);
	var wDiv = getEditorContainer(containerId, 'wysiwyg');
	var sDiv = getEditorContainer(containerId, 'source');
	var sea = document.getElementById(sourceAreaId);
	if (sDiv.style.display == 'none') {
		wDiv.style.display = 'none';
		sDiv.style.display = 'block';
		button.innerHTML = 'Editor View';
		sea.value = wysiwygDoc.body.innerHTML.replace(/<br>$/, '');
	} else {
		wDiv.style.display = 'block';
		sDiv.style.display = 'none';
		button.innerHTML = 'Source View';
		wysiwygDoc.body.innerHTML = sea.value;
	}
}
function getEditorContainer(ancestorId, which) {
	var wysiwygContainer = null;
	var className = 'commentEditor' + which.charAt(0).toUpperCase() + which.substring(1).toLowerCase() + 'View';
	var kids = document.getElementById(ancestorId).getElementsByTagName('div');
	for (var i = 0; i < kids.length; ++i)
		if (kids[i].className == className) {
			wysiwygContainer = kids[i];
			break;
		}
	return wysiwygContainer;
}
function getCommentText(baseId, commentId) {
	var editorText = '';
	var eccId = baseId + '-container';
	var cwiId = baseId + '-wysiwyg-iframe';
	var csaId = baseId + '-source-area';
	if (commentId) {
		eccId += '-' + commentId;
		cwiId += '-' + commentId;
		csaId += '-' + commentId;
	}
	var wDiv = getEditorContainer(eccId, 'wysiwyg');
	if (wDiv.style.display == 'block')
		editorText = getEditorDoc(cwiId).body.innerHTML;
	else
		editorText = document.getElementById(csaId).value;
	return editorText;
}
function clearCommentText(baseId) {
	getEditorDoc(baseId + '-wysiwyg-iframe').body.innerHTML = '';
	document.getElementById(baseId + '-source-area').value = '';
}
function changeToEdit(baseId, commentId, trailId) {
	var commentDiv;
	if (trailId)
		commentDiv = document.getElementById('trailComment' + commentId);
	else
		commentDiv = document.getElementById('userComment' + commentId);
	if (null != activeTrailEditor) {
		page.alert("But first...", "Please cancel or save your edit before editing another comment.", activeTrailEditor);
		return false;
	}
	var defaultText = document.getElementById('realCommentText' + commentId).innerHTML;
	var parentElement = commentDiv.parentNode;
	var editorDiv = createEditor(baseId, commentId, defaultText, trailId);
	activeTrailEditor = editorDiv;
	replacedDiv = commentDiv;
	parentElement.replaceChild(editorDiv, commentDiv);
}
function cancelEdit(baseId, id) {
	var editorDiv = document.getElementById(baseId + '-container-' + id);
	var parentElement = editorDiv.parentNode;
	parentElement.replaceChild(replacedDiv, editorDiv);
	activeTrailEditor = null;
}
function createEditor(baseId, id, text, trailId) {
	var editorContainerDiv = document.createElement('div');
	editorContainerDiv.id = baseId + '-container-' + id;
	editorContainerDiv.className = 'wysiwygEditor';

	var viewToggleContainer = document.createElement('div');
	viewToggleContainer.className = 'editorViewToggle';
	editorContainerDiv.appendChild(viewToggleContainer);

	var viewToggleLink = document.createElement('a');
	viewToggleLink.href = "#";
	viewToggleLink.id = baseId + "-view-toggle";
	viewToggleLink.onclick = function() { toggleEditorView(baseId + '-container-' + id, baseId + '-wysiwyg-iframe-' + id, baseId + '-source-area-' + id, this); return false; }
	viewToggleLink.innerHTML = 'Source View';
	viewToggleContainer.appendChild(viewToggleLink);

	var wysiwygView = document.createElement('div');
	wysiwygView.className = 'commentEditorWysiwygView';
	wysiwygView.style.display = 'block';
	editorContainerDiv.appendChild(wysiwygView);

	var buttonBar = document.createElement('div');
	buttonBar.className = 'wysiwygButtons';
	wysiwygView.appendChild(buttonBar);

	var boldButtonContainer = document.createElement('div');
	boldButtonContainer.className = 'wysiwygButton';
	buttonBar.appendChild(boldButtonContainer);

	var boldButton = document.createElement('a');
	boldButton.href = "#";
	boldButton.onclick = function() { editorCommand(baseId + '-wysiwyg-iframe-' + id, 'Bold', false, null); return false; }
	boldButtonContainer.appendChild(boldButton);

	var boldButtonImage = document.createElement('img');
	boldButtonImage.src = "/pages/images/editor/editor-bold.png";
	boldButton.appendChild(boldButtonImage);

	var italicButtonContainer = document.createElement('div');
	italicButtonContainer.className = 'wysiwygButton';
	buttonBar.appendChild(italicButtonContainer);

	var italicButton = document.createElement('a');
	italicButton.href = "#";
	italicButton.onclick = function() { editorCommand(baseId + '-wysiwyg-iframe-' + id, 'Italic', false, null); return false; }
	italicButtonContainer.appendChild(italicButton);

	var italicButtonImage = document.createElement('img');
	italicButtonImage.src = "/pages/images/editor/editor-italic.png";
	italicButton.appendChild(italicButtonImage);

	var underlineButtonContainer = document.createElement('div');
	underlineButtonContainer.className = 'wysiwygButton';
	buttonBar.appendChild(underlineButtonContainer);

	var underlineButton = document.createElement('a');
	underlineButton.href = "#";
	underlineButton.onclick = function() { editorCommand(baseId + '-wysiwyg-iframe-' + id, 'Underline', false, null); return false; }
	underlineButtonContainer.appendChild(underlineButton);

	var underlineButtonImage = document.createElement('img');
	underlineButtonImage.src = "/pages/images/editor/editor-underline.png";
	underlineButton.appendChild(underlineButtonImage);

	var createLinkButtonContainer = document.createElement('div');
	createLinkButtonContainer.className = 'wysiwygButton';
	buttonBar.appendChild(createLinkButtonContainer);

	var createLinkButton = document.createElement('a');
	createLinkButton.href = "#";
	createLinkButton.onclick = function() {
		var linkUrl = prompt('Enter link URL:', 'http://');
		editorCommand(baseId + '-wysiwyg-iframe-' + id, 'CreateLink', false, linkUrl);
		return false;
	}
	createLinkButtonContainer.appendChild(createLinkButton);

	var createLinkButtonImage = document.createElement('img');
	createLinkButtonImage.src = "/pages/images/editor/editor-link.png";
	createLinkButton.appendChild(createLinkButtonImage);

	var insertImageButtonContainer = document.createElement('div');
	insertImageButtonContainer.className = 'wysiwygButton';
	buttonBar.appendChild(insertImageButtonContainer);

	var insertImageButton = document.createElement('a');
	insertImageButton.href = "#";
	insertImageButton.onclick = function() {
		var imageUrl = prompt('Enter Image URL:', 'http://');
		editorCommand(baseId + '-wysiwyg-iframe-' + id, 'InsertImage', false, imageUrl);
		return false;
	}
	insertImageButtonContainer.appendChild(insertImageButton);

	var insertImageButtonImage = document.createElement('img');
	insertImageButtonImage.src = "/pages/images/editor/editor-image.png";
	insertImageButton.appendChild(insertImageButtonImage);

	var unorderedListButtonContainer = document.createElement('div');
	unorderedListButtonContainer.className = 'wysiwygButton';
	buttonBar.appendChild(unorderedListButtonContainer);

	var unorderedListButton = document.createElement('a');
	unorderedListButton.href = "#";
	unorderedListButton.onclick = function() { editorCommand(baseId + '-wysiwyg-iframe-' + id, 'InsertUnorderedList', false, null); return false; }
	unorderedListButtonContainer.appendChild(unorderedListButton);

	var unorderedListButtonImage = document.createElement('img');
	unorderedListButtonImage.src = "/pages/images/editor/editor-ul.png";
	unorderedListButton.appendChild(unorderedListButtonImage);

	var undoButtonContainer = document.createElement('div');
	undoButtonContainer.className = 'wysiwygButton';
	buttonBar.appendChild(undoButtonContainer);

	var undoButton = document.createElement('a');
	undoButton.href = "#";
	undoButton.onclick = function() { editorCommand(baseId + '-wysiwyg-iframe-' + id, 'Undo', false, null); return false; return false; }
	undoButtonContainer.appendChild(undoButton);

	var undoButtonImage = document.createElement('img');
	undoButtonImage.src = "/pages/images/editor/editor-undo.png";
	undoButton.appendChild(undoButtonImage);

	var redoButtonContainer = document.createElement('div');
	redoButtonContainer.className = 'wysiwygButton';
	buttonBar.appendChild(redoButtonContainer);

	var redoButton = document.createElement('a');
	redoButton.href = "#";
	redoButton.onclick = function() { editorCommand(baseId + '-wysiwyg-iframe-' + id, 'Redo', false, null); return false; }
	redoButtonContainer.appendChild(redoButton);

	var redoButtonImage = document.createElement('img');
	redoButtonImage.src = "/pages/images/editor/editor-redo.png";
	redoButton.appendChild(redoButtonImage);

	var editorIFrame = document.createElement('iframe');
	editorIFrame.id = baseId + "-wysiwyg-iframe-" + id;
	editorIFrame.height = "50";
	editorIFrame.width = "98%";
	buttonBar.appendChild(editorIFrame);
	initText = text.replace(/'/g, "\'");
	setTimeout("initEditor('" + baseId + "-wysiwyg-iframe-" + id + "')", 250);

	var sourceViewDiv = document.createElement('div');
	sourceViewDiv.className = 'commentEditorSourceView';
	sourceViewDiv.style.display = 'none';
	editorContainerDiv.appendChild(sourceViewDiv);

	var sourceEditorArea = document.createElement('textarea');
	sourceEditorArea.id = baseId + "-source-area-" + id;
	sourceEditorArea.cols = "90";
	sourceEditorArea.rows = "3";
	sourceViewDiv.appendChild(sourceEditorArea);

	var cancelEditButton = document.createElement('input');
	cancelEditButton.type = 'button';
	cancelEditButton.value = 'Cancel';
	cancelEditButton.onclick = function() { cancelEdit(baseId, id); }
	editorContainerDiv.appendChild(cancelEditButton);

	var saveButton = document.createElement('input');
	saveButton.type = 'button';
	saveButton.value = 'Save';
	if (trailId) {
		saveButton.onclick = function() {
			saveTrailComment(baseId, id, trailId);
			activeTrailEditor = null;
			return false;
		};
	} else {
		saveButton.onclick = function() {
			saveUserComment(id);
			activeTrailEditor = null;
			return false;
		};
	}
	editorContainerDiv.appendChild(saveButton);

	return editorContainerDiv;
}
replacedDiv = null;
activeTrailEditor = null;
initText = '';


// /var/www/trailfire.com-release/content/pages/js/trailview.js 

function editTrailTitle(editLink, activeTab, trailId) {
	dismissRenameError();
	editingTrailKey = trailId;
	var link = document.getElementById('trailTitle-' + activeTab + '-' + trailId);
	var form = document.getElementById('editForm-' + activeTab + '-' + trailId);

	editLink.style.display = 'none';
	link.style.display = 'none';
	form.style.display = 'inline';

	renameTab = activeTab;
	return false;
}
function saveTrail(form, refresh) {
	if (null == refresh)
		refresh = false;
	refreshPage = refresh;
	var fields = form.getElementsByTagName('input');
	var key;
	for (var i = 0; i < fields.length; ++i)
		if (fields[i].name == 'label')
			newTrailTitle = fields[i].value;
		else if (fields[i].name == 'trail')
			key = fields[i].value;
	trailTitleLink = document.getElementById('trailTitle-' + renameTab + '-' + key);

	// make ajax call to save trail title
	new ActionProxy().setInterestLabel(key, newTrailTitle, bind(renameS, this), bind(renameF, this));

	hideTrailForm(form, key);
	return false;
}
function hideTrailForm(form, trailId) {
	document.getElementById('editForm-' + renameTab + '-' + trailId).style.display = 'none';
	document.getElementById('statusDiv-' + renameTab + '-' + trailId).style.display = 'inline';
	return false;
}

function setTrailCategories(form) {
	var fields = form.getElementsByTagName('input');
	var trailId;
	for (var i = 0; i < fields.length; ++i)
		if (fields[i].name == 'trail') trailId = fields[i].value;
	var fields = form.getElementsByTagName('select');
	var cats = new Array();
	for (var i = 0; i < fields.length; ++i)
		if (fields[i].name == 'cat1') cats.push(fields[i].value);
		else if (fields[i].name == 'cat2') cats.push(fields[i].value);

	new ActionProxy().setTrailCategories(trailId, cats, setCategoriesSucceeded, setCategoriesFailed);
	return false;
}
function setCategoriesSucceeded(response) { 
	var trailId = response['trailId'];
	document.getElementById('categoriesSucceeded-' + trailId).style.display = 'block';
}
function setCategoriesFailed(response) {
	var trailId = response['trailId'];
	document.getElementById('categoriesFailed-' + trailId).style.display = 'block';
}
function onLoadThumbnail(imgElem)
{	
	Log.debug("onLoadThumbnail");
	var thumbElem = imgElem;
	// Get the thumbnail-box element because it contains all the children...
	if (imgElem.parentNode && imgElem.parentNode.parentNode)
	{
		thumbElem = imgElem.parentNode.parentNode;
	}
	
	var bigThumbSRC =imgElem.src + "&size=large";

	var hoverMarkup = "<br><br><img src=" + bigThumbSRC + ">";
	
	url = imgElem.attributes['tf_url'].value;
	title = imgElem.attributes['tf_title'].value;

	if (url.length > 100){
		url = url.substring(0,100);
		url += "..."
	}
	hoverMarkup += "<div class='hoverMarkUrl'>" + url + "</div>";
	
	setHoverText(thumbElem, hoverMarkup, page,true, title);
	
}
function onMouseOoutThumbnail(markId)
{
	Log.debug("onMouseOverThumbnail");
}

function moveMark(bubbleId, trailId, direction) {
	var element = document.getElementById('trailview-mark-' + bubbleId);
	var eCopy = element.cloneNode(true);
	var sCopy;

	var sibling; // the one we want to switch places with

	if (direction == 'up') {
		sibling = element.previousSibling;
		while (sibling != null && sibling.className != 'trailview-mark')
			if (sibling.previousSibling == null)
				sibling = null;
			else
				sibling = sibling.previousSibling;
		if (sibling != null)
			sCopy = sibling.cloneNode(true);
	} else if (direction == 'down') {
		sibling = element.nextSibling;
		while (sibling != null && sibling.className != 'trailview-mark')
			if (sibling.nextSibling == null)
				sibling = null;
			else
				sibling = sibling.nextSibling;
			if (sibling != null)
				sCopy = sibling.cloneNode(true);
	}

	if (sCopy != null) {
		document.importNode(eCopy, true);
		document.importNode(sCopy, true);

		var parent = element.parentNode;
		parent.replaceChild(sCopy, element);
		parent.replaceChild(eCopy, sibling);

		elementId = element.id.match(/^trailview-mark-(\d+)$/);
		siblingId = sibling.id.match(/^trailview-mark-(\d+)$/);
		elementId = elementId[1];
		siblingId = siblingId[1];

		var ar = new Array();
		if (direction == 'down') {
			ar.push(elementId);
			new ActionProxy().arrangeBubbles(ar, trailId, siblingId, moveSucceeded, moveFailed);
		} else {
			ar.push(siblingId);
			new ActionProxy().arrangeBubbles(ar, trailId, elementId, moveSucceeded, moveFailed);
		}

		element = null;
		sibling = null;
	}

	return false;
}
function moveSucceeded() { }
function moveFailed() { }
function renameS() {
	// rename this trail in all tabs
	var allAnchors = document.body.getElementsByTagName('a');
	var pattern = "^trailTitle-.*-" + editingTrailKey + "$";
	for (var i = 0; i < allAnchors.length; ++i) {
		if (allAnchors[i].id && allAnchors[i].id.match(pattern)) {
			allAnchors[i].innerHTML = newTrailTitle;
		}
	}
	document.getElementById('statusDiv-' + renameTab + "-" + editingTrailKey).style.display = 'none';
	document.getElementById('trailTitle-' + renameTab + "-" + editingTrailKey).style.display = 'inline';
	document.getElementById('editLink-' + renameTab + "-" + editingTrailKey).style.display = 'inline';
	if (refreshPage) {
		refreshPage = null;
		document.location.reload();
	}
}
function renameF() {
	document.getElementById('statusDiv-' + renameTab + "-" + editingTrailKey).style.display = 'none';
	document.getElementById('renameErrorDiv-' + renameTab + "-" + editingTrailKey).style.display = 'inline';
}
function dismissRenameError(tab, trailId) {
	if (trailId)
		editingTrailKey = trailId;
	if (null == editingTrailKey)
		return;
	if (null == tab && null != renameTab)
		tab = renameTab;
	else if (null == tab)
		return;
	document.getElementById('editForm-' + tab + '-' + editingTrailKey).style.display = 'none';
	document.getElementById('renameErrorDiv-' + tab + '-' + editingTrailKey).style.display = 'none';
	document.getElementById('statusDiv-' + tab + '-' + editingTrailKey).style.display = 'none';
	document.getElementById('trailTitle-' + tab + '-' + editingTrailKey).style.display = 'inline';
	document.getElementById('editLink-' + tab + '-' + editingTrailKey).style.display = 'inline';
}

if (!document.importNode) {
	document.importNode = function(oNode, bImportChildren) {
		if (!bImportChildren)
			throw new Error('importNode without children is not implemented.');

		var html = oNode.outerHTML;
		var tempNode = this.createElement('DIV');
		var oNew;
           
		if (oNode.tagName == 'TR') {
			html = '<TABLE><TBODY>' + html + '</TBODY></TABLE>';
			tempNode.innerHTML = html;
			oNew = tempNode.firstChild.firstChild.firstChild;
		} else {
			tempNode.innerHTML = html;
			oNew = tempNode.firstChild;
		}

		return oNew;
	};
}
function makePrivate(id, anchor) {
	var visDiv = document.getElementById('visibility-' + id);
	visDiv.innerHTML = 'private';
	visDiv.className = 'privateTrail';
	anchor.onclick = function() { makePublic(id, this); return false; };
	new ActionProxy().makePrivate(id, toggleSucceeded, toggleFailed);
}
function makePublic(id, anchor) {
	var visDiv = document.getElementById('visibility-' + id);
	visDiv.innerHTML = 'public';
	visDiv.className = 'publicTrail';
	anchor.onclick = function() { makePrivate(id, this); return false; };
	new ActionProxy().makePublic(id, toggleSucceeded, toggleFailed);
}
function toggleSucceeded() { }
function toggleFailed() { }

function setVisibility(id) {
	var se = document.getElementById('visibility-' + id);
	var visibility = se.options[se.selectedIndex].value;
	
	function success(response)
	{
		;
	}
	
	function failure(response)
	{
		page.alert("Error",Utils.errorToString(ret),se);
	}

	new ActionProxy().setInterestVisibility(id, visibility, success, failure);
}

function adminSaveCategories(trailId) {
	var se1 = document.getElementById('category-' + trailId + '-1');
	var se2 = document.getElementById('category-' + trailId + '-2');
	var cat1 = se1.options[se1.selectedIndex].value;
	var cat2 = se2.options[se2.selectedIndex].value;
	
	function success(response)
	{
		;
	}
	
	function failure(response)
	{
		page.alert("Error",Utils.errorToString(ret),se1);
	}

	new ActionProxy().setTrailCategories(trailId, [ cat1, cat2 ], success, failure);
}

function interestSubscribe(interestId, anchor) {
	anchor.onclick = function()
	{
		interestUnsubscribe(interestId, this, false); 
		return false;
	};

	//anchor.setAttribute('onclick', "return false;");
	anchor.innerHTML = 'remove from favorites';
	new ActionProxy().subscribeInterest(interestId, subscribeSucceeded, subscribeFailed);
}
function interestUnsubscribe(interestId, anchor, removeDiv) {
	if (removeDiv) {
		var trailDiv = document.getElementById('trail-' + interestId);
		var par = trailDiv.parentNode;
		par.removeChild(trailDiv);
		//var viewAllLink = document.getElementById('viewAllSubs');
		//var numSubs = viewAllLink.innerHTML.match(/subscriptions \((\d+)\)/);
		//numSubs = parseInt(numSubs[1]) - 1;
		//viewAllLink.innerHTML = viewAllLink.innerHTML.replace(/^(.*)\(\d+\)$/, "$1(" + numSubs + ")");
	} else {
		anchor.onclick = function()
		{
			interestSubscribe(interestId, this); 
			return false;
		};
		anchor.innerHTML = 'add to favorites';
	}
	new ActionProxy().unsubscribeInterestId(interestId, subscribeSucceeded, subscribeFailed);
}
function subscribeSucceeded() { }
function subscribeFailed() { }

/*
 * delete one trail from a list of trails on a page
 */
function deleteTrail(trailId,elemId) {
	page.confirm("Delete Trail","Really delete this trail?",
		function(reallyDelete)
		{
			if (reallyDelete) {
				new ActionProxy().destroyInterestId(trailId, deleteTrailSucceeded, deleteTrailFailed);
				removeTrailDiv(elemId);
			}
		}
	,page.find(elemId));
}

/*
 * delete one trail from a trailview page, reload some other page when done
 */
function deleteTrailPage(trailId,elemId,reloadUrl) {
	page.confirm("Delete Trail","Really delete this trail?",
			function(reallyDelete)
			{
				if (reallyDelete) {
					new ActionProxy().destroyInterestId(trailId, deleteTrailSucceeded, deleteTrailFailed);
					window.location.href = reloadUrl;
				}
			}
		,page.find(elemId));
	
}

function deleteTrailSucceeded() { 
	if (deleteRedirectURL != '') location.href = deleteRedirectURL;
}
function deleteTrailFailed() { }

function deleteMark(markId, trailId, redirectURL, elemId) {
	var doRedirect = false;
	deleteRedirectURL = redirectURL;
	page.confirm("Delete Mark","Really delete this mark?",
		function(reallyDelete)
		{
			if (reallyDelete) 
			{
				var siteCountElement = document.getElementById('markCount');
				var siteCount = siteCountElement.innerHTML.match(/(\d+) mark/);
				siteCount = siteCount[1] - 1;
				if (siteCount == 0) {
					var reallyReallyDelete = confirm("By deleting all the marks in a trail, you also delete the trail.  Continue?");
					if (!reallyReallyDelete)
						return;
					doDeleteTrail = true;
					doRedirect = true;
				}
				siteCountElement.innerHTML = "" + siteCount + (siteCount == 1 ? ' mark' : ' marks') + " in this trail";
				dmTrailKey = trailId;
				new ActionProxy().deleteBubble(markId, deleteMarkSucceeded, deleteMarkFailed);
				var markDiv = document.getElementById('trailview-mark-' + markId);
				markDiv.parentNode.removeChild(markDiv);
			}
		}
	,page.find(elemId));
	if (doRedirect)
		location.href = redirectURL;
}
function deleteMarkSucceeded() {
	if (dmTrailKey != null) {
		if (doDeleteTrail == true) {
			new ActionProxy().destroyInterestId(dmTrailKey, deleteTrailSucceeded, deleteTrailFailed);
			removeTrailDiv(dmTrailKey);
			location.href = deleteRedirectURL;
		}
		dmTrailKey = null;
	}
}
function deleteMarkFailed() { }

function removeTrailDiv(trailId) {
	var trailDiv = document.getElementById('trail-' + trailId);
	if (!trailDiv) { trailDiv = document.getElementById(trailId); }
	if (trailDiv) {
		var par = trailDiv.parentNode;
		par.removeChild(trailDiv);
	}
}

function highlightRating(trailId, rating) {
	starSources["" + trailId] = new Array();
	for (var i = 1; i <= 5; ++i) {
		var starImage = document.getElementById('trailRating-' + trailId + '-' + i);
		starSources["" + trailId][i] = starImage.src;
		if (i <= rating)
			starImage.src = '/pages/images/rating4q.gif';
		else
			starImage.src = '/pages/images/rating0q.gif';
	}
}
function dehighlightRating(trailId) {
	if (starSources["" + trailId] == null)
		return;
	for (var i = 1; i <= 5; ++i) {
		var starImage = document.getElementById('trailRating-' + trailId + '-' + i);
		starImage.src = starSources["" + trailId][i];
	}
}
function rateTrail(trailId, rating) {
	new ActionProxy().rateTrail(trailId, rating, rateSucceeded, rateFailed);
}
function rateSucceeded(response) {
	var trailId = response['interestId'];
	var trailRating = response['newRating'];
	var hotness = response['hotness'];
	imgNot = document.getElementById('notButton-' + trailId);
	imgHot = document.getElementById('hotButton-' + trailId);
	document.getElementById('notCount-' + trailId).innerHTML = response['negativeVotes'];
	document.getElementById('hotCount-' + trailId).innerHTML = response['positiveVotes'];
	if (trailRating == 1) {
		imgNot.src = "/pages/images/rating/not-inactive.png";
		imgHot.src = "/pages/images/rating/hot.png";
		blinkNumbers('notCount-' + trailId,7);
	} else if (trailRating == 5) {
		imgNot.src = "/pages/images/rating/not.png";
		imgHot.src = "/pages/images/rating/hot-inactive.png";
		blinkNumbers('hotCount-' + trailId,7);
	}
	
	// animate the new rating
	var width = 58;
	var w = Math.floor((trailRating-1)/4*width);
	var scale = document.getElementById('hotScale-' + trailId);
	scale.style.width = "0px";
	//document.getElementById('slider-' + trailId).style.display = 'none';
	//document.getElementById('slider-' + trailId).style.left = w;
	animateScale(trailId,0,w);
}
function animateScale(trailId,current,max) {
	//alert("animate: " + trailId + ", " + current + ", " + max);
	var scale = document.getElementById('hotScale-' + trailId);
	if (current < max) {
		scale.style.width = current;
		document.getElementById('slider-' + trailId).style.left = current + 26;
		current++;
		setTimeout('animateScale(' + trailId + ',' + current + ',' + max + ')', Math.floor(200/max));
	} else {
		scale.style.width = max;
		document.getElementById('slider-' + trailId).style.left = max + 26;
		document.getElementById('slider-' + trailId).style.display = 'block';
	}
}
function blinkNumbers(divId,current) {
	var count = document.getElementById(divId);
	if (current > 0) {
		current--;
		if (count.style.color == '#fff') { count.style.color = '#000'; } 
		else { count.style.color = '#fff'; }
		setTimeout('blinkNumbers("' + divId + '",' + current + ')', 100);
	} else { 
		count.style.color = '#000';
	}
}

function rateSucceeded2(response) {
	var trailId = response['interestId'];
	var rating = response['newRating'];
	if (response['votes'] == 1)
		document.getElementById('trailVotes-' + trailId).innerHTML = '(1 rating)';
	else
		document.getElementById('trailVotes-' + trailId).innerHTML = '(' + response['votes'] + ' ratings)';

	for (var i = 1; i <= 5; ++i) {
		if (Math.floor(rating + 0.25) >= i)
			starSources["" + trailId][i] = "/pages/images/rating4q.gif";
		else if (Math.ceil(rating) < i)
			starSources["" + trailId][i] = "/pages/images/rating0q.gif";
		else if (rating - Math.floor(rating) > 0.50)
			starSources["" + trailId][i] = "/pages/images/rating3q.gif";
		else if (rating - Math.floor(rating) > 0.25)
			starSources["" + trailId][i] = "/pages/images/rating2q.gif";
		else if (rating - Math.floor(rating) > 0)
			starSources["" + trailId][i] = "/pages/images/rating1q.gif";
	}
	dehighlightRating(trailId);
}
function rateFailed() { }

/* setTrailInfo
 * 
 * save trail info from the owner's trail edit page
 * trail name, trail categories, trail description
 */
function setTrailInfo(trailId, form, editorBaseId) {
	var trailname = form.trailName.value;
    var categories = new Array();
	var description = getCommentText(editorBaseId);

	function failure(response) { page.alert("Error",Utils.errorToString(response),form); }

	new ActionProxy().setTrailInfo(trailId, trailname, description, setTrailInfoSucceeded, failure);
}

function setTrailInfoSucceeded(ret) {
	var url = document.location.href;
	var i = url.indexOf('?mode=edit');
	if (i != -1) { 
		url = url.substr(0,i);
		document.location = url;
	} else {
		page.refresh();
	}
}

function editTrailDescription(trailId) {
	document.getElementById('trailDescription-' + trailId).style.display = 'none';
	document.getElementById('editDescription-' + trailId).style.display = 'block';
}

function setTrailDescription(trailId, form) {
	var description = form.description.value;
	
	var td = page.find('trailDescription-' + trailId);
	if (td != null)
		td.style.display = 'block';

	function success(response)
	{
		td.innerHTML = 'description saved';
		setTimeout(function() { td.innerHTML = ''; },3000);
	}	

	function failure(response)
	{
		page.alert("Error",Utils.errorToString(response),form);	
	}

	new ActionProxy().setInterestDescription(trailId, description, descriptionSucceeded, failure);
}

function setTrailAccessMode(trailId, mode, sharingForm) {
	var privateDiv = page.find('sharePrivate');
	var everyoneDiv = page.find('shareEveryone');
	var advancedDiv = page.find('shareAdvanced');
	
	if (mode == 0) privateDiv.style.display = 'block';
	else privateDiv.style.display = 'none';
	if (mode == 1 || mode == 2) everyoneDiv.style.display = 'block';
	else everyoneDiv.style.display = 'none';
	if (mode == 3) advancedDiv.style.display = 'block';
	else advancedDiv.style.display = 'none';
	
	var permission = mode;
	if (mode == 3) permission = 0;  // advanced mode means permission is private, but others are shared with
	
	// for the non-advanced modes, clear the contact sharing list
	var clearSharing = false;
	if (mode == 0 || mode == 1 || mode == 2) {
		clearSharing = true;
		if (sharingForm != null) sharingForm.reset();
	}
	
	function success(response) {}
	function failure(response) {}
	new ActionProxy().setTrailPermission(trailId, permission, clearSharing, success, failure);
}


function setTrailSharing(trailId, form) {
	var visibility = new Array();
	if (form.visibility.length)
		for (var i=0; i<form.visibility.length; i++)
			if (form.visibility[i].checked) visibility['id' + form.visibility[i].value] = 1;
			else visibility['id' + form.visibility[i].value] = 0;
	else 
		if (form.visibility.checked) visibility['id' + form.visibility.value] = 1;
		else visibility['id' + form.visibility.value] = 0;

	var editability = new Array();
	if (form.editability.length)
		for (var i=0; i<form.editability.length; i++)
			if (form.editability[i].checked) editability['id' + form.editability[i].value] = 1;
			else editability['id' + form.editability[i].value] = 0;
	else 
		if (form.editability.checked) editability['id' + form.editability.value] = 1;
		else editability['id' + form.editability.value] = 0;

	function shareSuccess(response) { page.refresh(); }
	function shareFailure(response) { page.alert("Error",Utils.errorToString(response),form); }
	new ActionProxy().setTrailSharing(trailId, visibility, editability, shareSuccess, shareFailure);

	// set permission based on checking of everyone boxes
	var permission = 0;
	if (form.everyoneVis.checked) permission = 1;
	if (form.everyoneEdit.checked) permission = 2;
	function permSuccess(response) {}
	function permFailure(response) {}
	new ActionProxy().setTrailPermission(trailId, permission, 0, permSuccess, permFailure);

}

function setTrailTags(trailId, form) {
	var tagStr = form.tags.value;
	function success(response) { page.refresh(); }
	function failure(response) { page.alert("Error",Utils.errorToString(response),form); }
	new ActionProxy().setTrailTags(trailId, tagStr, success, failure);
}

function showCommentForm(trailId) {
	page.find('commentForm-' + trailId).style.display = 'block';
}

function addTrailComment(userId, commentForm) {
	var baseId = 'main-comment';
	var comment = getCommentText(baseId);
	if (comment.length > 0 && comment != '<br>') {
		function success(ret) {
			var url = window.location.href;
			if (window.location.href.indexOf("#") != -1) {
				var i = window.location.href.indexOf("#");
				url = window.location.href.substr(0,i) + "#lastcomment";
			} else {
				url = window.location.href + "#lastcomment";
			}
			window.location.href = url;
			document.location.reload();
		}

		function failure(response) 
		{
			page.alert("Error",Utils.errorToString(response),commentForm);	
		}

		new ActionProxy().addComment(userId, comment, success, failure);
		clearCommentText(baseId);
	}
}

function saveTrailComment(baseId, commentId, trailId) 
{
	var comment = getCommentText(baseId, commentId);

	var editorDiv = page.find(baseId + '-container-' + commentId);

	function success(response)
	{
		var parentNode = editorDiv.parentNode;
		parentNode.replaceChild(replacedDiv, editorDiv);
		page.find('realCommentText' + commentId).innerHTML = comment;
	}
	
	function failure(response)
	{
		page.alert("Error",Utils.errorToString(response),editorDiv);	
	}

	new ActionProxy().editComment(trailId, commentId, comment,success,failure);
}

function deleteTrailComment(commentId, trailId) {

	function deleteConfirmed(confirmed)
	{
		function success(response)
		{
			page.refresh();		
		}
	
		function failure(response)
		{
			page.alert("Error",Utils.errorToString(response),page.find("realCommentText" + commentId))
		}

	 	if (confirmed) 
		     new ActionProxy().deleteComment(trailId, commentId, success, failure);
	}

	page.confirm("Confirm Delete Comment",
	             "Are you sure you want to delete this comment?",
				 deleteConfirmed,
	             page.find("realCommentText" + commentId));
}

function deleteAvatar(elem) 
{	
	function deleteConfirmed(confirmed)
	{
		function success(response)
		{
			var adr = page.find('avatarDeleteRow');
			adr.parentNode.removeChild(adr);
		}
		
		function failure(response)
		{
			page.alert("Error",Utils.errorToString(response),elem)			
		}

		if (confirmed)
			new ActionProxy().deleteAvatarImage(success, failure);
	}
	
	page.confirm("Really Delete?", "Are you sure you want to delete your user image?",
					deleteConfirmed,elem);
}



/*** making a slideshow trailview ***/
slideshowUrls = new Array();
slideshowImgId = null;
var img1 = null;
var img2 = null;
var href1 = null;
function createTrailSlideshow(imgId,width) {
	slideshowImgId = imgId;
	href1 = document.getElementById('slideshowHref1');
	img1 = document.getElementById(slideshowImgId + '1');
	img2 = document.getElementById(slideshowImgId + '2');
	trailSlideshow(0,-1,width);
}
function trailSlideshow(current,counter,width) {
	var steps = 12;
	
	if (counter == steps || counter == -1) {
		img2.style.left = width + 'px';
		img1.src = 'http://thumbnails.trailfire.com/thumbnail.php?size=large&url=' + slideshowUrls[current];
		href1.href = slideshowMarkUrls[current];
		current++;
		if (current >= slideshowUrls.length) current = 0;
		img2.src = 'http://thumbnails.trailfire.com/thumbnail.php?size=large&url=' + slideshowUrls[current];
		img2.style.borderWidth = '0px 0px 0px 1px';
		
		setTimeout('trailSlideshow(' + current + ',' + 0 + ',' + width + ')', 2000);
	} else {
		counter++;
		var l = Math.floor(width * (1 - (counter/steps)));
		img2.style.left = l + 'px';
		if (counter == steps) img2.style.borderWidth = '0px 0px 0px 0px';
		
		//setTimeout('trailSlideshow(' + current + ',' + counter + ',' + width + ')', 50);
		setTimeout(function() {
			trailSlideshow(current,counter,width);
			},50);
	}
}

function setTrailAllowSecret(trailId, checkbox, textDivId, inputDivId) {
	// do both copies (one in the private div, one in advanced)
	var pTextDiv = document.getElementById('privateTrailText');
	var pInputDiv = document.getElementById('privateCopyPaste');
	var pEmailDiv = document.getElementById('privateEmailLink');
	var aTextDiv = document.getElementById('advancedTrailText');
	var aInputDiv = document.getElementById('advancedCopyPaste');
	var aEmailDiv = document.getElementById('advancedEmailLink');
	var allow = checkbox.checked;

	if (checkbox.checked) {
		pTextDiv.style.color = '#666';
		pInputDiv.disabled = false;
		pInputDiv.style.background = '#eee';
		pInputDiv.style.color = '#666';
		pEmailDiv.style.display = 'block';
		aTextDiv.style.color = '#666';
		aInputDiv.disabled = false;
		aInputDiv.style.background = '#eee';
		aInputDiv.style.color = '#666';
		aEmailDiv.style.display = 'block';
	} else {
		pTextDiv.style.color = '#999';
		pInputDiv.disabled = true;
		pInputDiv.style.background = '#ccc';
		pInputDiv.style.color = '#999';
		pEmailDiv.style.display = 'none';
		aTextDiv.style.color = '#999';
		aInputDiv.disabled = true;
		aInputDiv.style.background = '#ccc';
		aInputDiv.style.color = '#999';
		aEmailDiv.style.display = 'none';
	}

	function success() {}
	function failure() {}
	new ActionProxy().setTrailAllowSecret(trailId, allow, success, failure);
}

function addUserComment(userId) {
	var comment = getCommentText('main-comment');

	if (comment.length > 0 && comment != '<br>') {
		function success(ret) {
			var url = window.location.href;
			if (window.location.href.indexOf("#") != -1) {
				var i = window.location.href.indexOf("#");
				url = window.location.href.substr(0,i) + "#lastcomment";
			} else {
				url = window.location.href + "#lastcomment";
			}
			window.location.href = url;
			document.location.reload();
		}
		function failure(response) 
		{
			page.alert("Error",Utils.errorToString(response),commentForm);	
		}
		new ActionProxy().addUserComment(userId, comment, success, failure);
	}
}

function saveUserComment(commentId) {
	var comment = getCommentText('uComment', commentId);
	var editorDiv = page.find('uComment-container-' + commentId);
	function success(response)
	{
		var parentNode = editorDiv.parentNode;
		parentNode.replaceChild(replacedDiv, editorDiv);
		page.find('realCommentText' + commentId).innerHTML = comment;
		page.find('userCommentTS' + commentId).innerHTML = response['timestamp'];
	}
	function failure(response)
	{
		page.alert("Error",Utils.errorToString(response),editorDiv);	
	}

	new ActionProxy().editUserComment(commentId, comment, success, failure);
}

function deleteUserComment(commentId, userId) {
	function deleteConfirmed(confirmed) {
		function success(response) {
			page.refresh();		
		}
		function failure(response) {
			page.alert("Error",Utils.errorToString(response),page.find("realCommentText" + commentId))
		}
	 	if (confirmed) 
		     new ActionProxy().deleteUserComment(commentId, userId, success, failure);
	}
	page.confirm("Confirm Delete Comment",
	             "Are you sure you want to delete this comment?",
				 deleteConfirmed,
	             page.find("realCommentText" + commentId));
}

function tagTrailInPlace(trailId) {
	
	function success(response) {
		if (response && response['topTags']) {
			tagsDiv = page.find('tags-' + trailId);
			var tags = response['topTags'];
			tags = tags.slice(0,5);
			tagsDiv.innerHTML = tags.join(", ") + ',';
		}
	}
	function failure() {}
	
	thisDiv = page.find('trailTagEdit-' + trailId);
	new TrailTagsDialog(browser,page,trailId,thisDiv,null,success,failure);
}


editingTrailKey = null;
dmTrailKey = null;
refreshPage = null;
starSources = new Array();

var browser = new SiteBrowserModel(PIPESTONE,PIPESTONE_VERSION,window,"http://images.trailfire.com/");
var page = new SitePageModel(PIPESTONE,PIPESTONE_VERSION,browser);

// backwards compat for proxy.js
_platform = browser;
  
browser.onPageLoad(page,window);