
//Including: CF.url.js

//Including: CF.js
/*   
Copyright (c) 2009 Crowd Factory, Ian Taylor

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

 */
   


/** @namespace 
 * The top-level CF namespace.  This contains many helper functions for 
 * common JS tasks, most of which are used by the template engine and the 
 * widget frameworks. 
 * */
var CF = {};
CF.version = "1.0";
CF.buildNum = "1,590";
CF.buildDate = "2009/10/07 16:11";

/**
 * @description 
 * Turns an iterable object into an array.
 * The classic use of this is turning the arguments variable into an actual array.
 */
CF.toArray= function (iterable) 
{
  if (!iterable) return [];
  if (iterable.toArray) return iterable.toArray();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
};

/**
 * Creates a shallow clone of an array.
 */
CF.arrayClone = function (arr)
{
	var a = [];
	jQuery.each(arr, function (i, o){a.push(o);});
	return a;
};

/**
 * @description 
 * Add the properties of one object to another object.  This can be used to make objects "inherit" from each other. 
 * @return {object} the destination object
 */
CF.extend = function(destination, source) 
{
	if(source)
	  for (var property in source)
	    destination[property] = source[property];
  return destination;
};

/**
 * @function
 * An alias for CF.extend
 * @see CF.extend
 */
CF.mixin = CF.extend; 

/**
 * Returns the keys (property names) of an object.
 */
CF.keys = function(object) 
{
    var keys = [];
    for (var property in object)
      keys.push(property);
    return keys;
};

/**
 * Returns the first object to match an iterator function 
 * @param {array} arr
 * @param {function} iterator function with two parameters index, and value
 * @param {object} context the scope to execute the iterator in.  Default of null will execute the iterator in the global scope.
 * @return {object} the object found or null if nothing found. 
 */
CF.arrayFind = function (arr, iterator, context)
{
	 var result = null;
    jQuery.each(arr, function(index, value) {
      if (iterator.call(context, index, value))
      {
        	result = value;
        	return false;
      }
    });
    return result;
};
/**
 * Returns all values for a certain property of all objects in an array. 
 * @param {array} arr an array of objects.
 * @param {string} key function with two parameters index, and value
 * @param {object} context the scope to execute the iterator in.  Default of null will execute the iterator in the global scope.
 * @return {array} an array containing the value of each property in the input array.
 */
CF.pluck = function (arr, key)
{
	return CF.collect(arr, function (i,o){return o[key];});
};

/**
 * Like CF.arrayFind, but returns all objects that were found instead of just the first one.
 * @param {array} arr
 * @param {function} iterator function with two parameters index, and value
 * @param {object} context the scope to execute the iterator in.  Default of null will execute the iterator in the global scope.
 * @return {array} An array of objects found. 
 */
CF.arrayFindAll = function(arr, iterator, context) 
{
    var results = [];
    jQuery.each(arr, function(index, value) {
      if (iterator.call(context, index, value))
        results.push(value);
    });
    return results;
};
/**
 * Like CF.arrayFindAll, but returns an array containing only objects not found.
 * @param {array} arr
 * @param {function} iterator function with two parameters index, and value
 * @param {object} context the scope to execute the iterator in.  Default of null will execute the iterator in the global scope.
 * @return {array} An array of objects for which the iterator function returned falsey. 
 */
CF.arrayReject  = function (arr, iterator, context)
{
	var results = [];
    jQuery.each(arr, function(index, value) {
      if (!iterator.call(context, index, value))
        results.push(value);
    });
    return results;
};

/**
 * Removes all falsey values from an array.  Usually used to remove nulls and undefineds from an array
 * @param {array} arr
 * @return {array} An array with only truthy values. 
 */
CF.arrayCompact = function (arr)
{
	var l = arr.length;
	var i, v;
	var newArr = [];
	for(i=0; i < l; i++)
	{
		v = arr[i];
		if(v)
		{
			newArr.push(v);
		}
	}
	return newArr;
};
/**
 * Takes an element or jQuery extended element and checks if it has the className provided.
 * Jquery 1.3 has an insanely slow implementation of hasClass.  This is radically faster.
 * On rendering a template that took ~1000ms to render, switching jQuery's hasClass to this 
 * cut the render time to ~700ms.  The time spent in this method dropped from ~330ms to ~40ms.
 */
CF.hasClass = function (node, className)
{
	if(node.jquery)
	{
		node = node.get(0);
	}
	if(!node.className)
		return false;
	var s = " "+ node.className+" ";
	return (s.indexOf(" " + className + " ") != -1);
};

/**
 * Gets the result of a function being called with that function being passed the index and value for each item in the array "list"
 * nulls and undefined values are excluded from the list.
 */
CF.collect = function (list, fx)
{
	var arr = [];
	jQuery.each(list, function (i, item){
		arr.push(fx(i, item));
	});
	return CF.arrayCompact(arr);
};

CF.first = function (list, fx)
{
	var l = list.length;
	for(var i =0; i < l; i++)
	{
		var v = fx(i, list[i]);
		if (v != null && v != undefined)
		{
			return v;
		}
	}
	return null;
};
/**
 * Returns true if the browser is IE
 */
CF.isIE = function ()
{
	return jQuery.browser.msie;
};

/** Returns true if the browser is MSIE 6 */
CF.isIE6 = function ()
{
	return CF.isIE() && parseInt(jQuery.browser.version) == 6;
};
/** Returns true if the browser is MSIE7 or IE 8 pretending to be IE 7 */
CF.isIE7 = function ()
{
	return CF.isIE() && parseInt(jQuery.browser.version) == 7;
};
/** Returns true if the browser is IE 8 */
CF.isIE8 = function ()
{
	return CF.isIE() && parseInt(jQuery.browser.version) == 8;
};
/**
 * Returns true if the browser is IE and the browser is running in IE 7 compatibility mode.
 */
CF.isIE8Compat = function ()
{
	//postMessage was added in IE8.  
	//If the browser claims to be IE7 but has postmessage, it is almost assuredly IE 8 in Compatibility mode.
	return CF.isIE7() && window.postMessage;
};
/**
 * Returns true if the browser is Safari (not Chrome).
 */
CF.isSafari = function ()
{
	var nav = navigator.userAgent.toLowerCase();
	return (nav.indexOf("safari") != -1 && nav.indexOf("chrome") == -1);
};

CF.isUndefined = function(object) {
    return typeof object == "undefined";
};

CF.isNumber = function(object) {
    return typeof object == "number";
};

CF.inList = function (thing, list)
{
	var l = list.length;
	for(var i=0; i< l; i++)
	{
		if (thing === list[i])
			return true;
	}
	return false;
};

/**
 * Logs a statement to the console at ERROR: level
 */
CF.error = function (msg, info)
{
	if(window.console)
	{
		console.log("ERROR: "+ CF._logBuilder(CF.toArray(arguments)));
	}
};
CF._logBuilder = function(args, sep)
{
	sep = sep || " arg";
	var s = "";
	s += args.shift();
	jQuery.each(args, function (i, o) 
	{
		if (typeof o == "string")
			s += " " + o;
		else 
			s += sep + i+1 + " " + CF.toJSON(o);
	});
 	return s;
};
/** Logs a statement to the console at INFO: level */
CF.log = function ()
{
	if(window.console)
	{
		console.log("INFO: "+ CF._logBuilder(CF.toArray(arguments)));
	}	
};
/** A convenience method for checking if an event was keycode 13 (the enter key). 
 * @return {function} A function that wraps the onEnterFx parameter with a check for keyCode 13.
 * */
CF.enterPressed = function(onEnterFx)
{
	var fx =  function (e)
	{
		if(e.keyCode == 13)
		{
			onEnterFx();
		}
	};
	return fx;
};
/**
 * the .focus() method from jQuery fails when the document is not yet appended to the DOM.  
 * This method allows you to specify that a field should be focused at some point in the future, and it will keep trying 
 * Until the focus() succeeds.
 */
CF.focusLater = function(jq, time)
{
	if (!time)
	{
		time = 200;
	}	
	var focusFx = function ()
	{
		try {jq.get(0).focus();
		}
		catch (e)
		{
			CF.focusLater(jq);
		}
	};
	setTimeout(focusFx, time);
	return jq;
};

CF.nl2br = function (str)
{
	return str.replace(/\n/g, '<br />');
};

/**
 * @deprecated
 * Will be removed in a future release
 * @param {Object} ns
 */
CF.makeNamespace = function(ns)
{
	var fx = function (obj, strArr, i)
	{
		if(i < (strArr.length - 1))
		{
			var str = strArr[i];
			if (!obj[str])
			{
				obj[str] = {};
			}
			i++;
			fx(obj[str], strArr, i);
		}
	};
	
	var parts = ns.split(".");
	fx(window, parts, 0);
};

/**
 * Evals a string as javascript using new Function().  This is much faster than eval(str) because 
 * new Function() optimizes for the eval'd javascript to not contain functions or other such structures.
 * The string passed in will be evaluated in a new function with the "with" operator set to the data passed in 
 * as the second parameter. 
 */
CF.evalFx = function(str, data, suppress, errTxt)
{
	if (!data)
	{
		data = {};
	}
	try{
		return new Function ('data', "with (arguments[0]) return " + str)(data);
	}
	catch (e){ 
		if(!suppress)
			CF.error((errTxt || "Error applying expression: ") +str, e.message);
	}
	return undefined;
};


/**
  * @function
  * @description
  * Quotes strings appropriately for their inclusion into JSON.
*/                     
CF.quoteString = function()
{
   var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g;
   var meta = {    // table of character substitutions
       '\b': '\\b',
       '\t': '\\t',
       '\n': '\\n',
       '\f': '\\f',
       '\r': '\\r',
       '"' : '\\"',
       '\\': '\\\\'
       };

   // Places quotes around a string, inteligently.
   // If the string contains no control characters, no quote characters, and no
   // backslash characters, then we can safely slap some quotes around it.
   // Otherwise we must also replace the offending characters with safe escape
   // sequences.
   var fx = function (string)
   {
       if (escapeable.test(string))
       {
           return '"' + string.replace(escapeable, function (a) 
           {
	           var c = meta[a];
	           if (typeof c === 'string') {
	               return c;
	           }
	           c = a.charCodeAt();
	           return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
	       }) + '"';
       }
       return '"' + string + '"';
   };
   return fx;
//We call this fx to cache escapeable and meta via closure;
}();

/**
 * Converts an object o to a JSON String representing that object.
 */
CF.toJSON = function (o)
{
    var type = typeof(o);
    if (type == "undefined")
        return "undefined";
    else if (type == "number" || type == "boolean")
        return o + "";
    else if (o === null)
        return "null";
    else if (type == "function")
    	return CF.quoteString("function()");
    // Is it a string?
    else if (type == "string") 
    {
        return CF.quoteString(o);
    }
    else if (type == "date")
    {
    	return o.getTime();
    }
    // Does it have a .toJSON function?
    else if (type == "object" && typeof o.toJSON == "function") 
        return o.toJSON();
    
    // Is it an array?
    else if (jQuery.isArray(o)) 
    {
        var ret = [];
        for (var i = 0; i < o.length; i++) {
            ret.push( CF.toJSON(o[i]) );
        }
        return "[" + ret.join(",") + "]";
    }
    else
    {
    // It's probably an object, then.
        var objParts = [];
        for (var k in o) {
            var name = '"' + k + '"';
            var val = CF.toJSON(o[k]);
            if (typeof(val) != "string") {
                // skip non-serializable values
                continue;
            }	            
            objParts.push(name + ":" + val);
        }
        return "{" + objParts.join(", ") + "}";
    }
        
};

/**
 * Creates a closure that allows the function fx to only be called once.  Subsequent calls will return undefined.
 * Good for stopping double bounce on click event handlers.
 */
CF.once = function (fx)
{
	var occur = false;
	
	var ret= function (){
		if(!occur)
		{
			occur = true;
			return fx.apply(null, CF.toArray(arguments));
		}
		return undefined;
	};
	return ret;
};

/***
 * @deprecated
 * This will be removed in a future release.
 * A dynamic script loader
 */
CF.require = function (str)
{
	CF.makeNamespace(str);
	if (!CF.evalFx(str))
	{
		if (!CF.requireUrl)
		{
			var scpts = jQuery("script");
			CF.requireUrl = CF.first(scpts, function (i, elem){
				var src = jQuery(elem).attr("src");
				if (src && src.indexOf("CF.js") != -1)
				{
					return src.split("CF.js")[0];
				}
			});
		}
		var fileUrl = (CF.requireUrl || "") + str.replace(/\./g, "/") + ".js";		
		CF.appendScript(fileUrl);		
	}
};

/**
 * Appends a script block to the head. Good for dynamic loading scripts.
 */
CF.appendScript = function (src)
{
	jQuery("head").append(CF.build("script", {type:"text/javascript", src: src}));
};

/**
* @function
* Allows you to create a new DocumentFragment node by passing in jQuery objects, Dom Nodes, or text strings, either inside of an array
* or as a single item.
*
* See: http://ejohn.org/blog/dom-documentfragments/  for more on why to use documentfragments.
*/

CF.docFrag = function (children)
{
    var addHtmlTextToFrag = function (f, text)
    {
    	//Fast case for no tags.
    	if (text.indexOf("<") == -1){
    		var t = text.replace(/&amp;/g, "&").replace(/&apos;/g, "'").replace(/&quot/g, "\"");
    		f.appendChild(document.createTextNode(t));
    		return;
    	}    		
    	var tempElem = document.createElement("div");
    	tempElem.innerHTML = text.toString();
    	var c = CF.arrayClone(tempElem.childNodes);
    	var len = c.length;
    	for(var i = 0; i < len; i++)
    	{
    	   f.appendChild(c[i]);
        }
    };
    var addFx =  function (f, item) 
    {
    	if (item.jquery)
    		f.appendChild(item.get(0));
    		//item.each(function (idx, node){f.appendChild(node)});
    	else if (item.nodeType == 1 || item.nodeType == 3 || item.nodeType == 11) //Element, Text, and DocumentFragment nodes get appended.
            f.appendChild(item);
        else 
            addHtmlTextToFrag(f, item);
    };
   
    var fx = function (children)
    {
    	var f = document.createDocumentFragment();
	    if (children) 
	    {
	        if (jQuery.isArray(children)) 
	        {
	        	var i =0;
	        	var l = children.length;
	        	for(i = 0; i < l; i++)
	        		addFx(f, fx(children[i]));
	        }
	        else 
	        {
	        	addFx(f,children);
	        }
	    }
	    return f;
    };
    return fx;
}();


/**
*  @function
*  @param {String} selector - A selector to create the node with. 
*  @param {Object} [attributes] - The attributes to add to this node
*  @param {Array|Element|String|jQuery} [children] - an object to add as a child node
*  @return {jQuery} - A single jQuery wrapped Element representing this node
*  
*  @description
*  A powerful and flexible node builder in the spirit of Scriptacuolus's Builder.node.
*  Allows you to chain together object node creation and event binding into arrays.
*  Supports CSS syntax for class, attributes, and id, in node declarations.
*  
*  Nodes created with CF.build are not part of the DOM until they are otherwise inserted, making 
*  the performance as good as possible. 
*  in the example below this is accomplished with the jQuery html function.
*  
*
*  @example
*  jQuery("#someElem").html(
*   CF.build(".hello", [
*	 CF.build("a#myLink", "Alert clicker").click(function (){alert('you clicked')}),
*	  CF.build("ol", {style:"padding:5px"}, [
*		CF.build("li.myItem", CF.build("input[type=text][value=hello]")),
*		CF.build("li.myItem", [
*		 "<a href='#'>Some inlined HTML</a>",
*		 " just text ",
*		 CF.build("span#cool", "A Span").mouseover(function (){alert("you touched the span")})
*       ])
*      ]);
* 	  ])
*	);
*	a#myLink element would alert "you clicked" when it is clicked, and the span with "A Span" would have a mouseover event.
*	This is the html that would be rendered:
*   <textarea>
*   <div class="hello">
*		<a id="myLink">Alert clicker</a>
*		<ol style="padding:5px">
*			<li class="myItem">
*				<input type="text" value="hello"></input>
*			</li>
*				<a href="#">Some inlined HTML</a> just text <span id="cool">A Span</span>
*			</li>
*		</ol>
*	</div>
*	</textarea>
*	</pre>
*/
CF.build = function ()
{
	//These selector expressions were ripped from the Sizzle code.
	//caching regEx via closure...
	var elemExpr = /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/;
	var idExpr = /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/;
	var classExpr = /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/g;
	var attrExpr = /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/g;
	
	var build = function (selector, attributes, children)
	{
		if(arguments.length == 2 && attributes!=null && (typeof attributes == "string" || jQuery.isArray(attributes) || attributes.tagName || attributes.jquery))
	    {
	        children = attributes;
	        attributes = {};
	    }
		if (!attributes)
		{
			attributes = {};
		}
		var elemTag;
		elemTag = selector.match(elemExpr);
		if(elemTag)
		{
			elemTag = elemTag[0];
		}
		var id = selector.match(idExpr);
	    var className = selector.match(classExpr);
	    var attrs = selector.match(attrExpr);
	    if (!elemTag)
	    {
	    	elemTag = "div";
	    }
	    if (id)
	    {
	    	attributes.id = id[1];
	    }
	    if (attrs)
	    {
	    	jQuery.each(attrs, function (i, attr)
	    	{
	    		var parts = attr.replace(/[\[\]]/g, "").split("=");
	    		if (parts && parts.length ==2)
	    		{
	    			attributes[parts[0]] = parts[1];
	    		}
	    	});
	    }
	    var elem;
	    if((elemTag === "input" || elemTag ==="button") && CF.isIE())
	    {
	    	//In IE, an input or button type can only be set once, at creation.  If you try to set it
	    	//after the fact, you get exceptions
	    	CF.log("Attribs", attributes);
	    	var type = attributes.type ? "type=\""+attributes.type+"\"" : ""; 
	    	//Name has to be set at creation time for radio buttons only, but doesn't hurt for any other input/buttons.
	    	var name = attributes.name ? "name=\""+attributes.name+"\"" : "";
	    	elem = jQuery(document.createElement("<"+elemTag+" " +type+" " +name+">"));
	    	delete attributes["type"];
	    	delete attributes["name"];
	    }
	    else
	    	elem = jQuery(document.createElement(elemTag));
		elem.attr(attributes);	    	
	    
	    if (className)
	    {
	       	var cls ="";
	    	jQuery.each(className, function (i, str) { cls+= str.replace(".", "") + " ";});
	    	elem.addClass(cls);
	    }    
	
	    if (children)
	    {
	    	if(children.nodeType == 11)
	    		//Already a documentfragment.
	    		elem.append(children);
	    	else
	    		elem.append(CF.docFrag(children));
	    }
	    return elem;
	};
	return build;
}();


/** @class
 * 
 * A simple, instance based event publishing and subscription class.
* Create one of these to have a target to bind custom events to.  This allows for 
* loose and lightweight handling of events between objects.
*
* @example
* var Thing1 = function ()
  {
  	 var that = {};
  	 that.events = CF.EventPublisher();
  	 that.frob = function ()
  	 {
  	 	that.events.fire("frobbed", "frob activated");
  	 }
  	 return that;
  }  
  
  var thing1 = Thing1();
 
  var frob1Key = thing1.events.listen("frobbed", function (){alert('frob1')});
  var frob2Key = thing1.events.listen("frobbed", function (){alert('frob2')});
  
  thing1.frob(); // alerts "frob1", then "frob2"
  thing1.events.unlisten(frob1Key);
  thing1.frob(); // alerts "frob2"
 
  //Create a second object  
  var Thing2 = function (thing1)
  {
  	 var that = {};
  	 that.onFrob = function (event, frobText)
  	 {
  	 	alert(frobText);
  	 }
  	 thing1.events.listen("frobbed", that.onFrob);
  }  
 
  var thing2 = Thing2(thing1);
  
  thing1.frob();  //alerts "frob2" then "frob activated";
  
  thing1.unlisten("frobbed");
  thing1.frob();  //nothing happens  
 */
CF.EventPublisher = function ()
	{
		var instanceCount = 0;
		
		var that = {};
		that.events = [];
		
		/**
		 * @description
		 * Fires an event, passing all functions listening to the event the value of "data" as their first parameter
		 * Unlistens any events that were only meant to run once after they are fired.
		 * @return Returns an array of all results of the functions fired.
		 */
  		that.fire = function (eventName)
		{
  			var retVals = [];
  			var toUnlisten = [];
  			var args = CF.toArray(arguments);
  			jQuery.each(that.events, function(i, evt)
			{
				if (!evt.name || evt.name == eventName)
				{
					CF.log("Firing event", eventName);
					retVals.push(evt.fx.apply(null, args));
				}
				if (evt.once)
				{
					toUnlisten.push(evt.key);
				}
			});
			jQuery.each(toUnlisten, function (i, key){ that.unlisten(key);});
			return retVals;
		};
		/**
		 * @description
		* Takes an event names and a function to call when the event occurs.
		* Returns the key to this event instance, which can be passed into the unlisten function.
		* if eventName is null, the listenerFx will be called for all events. 
		* @return {Number} an event instance key that can be used to unlisten from this event
		*/
		that.listen = function (eventName, listenerFx, once)
		{
			if(!once)
				once = false;
			instanceCount++;
			CF.log("Listening for event", eventName);
			that.events.push({key:instanceCount, name:eventName, fx:listenerFx, once:once});
			return instanceCount;
		};	
		
		
		/**
		 * @description
		 * removes listeners from either a single instance or from all events with that name
		*/
		that.unlisten = function (keyOrName)
		{
			var targetProp = CF.isNumber(keyOrName)? "key":"name";
			that.events = CF.arrayReject(that.events, 
				function (i, evt)
				{
					var eq = evt[targetProp] == keyOrName;
					if(eq) 
						CF.log("Unlistening event", keyOrName);
					return eq;
				}
			);
		};
		/**
		 * @description
		 * Removes all listeners to this eventListener 
		 */
		that.unlistenAll = function ()
		{
			that.events = {};
		};
		
		return that;
};


//= require <CF.js>
/**
 * 
 * @static
 * @class
 * Helper functions that provide support for some basic URL operations.
 * 
 */
CF.url = function ()
{
	var that = {};
	/** Fetches parmeters off of an url
	 * @param paramName If null or undefined, all parameters will be returned as an object.  If specified as 
	 * a string, returns the value of that parameter.
	 * @param url If null or undefined, the current location.href is used.  If set, uses the specified url as
	 * the source of the parameters.
	 */
	that.params = function (paramName, url)
	{
		if (!url)
		{
			url = location.href;
		}
		var queryParams = url.split("?");
	    if (queryParams.length > 1)
		{
	    	var paramsObj = that.fromQueryString(queryParams[1]);
	    	if(paramsObj && paramName)
	    		return paramsObj[paramName];
	    	return paramsObj;
	    }
	    if (paramName)
	    	return null;
		return {};
	};

	/**
	 * Builds an url by passing an array of path parts and an object containing the
	 * queryString parts as key/value pairs.
	 */
	that.build =  function (pathArr, qsObj)
	{
		var ps = "";
		
		var pathAdd = function (i, p){
		  p = p.toString();
			ps += p;
			if (!p.lastIndexOf("/") == p.length -1 && i < pathArr.length -1)
			{
				ps +="/";
			}
		};
		if(pathArr)
		{
			jQuery.each(pathArr, pathAdd);
		}
		return that.addParams(qsObj || {}, ps);
	};
	
	/***
	 * Adds a single key value pair to a url making certain to use the correct query parameter separation and encoding.
	 */
	that.addParam = function (k, v, url)
	{
		var o = {};
		o[k]=v;
		return that.addParams(o, url);
	};
	
	that.addParams = function(params, url)
	{
		url = url || location.href;
		var h = that.getHash(url, true);
		var parts = url.split("#");
		url = parts[0];
		var qs = that.toQueryString(params);
		url = that._stripXtra(url);
		var i = url.indexOf("?");
		if(i == -1)
			url+= "?";			
		else
			qs = "&"+qs;
		return url + qs + h;
	};
	
	/**
	 * Gets the hash part of the url or empty string if none exists.
	 */
	that.getHash = function (url, inc)
	{
		url = url || location.href;
		var parts = url.split("#");
		if (parts.length > 1)
		{
			return inc ? "#" + parts[1] : parts[1];
		}
		return "";
	};

	that.removeHash = function (url)
	{	
		url = url || location.href;
		return url.split("#")[0];
	};
	
	/**
	 * Removes one or more parameters from an url and returns the url.
	 */
	that.removeParam = function (names, url)
	{
		url = url || location.href;
		var front= url.split("?")[0];
		var params = that.params(null, url);
		names = jQuery.isArray(names) ? names : [names];
		jQuery.each(names, function (i, name){
			delete params[name];			
		});
		var res = that.addParams(params, front + that.getHash(url, true));
		return that._stripXtra(res);
	};  

	that._stripXtra = function (url)
	{
		if(!url)
			return url;
		var lst = url.charAt(url.length -1);
		if ( lst == "?" || lst == "&")
			url = url.substr(0, url.length - 1);
		return url;
	};
	
	/**
	 * Turns a queryString into a object of key/value pairs.
	 * If a key is specified more than once, the resulting value will be an array
	 * containing all values for that key.
	 */
	that.fromQueryString = function (str)
	{
		var obj = {};
		jQuery.each(str.split("&"), function (i, pair){
			var parts = pair.split("=");
			var key = parts[0];
			if(parts.length == 2)
			{
				var val = that.decUri(parts[1]);
				var oldVal = obj[key]; 
				if(oldVal)
			    {
			    	if(!jQuery.isArray(oldVal))
			    	{		    		
			    		obj[key] = [oldVal];
			    	}
			    	obj[key].push(val);
			    }
			    else
			    {
			    	obj[key] = val;
			    }
			}
		});
		return obj;
	};
	/**
	 * A version of decodeURIComponent that decodes + as space (as per the RFC).
	 * 
	 */
	that.decUri = function (str)
	{
		return decodeURIComponent(str).replace(/\+/g, " ");
	};
	
	that.friendly = function (str)
	{
		return str.toLowerCase() // change everything to lowercase
				.replace(/[_|\s]+/g, "-") // change all spaces and underscores to a hyphen
				.replace(/[^a-z0-9-]+/g, "") // remove all non-alphanumeric characters except the hyphen
				.replace(/[-]+/g, "-"); // replace multiple instances of the hyphen with a single instance
	};
	
	/**
	 * Turns an object of key value pairs into a string.
	 * If a value is an array it will be turned into multiple
	 * of the same key with different values.
	 */
	that.toQueryString= function (obj)
	{
		var parts = [];
		var keys = CF.keys(obj);
		/**
		 * @ignore
		 */
		var addFx = function (key, val)
		{
			parts.push(key + "=" + encodeURIComponent(val));
		};
		
		jQuery.each(keys, function (i, key)
		{
			var val = obj[key];
		
			if (jQuery.isArray(val))
				jQuery.each(val, function (i, v){addFx(key, v);});
			else
				addFx(key, val);
		});
	    return parts.join("&");	
	};
	return that;
}();

//Including: CF.ajax.js
CF.ajax = function ()
{
	var that = {};
	that.xd_receivers= {};
	that.seq = 1;
	that.xd_polltime=50;
	that.xd_queue=[];
	
	that.ajax = function (url, params, doneFx)
	{
		jQuery.ajax({
					dataType:"json",
					url:url,
					data:params,
					type:"POST",
					success:doneFx
				});	
	};
	that.jsonp = function (url, params, doneFx)
	{
		jQuery.ajax({
			dataType:"jsonp",
			url: url,
			data: params,
			jsonp:"jsonp",
			success:doneFx
		});			
	};
	that.formPost = function(url, params)
	{
		var inputs = [];
		jQuery.each(CF.keys(params), function (i, k) {inputs.push(CF.build("input", {type:"hidden", name:k, value:params[k]}));});
		var form = CF.build("form", {method:"POST", action:url}, inputs);
		jQuery("body").append(form);
		form.submit();
	};
	that.xd_iframe = function (url, params, doneFx)
	{	var msg = {
			seq:that.seq++,
			url:url,
			params:params
		};
		that.xd_receivers[msg.seq] = doneFx;
		if (window.postMessage)
		{
			that.waitForXdHost(function (){that.xd_host.postMessage(CF.toJSON(msg), "*");});
		}
//		else{
//		that.waitForXdHost(msg, function (v){that.xd_host.postMessage(CF.toJSON(msg), "*");});
//		
//			that.xd_queue.push(v);
//			that.xd_check();
//		}	
	};
	
	that.waitForXdHost = function (fx)
	{
		if (that.xd_host && that.xd_hostReady)
			fx();
		else
			setTimeout(function (){that.waitForXdHost(fx);}, that.xd_polltime);
	};
	
	that.xd_setup = function (hostname, path, xd_path)
	{
		jQuery(function (){
			that.xd_hostname= hostname;
			var frameName = "xd_frame_"+that.seq;
			var src = hostname + xd_path;
			//Safari has an issue where it will only set cookies on child iframes if the iframe is "active".
			//Usually, that means that the user has clicked on the frame.  We get around this by having the iframe
			//Do a POST that redirects back to the xd_host, which also makes the iframe "active".  From then on, it will
			//Set cookies normally.
			if (window.postMessage)
				jQuery(window).bind("message", that.xd_msg_handler);
			else
				setTimeout(that.xd_check_name, that.xd_polltime);			
			if (CF.isSafari())
				src = CF.url.addParam("safariFix", hostname+path+"no-op", src);
			that.xd_host_elem = CF.build("iframe", {style:"display:none", name:frameName, src:src}); 
			jQuery("body").append(that.xd_host_elem);
			that.xd_host = window.frames[frameName];
			CF.log("xd_host iframe created");
		});		
	};
	that.xd_check = function ()
	{
		var n = that.xd_host.name;
		if ( n == "ready" && that.xd_queue.length > 0)
			that.xd_host.name = "request:"+CF.toJSON(that.xd_queue.pop());
		if (n.indexOf("response:") == 0 )
		{
			var msg = n.replace("response:", "");
			that.xd_msg_dispatch(msg);
			that.xd_host.name = "ready";
			that.xd_check();
		}
	};
	that.xd_msg_dispatch = function (msg)
	{
		if(!msg.seq || !that.xd_receivers[msg.seq])
		{
			CF.error("Message with invalid sequence received", msg);
			return;
		}
		that.xd_receivers[msg.seq](msg.data);	
		delete that.xd_receivers[msg.seq]; 
	};
	that.xd_check_name = function ()
	{
		if(that.xd_last != that.xd_host.name)
			that.xd_check();
		that.setTimeout(that.xd_check_name, that.xd_polltime);
	};
	that.xd_msg_handler = function (msg)
	{
		if(msg.type != "message")
			return;
		msg = msg.originalEvent;
		if (msg.origin != that.xd_hostname) {
			CF.error("Wrong origin on XD request", data.origin, that.hostname);
			return;
		}
		if(! that.xd_hostReady && msg.data == "ready")
			that.xd_hostReady = true;
		else
		{
			that.xd_msg_dispatch(CF.evalFx(msg.data));	
		}
	};
	
	return that;
}();

//Including: CF.iframe.js
CF.iframe = {};

/**
 The count of how many frames have been used.  This needs to be incremented each time an iframe is added that uses the IframeUploadComm.js
*/
CF.iframe.frameCount = 0;

/**
 * An event publisher for iframe communication.
 */
CF.iframe.events = CF.EventPublisher();


//Including: CF.iframe.IframeUploadComm.js
//= require <CF.url.js>
/**
* @class
* A class used in included iframes to allow for communication of upload related events.
* 
* @param targetElem {Element} The form element that will be used as the upload form
* 
*/
CF.iframe.IframeUploadComm = function (targetElem)
{
	var that = {};
	var urlParams = CF.url.params();
	var eventPub;
	if (window.parent && window.parent.CF && window.parent.CF.iframe)
	{
		eventPub = window.parent.CF.iframe.events;
	}
	if (!eventPub)
	{
		CF.error("This iframe requires that it is loaded within a page on the same host, and that iframe communication can be established.");		
	}
	var iframeId = urlParams.iframeId; 
	if(!iframeId)
	{
		CF.error("No iframeId parameter was passed to the iframe_target.html page");
		return;
	};
	
	that.run = function(params, uploadElem, uploadUrl)
	{
		targetElem.empty();
		that.setRequestParams(targetElem,params);
		that.setUploadUrl(uploadUrl);
		//that.addStyles(styles);
		//IE doesn't want to append these dom elements that are passed from another window
		//Using html seems to work though
		targetElem.append(uploadElem.html());
		//targetElem.html(targetElem.html() +  uploadElem.html());
	};
	/**
	 * Updates the hidden inputs with new values.
	 */
	that.updateParams = function (params)
	{
		jQuery.each(CF.keys(params), function (i, k)
			{
				targetElem.find("input[name="+k+"]").val(params[k]);				
			});
	};
	that.setRequestParams = function (targetElem, params)
	{
		if (!params.redirect)
			params.redirect = that.getRedirLoc();
		var arr = [];
		jQuery.each(CF.keys(params), function (i, k){
			arr.push(CF.build("input", {type:"hidden", name:k, value:params[k]}));
		});
		
		targetElem.append(CF.build(".hiddenParams", arr));						
	};
//	that.addStyles = function (styles)
//	{
//		if (styles && styles.length > 0)
//		{
//			styles.each(function (i, s){
//				jQuery("head").append(jQuery(s).clone());
//			});
//		}
//	};
	that.getRedirLoc = function ()
	{
		var loc = location.href;
		loc = loc.split("?")[0];
		return CF.url.addParam("iframeId", iframeId, loc);			
	};
	that.setUploadUrl = function (url)
	{
		targetElem.attr("action", url);
	};		
	that.uploadSuccess = function ()
	{
		eventPub.fire(iframeId + "_success", that, urlParams.uid, urlParams.url);
	};
	that.uploadFail = function ()
	{
		eventPub.fire(iframeId + "_fail", that, urlParams.error_str, urlParams.error_code);
	};
	that.onSubmit = function (evt)
	{
		var results = eventPub.fire(iframeId + "_uploadStart", that);
		var ok= true;
		jQuery.each(results, function (i, r)
		{
			if (r === false)
				ok = false;
		});
		return ok;
	};
	that.iframeReady = function()
	{
		eventPub.fire(iframeId + "_ready", that);
	};	
	if (urlParams.error_code && urlParams.error_code != "0")
	{
		that.uploadFail();
	}
	else if (urlParams.uid && urlParams.url)
	{
		that.uploadSuccess();
	}
	else	
	{
		that.iframeReady();
	}
	targetElem.submit(that.onSubmit);
};

//Including: CF.RestV1.js
//= require <CF.ajax.js>

/**
 * 
 * @class
 * <p>
* The CF rest API class, Create an instance of this class to call the Crowd Factory JSON rest API.
* Into the opts parameter, place any information that you want to be passed as a parameter on every request.
* Good candidates are the subscriber, product, and topcommunity parameters that are required by most requests, and pretty which returns nicely
* Formatted json (good for debugging).
*</p>
* <p>
* The class exposes one low level api for making REST calls, the .restCall method, along with helper methods for many of the API functions.
* </p>
* 
* <p>
* The helper functions all require a completeFx parameter, which is a function to call when the result is returned.  
* All of the helper methods call wrapHandleErrors, to
* wrap your completeFx function in a closure that cleans up some of the REST error handling.  These wrapped functions will call
* your completeFx and pass it two parameters, the desired data and the error code.  The error code will test to false if the call 
* was a success.  See the usage example below to see how to handle errors.
* Each helper method presents its minimally required fields as arguments, and allows the addition of non-required parameters with the last argument,
* params.  Params are passed as a javascript object, they are never required and 
* can safely be omitted if additional parameters are not needed.
* </p>
* 
* <p>
* <em>Note</em>: to date, there are not helper methods for many of the Crowd Factory API calls.  More helper methods will be added over time.
* </p>
* 
* @example
Usage:
var myRest = CF.RestV1("http://www.mydomain.com", "rest", {product:myproduct, subscriber:mysubscriber, topcommunity:mytopcommunity, pretty:1});

var myUserGetCompleteFx = function (user, error)
{
	if(!error)
	{
		//your logic here
		alert(user.display_name);
	}
	else
	{
		//your error handling here.
		alert("Error code: "+ error.error_code);
	}
}

myRest.user_get(myUserGetCompleteFx, "someusername");


* 
*
*/

CF.RestV1 =  function (hostname, path, opts)
{
	var that = {};
	that.hostname = hostname;
	that.path = path;
	that.opts = opts;
	that.events = CF.EventPublisher();
	that.xd_path = "/xd/xd_host.html";
	
	/**
	 * @cf_nonAutoDoc
	 */
	that.restCall = function (url, completeFx, params)
	{
		params = that.safeParams(params);
		params = CF.extend(params, opts);
		that.events.fire("request_started", url, params);
		/**
		 * @cf_nonAutoDoc
		 */
		var doneFx = function (data)
		{
			completeFx(data);
			that.events.fire("request_completed", url, data);
		};
		var reqUrl = hostname + path + url;
		if(params.redirect)
		{
			CF.log("Using form redirect transport.");
			CF.ajax.formPost(reqUrl, params, doneFx);
		}
		else if(that.useXDIframe())
		{
			CF.log("Using cross domain iframe transport", CF.isIE8Compat());
			CF.ajax.xd_iframe(reqUrl, params, doneFx);
		}
		else if (that.isCrossSite() || params.jsonp)
		{
			CF.log("Using jsonp cross site transport");
			CF.ajax.jsonp(reqUrl, params, doneFx);
		}		
		else
		{
			CF.log("Using Ajax transport.");
			CF.ajax.ajax(reqUrl, params, doneFx);
		}
	};

	/**
	 * @cf_nonAutoDoc
	 */
	that.isCrossSite = function ()
	{
		return hostname != (location.protocol+"//" + location.hostname);
	};
	
	that.useXDIframe = function ()
	{
		//IE8 works great, unless it's in busted mode.
		return that.isCrossSite() && window.postMessage && !CF.isIE8Compat();
	};
	
	/**
	 * @cf_nonAutoDoc
	 */
	that.wrapHandleErrors = function (completeFx, target)
	{
		/**
		 * @cf_nonAutoDoc
		 */
		var fx=  function (data)
		{
			if (data.error_code != 0)
			{
				completeFx(null, data);
			}
			else
			{
				if (target)
				{
					data = data[target];
				}
				completeFx(data, false);
			}
		};
		return fx;
	};

	/**
	 * @cf_nonAutoDoc
	 */
	that.safeParams = function (params)
	{
		return (params || {});
	};
	/**
	 * @cf_nonAutoDoc
	 * @see <a href='http://docs.crowdfactory.com/version/5/current/rest/v1_auth_login.html'>auth/login API Documentation</a>"
	 */
	that.login = function (completeFx, username, password, params)
	{
		params = that.safeParams(params);
		params.j_username = username;
		params.j_password = password;
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("auth/login", completeFx, params);
	};
	/**
	 * @cf_nonAutoDoc
	 * @see <a href='http://docs.crowdfactory.com/version/5/current/rest/v1_auth_logout.html'>auth/logout API Documentation</a>
	 */
	that.logout = function (completeFx, params)
	{
		params = that.safeParams(params);
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("auth/logout", completeFx, params);
	};
	that.user_get = function (completeFx, user, params)
	{
		params = that.safeParams(params);
		if(user)
		{
			params.user = user;
		}
		completeFx = that.wrapHandleErrors(completeFx, "user");
		that.restCall("v1/user/get", completeFx, params);
	};
	that.entity_get = function (completeFx, entity, params)
	{
		params = that.safeParams(params);
		params.entity = entity;
		completeFx = that.wrapHandleErrors(completeFx, "ExternalEntity");
		that.restCall("v1/entity/get", completeFx, params);
	};
	that.entity_create = function (completeFx, params)
	{
		params= that.safeParams(params);
		completeFx = that.wrapHandleErrors(completeFx, "ExternalEntity");
		that.restCall("v1/entity/create", completeFx, params);
	};
	that.entity_browse = function (completeFx, params)
	{
		params= that.safeParams(params);
		completeFx= that.wrapHandleErrors(completeFx, "entities");
		that.restCall("v1/entity/browse", completeFx, params);
	};
	that.rating_entity_create = function (completeFx, entity, rating, value, params)
	{
		params = that.safeParams(params);
		params.entity = entity;
		params.rating = rating;
		params.value = value;
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("v1/rating/entity/create", completeFx, params);
	};
	that.comment_entity_create = function (completeFx, entity, body, params)
	{
		params = that.safeParams(params);
		params.entity = entity;
		params.body = body;
		if(!params.subject)
		{
			params.subject = "";
		}
		completeFx = that.wrapHandleErrors(completeFx, "comment");
		that.restCall("v1/comment/entity/create", completeFx, params);
	};
	
	that.comment_entity_get = function (completeFx, entity, params)
	{
		params = that.safeParams(params);
		params.entity = entity;
		completeFx = that.wrapHandleErrors(completeFx, "comments");
		that.restCall("v1/comment/entity/get", completeFx, params);
	};
	that.comment_user_create = function (completeFx, user, body, params)
	{
		params = that.safeParams(params);
		params.user = user;
		params.body = body;
		if(!params.subject)
		{
			params.subject = "";
		}
		completeFx = that.wrapHandleErrors(completeFx, "comment");
		that.restCall("v1/comment/user/create", completeFx, params);
	};
	that.rating_user_create = function (completeFx, user, rating, value, params)
	{
		params = that.safeParams(params);
		params.user = user;
		params.rating = rating;
		params.value = value;
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("v1/rating/user/create", completeFx, params);
	};	
	that.comment_user_get = function (completeFx, user, params)
	{
		params = that.safeParams(params);
		params.user = user;
		completeFx = that.wrapHandleErrors(completeFx, "comments");
		that.restCall("v1/comment/user/get", completeFx, params);
	};
	that.comment_board_create = function (completeFx, board, body, params)
	{
		params = that.safeParams(params);
		params.board = board;
		params.body = body;
		if(!params.subject)
		{
			params.subject = "";
		}
		completeFx = that.wrapHandleErrors(completeFx, "comment");
		that.restCall("v1/comment/board/create", completeFx, params);
	};
	that.comment_board_get = function (completeFx, board, params)
	{
		params = that.safeParams(params);
		params.board = board;
		completeFx = that.wrapHandleErrors(completeFx, "comments");
		that.restCall("v1/comment/board/get", completeFx, params);
	};
	that.query_entity_most_rated = function (completeFx, params)
	{
		params = that.safeParams(params);
		completeFx = that.wrapHandleErrors(completeFx, "entities");
		that.restCall("v1/query/entity/most_rated", completeFx, params);
	};
	that.query_entity_top_rated = function (completeFx, params)
	{
		params = that.safeParams(params);
		completeFx = that.wrapHandleErrors(completeFx, "entities");
		that.restCall("v1/query/entity/top_rated", completeFx, params);
	};
	that.query_entity_most_commented = function (completeFx, params)
	{
		params = that.safeParams(params);
		completeFx = that.wrapHandleErrors(completeFx, "entities");
		that.restCall("v1/query/entity/most_commented", completeFx, params);
	};
	that.query_entity_recently_commented = function (completeFx, params)
	{
		params = that.safeParams(params);
		completeFx = that.wrapHandleErrors(completeFx, "entities");
		that.restCall("v1/query/entity/recently_commented", completeFx, params);
	};
	that.query_entity_highest_rated = function (completeFx, params)
	{
		params = that.safeParams(params);
		completeFx = that.wrapHandleErrors(completeFx, "entities");
		that.restCall("v1/query/entity/highest_rated", completeFx, params);
	};
	that.query_entity_number_lists = function (completeFx, params)
	{
		params = that.safeParams(params);
		completeFx = that.wrapHandleErrors(completeFx, "entities");
		that.restCall("v1/query/entity/number_lists", completeFx, params);
	};
	that.query_user_most_active = function (completeFx, params)
	{
		params = that.safeParams(params);
		completeFx = that.wrapHandleErrors(completeFx, "users");
		that.restCall("v1/query/user/most_active", completeFx, params);
	};
	that.list_get = function (completeFx, category, name, params)
	{
		params = that.safeParams(params);
		params.category = category;
		params.name = name;
		completeFx = that.wrapHandleErrors(completeFx, "userlist");
		that.restCall("v1/list/get", completeFx, params);
	};	
	that.list_entity_add = function (completeFx, entity, category, name, params)
	{
		params = that.safeParams(params);
		params.entity = entity;
		params.category = category;
		params.name = name;
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("v1/list/entity/add", completeFx, params);
	};
	that.list_entity_remove = function (completeFx, entity, category, name, params)
	{
		params = that.safeParams(params);
		params.entity = entity;
		params.category = category;
		params.name = name;
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("v1/list/entity/remove", completeFx, params);
	};
	that.list_entity_exists = function (completeFx, entity, category, name, params)
	{
		params = that.safeParams(params);
		params.entity = entity;
		params.category = category;
		params.name = name;
		completeFx = that.wrapHandleErrors(completeFx, "exists");
		that.restCall("v1/list/entity/exists", completeFx, params);
	};
	that.list_create = function (completeFx, category, name, params)
	{
		params = that.safeParams(params);
		params.category = category;
		params.name = name;
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("v1/list/create", completeFx, params);
	};
	that.activityevent_create = function (completeFx, params)
	{
		params = that.safeParams(params);
		completeFx = that.wrapHandleErrors(completeFx, "ActivityEvent");
		that.restCall("v1/activityevent/create", completeFx, params);
	};
	that.activityevent_get = function (completeFx, params)
	{
		params = that.safeParams(params);
		completeFx = that.wrapHandleErrors(completeFx, "activityevents");
		that.restCall("v1/activityevent/get", completeFx, params);
	};
	that.attribute_user_get = function (completeFx, user, params)
	{
		params = that.safeParams(params);
		params.user = user;
		completeFx = that.wrapHandleErrors(completeFx, "attributes");
		that.restCall("v1/attribute/user/get", completeFx, params);
	};
	that.connection_exists = function (completeFx, to_user, category, params )
	{
		params = that.safeParams(params);
		params.to_user = to_user;
		params.category = category;
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("v1/connection/exists", completeFx, params);
	};
	that.connection_create = function (completeFx, to_user, category, params)
	{
		params = that.safeParams(params);
		params.to_user = to_user;
		params.category = category;
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("v1/connection/create", completeFx, params);
	};
	that.flag_activityevent = function (completeFx, activityevent, params)
	{
		params = that.safeParams(params);
		params.activityevent = activityevent;
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("v1/flag/activityevent", completeFx, params);
	};
	that.flag_board = function (completeFx, board, params)
	{
		params = that.safeParams(params);
		params.board = board;
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("v1/flag/board", completeFx, params);
	};
	that.flag_comment = function (completeFx, comment, params)
	{
		params = that.safeParams(params);
		params.comment = comment;
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("v1/flag/comment", completeFx, params);
	};
	that.flag_entity = function (completeFx, entity, params)
	{
		params = that.safeParams(params);
		params.entity = entity;
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("v1/flag/entity", completeFx, params);
	};
	that.flag_user = function (completeFx, user, params)
	{
		params = that.safeParams(params);
		params.user = user;
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("v1/flag/user", completeFx, params);
	};
	that.loginreg_auth = function (completeFx, token, provider, params)
	{
		params = that.safeParams(params);
		params.token = token;
		params.provider = provider;
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("v1/loginreg/auth", completeFx, params);
	};
	that.loginreg_register = function (completeFx,params)
	{
		params = that.safeParams(params);
		completeFx = that.wrapHandleErrors(completeFx);
		that.restCall("v1/loginreg/register", completeFx, params);
	};
	/**
	 * @cf_nonAutoDoc
	 * Returns the url to use for the RPX forward instance.  This is passed to RPX as
	 * token_url
	 */
	that.loginreg_rpxforward_url = function(redirect)
	{
		var params = CF.extend({redirect:redirect}, that.opts);
		return CF.url.build([that.hostname,that.path, "v1/loginreg/rpxforward"], params);
	};
	that.entity_comment_count = function (completeFx, params)
	{
		params = that.safeParams(params);
		completeFx = that.wrapHandleErrors(completeFx, "comment_count");
		that.restCall("v1/entity/comment/count", completeFx, params);	
	};
	that.syndication_create = function (completeFx, provider, category, target, url, params)
	{
		params = that.safeParams(params);
		params.provider = provider;
		params.category = category;
		params.target = target;
		params.url = CF.url.removeParam([CF.login.loginTokenNameParam, CF.login.loginProviderParam, "cf_synd_id"]);
		completeFx = that.wrapHandleErrors(completeFx, "syndication", params);
		that.restCall("v1/syndication/create", completeFx, params);
	};
	if (that.useXDIframe()) {
		CF.ajax.xd_setup(that.hostname, that.path, that.xd_path);
	}
	return that;
};


/* included: CF/modal.js*/ 


/**
 * @static
 * @class
 * A simplisitic "lightbox" type effect.
 * Can be passed a jQuery node, or a node that contains a widget to be evaluated before display.
 * If content contains an img tag it will attempt to preload the image before fully showing the image to avoid
 * jerky resizing of the modal content window.<br/>
 * 
 * You need to define a few css styles for the modal to display properly. See the example section for 
 * a starting guide.
 * 
 * @example
&lt;style&gt;
.cf_modal_bg {background-color:#FFF}
.cf_modal_closer {float:right; font-size:25px; color:#666; cursor:pointer; margin-top:10px;}
.cf_modal_header {text-align:center; height:40px;}
.cf_modal_body {padding:0 10px 10px; background-color:#000; color:#666;}
.cf_modal_load {margin:auto; width:31px; height:31px; 
                background-image:url(../../images/greyspinner.gif);}
&lt;/style&gt;

&lt;script&gt;
var content = jQuery("&lt;div&gt;&lt;img src='myimage.jpg'&gt;&lt;/img&gt;&lt;/div&gt;");
CF.modal.show(content, null, {height:400});
&lt;/script&gt;
 *
 */
CF.modal = function ()
{
	var that ={};
	
	that.events = CF.EventPublisher();

	that.opts = {opacity:0.8,fromTop:50,height:300,width:300,fadeTime:600,allowEsc:true};
	
	/**
	 * Starts showing the modal if not already shown.
	 * @param {Element, jQuery} content  The content to place into the modal.
	 * @param {Object} data The data to pass to the widget processor (only used if content is a cf_widgetLoader).
	 * @param {Object} opts The options for this modal defauls are:
	 * {opacity:0.8,fromTop:50,height:300,width:300,fadeTime:600,allowEsc:true}
	 */
	that.show = function (content, data, opts)
	{
		/**
		 * @name CF.modal#modal_show_started
		 * @event
		 * @description
		 * Fired when the show function has been called. 
		 * @param {CF.modal} that The modal object.
		 */
		that.events.fire("modal_show_started", that);
		CF.extend(that.opts, opts);
		that.useFixed = !CF.isIE6();
		that.content = jQuery(content);
		if (that.content.is(".cf_widgetLoader"))
		{
			var widg = that.content;
			widg.removeClass("cf_noprocess");
			jQuery("<div></div>").append(widg);
			var res = CF.widget.process(widg, data, widg.id);
			if(res)
			{
				res.widget.start();
				that.content = res.targetElem;
			}
		}
		if(!that.isShown)
		{
			that.isShown = true;
			var elem = CF.build(".cf_modal_elem",
					[
					 	that.modalBg = CF.build(".cf_modal_bg").hide(),
					 	that.modalBody = CF.build(".cf_modal_body",
					 			[
					 			    that.modalCloser = CF.build(".cf_modal_closer", "x").click(that.hide),
					 			 	that.modalHeader = CF.build(".cf_modal_header"),
					 			 	that.modalLoad = CF.build(".cf_modal_load"),
					 			 	that.modalContent = CF.build(".cf_modal_content").hide(),
					 			]).hide(),
					 ]).hide();
			if(!that.built)
			{
				that.modalElem = elem;
				jQuery(document.body).append(that.modalElem);
				that.built=true;
			}
			else
			{
				that.modalElem.replaceWith(elem);
				that.modalElem = elem;
			}
			that._startAppear(content);
		}
	};
	/**
	 * Hides a modal if one is shown, unbinds the listeners of esc presses and scrolling.
	 */
	that.hide = function ()
	{
		that.modalElem.hide();
		/**
		 * @name CF.modal#modal_hidden
		 * @event
		 * @description
		 * Fired when the hidden function has been called and the modal is no longer active. 
		 * @param {CF.modal} that The modal object.
		 */
		that.events.fire("modal_hidden", that);
		//that.modalElem.fadeOut(that.opts.fadeTime, that._ie6Show);
		that.isLoaded=false;
		that.isShown =false;
		jQuery(window).unbind("scroll resize", that._centerBox);	
		jQuery(document).unbind("keypress", that._handleEsc);
	};
	that._handleEsc = function (e)
	{
		if (e.keyCode == 27 && that.isShown)
			that.hide();		
	};
	that._startAppear = function (content)
	{
		that._ie6Hide();
		that.modalElem.show();
		var win = jQuery(window);
		var middle = (win.height() / 2) + win.scrollTop();
		var w = that.opts.width;
		var h = that.opts.height;
		var pos = that.useFixed? "fixed" : "absolute";
		that.modalBody.css({position:pos, "z-index":"1001", overflow:"hidden",  width:w, height:h});
		that.modalLoad.css({"margin-top":(h/3) - that.modalLoad.height()});
		that.modalBg.css({"opacity":that.opts.opacity,position:pos});
		that._centerBox();
		that.modalBody.show();
		that.modalBg.fadeIn(that.opts.fadeTime);
		that._startContentLoad();
		if(that.opts.allowEsc)
		{
			jQuery(document).keydown(that._handleEsc);
		}
		win.bind("scroll resize", that._centerBox);
	};
	that._startContentLoad = function()
	{
		var imgs = that.content.filter("img");
		if (imgs.length ==0 )
			imgs = that.content.find("img");
	
		if (imgs.length > 0)
		{
			CF.log("starting image load");
			that.img = new Image();
			that.img.onload = that._contentLoaded;
			that.img.src = imgs.attr("src");
			var imgSrc = that.img.src;
			//preloading failed if it took more than 5s.
			setTimeout(function (){
				if (that.img && (imgSrc == that.img.src) && !that.isLoaded)
				{
					that._contentLoaded();
				}
				}, 5000);
		}
		else
		{
			that._contentLoaded();
		}
	};
	that._completeLoad = function ()
	{
		that.modalLoad.hide();
		that.modalContent.slideDown(that.opts.fadeTime);
		/**
		 * @name CF.modal#modal_show_complete
		 * @event
		 * @description
		 * Fired once the modal has finished loading and fully shown its content. 
		 * @param {CF.modal} that The modal object.
		 */
		that.events.fire("modal_show_complete", that);
	};
	that._centerBox   = function ()
	{
		var win = jQuery(window);		
		var nh = Math.max(that.modalContent.height() + that.modalHeader.height(), that.opts.height);
		var nw = Math.max(that.modalContent.width(), that.opts.width);
		var top = that.useFixed ? "0" : win.scrollTop();
		var btop = that.useFixed ? that.opts.fromTop : that.opts.fromTop + win.scrollTop();
		that.modalBg.css({top:top, height:win.height(), width:win.width()});
		that.modalBody.css({top:Math.max(0, btop), marginLeft:(win.width() - nw)/2});
	};
	that._contentLoaded = function ()
	{
		if (!that.isLoaded)
		{
			that.img = null;
			that.isLoaded =true;
			that.modalContent.append(that.content);
			var nw = Math.max(that.modalContent.width(), that.opts.width);
			var nh = Math.max(that.modalContent.height() + that.modalHeader.height(), that.opts.height);
			CF.log("width: "+nw + " height" + nh);
			var nleft = (jQuery(window).width() - nw) /2;
			that.modalBody.animate({width:nw, height:nh, marginLeft:nleft},{complete:that._completeLoad});
		}
	};	
	that._ie6Hide = function()
	{
		if (CF.isIE6())
		{
			jQuery("select").each(function (i, s){
				s = jQuery(s);
				if(s.css("visibility") !="hidden")
				{
					s.attr("cf_modal", "hidden");
					s.css("visibility", "hidden");
				}
			});
		}
	};
	that._ie6Show = function ()
	{
		if (CF.isIE6())
		{
			jQuery("select[cf_modal]" ).each(function (i, s){
				s = jQuery(s);
				if(s.attr("cf_modal") == "hidden")
				{
					s.attr("cf_modal", "");
					s.css("visibility", "");
				}
			});
		}
	};
	return that;
}();

//Including: CF.login.js

//Including: CF.context.js

//Including: CF.session.js

//Including: CF.cookie.js
/**
 * @class
 * Utilities functions for manipulating cookies
 * @static
 */

CF.cookie = function ()
{
	var that = {};
	/**
	 *  Creates a cookie
	 *  @param {string} name the cookie's name 
	 *  @param {string} value the cookies's value
	 *  @param {number} days  (optional) specifies the number of days to set the cookie for. If both this and hours are unset or null, a session cookie will be created.
	 *  @param {number} hours (optional) specifies the number of hours to set the cookie for.  If both this and days are unset or null, a session cookie will be created.
	 *  @param {string} path (optional) specifies the path for the cookie.  if unset it defaults to "/"
	 *  @return {string} the text of the cookie that was added to document.cookie. 
	 */
	that.createCookie = function (name,value,days,hours,path) 
	{
	  var expires = "";
	  var time = 0;
	  if (days) {
		time += days*24*60*60*1000;
	  }
	  if(hours) {
		  time += hours*60*60*1000;
	  }
	  if (time > 0)
	  {
	    var date = new Date();
	    date.setTime(date.getTime()+time);
	    expires = "; expires="+date.toGMTString();
	  }
	  if(!path)
	  {
		  path = "/";
	  }
	  var cook =name+"="+value+expires+"; path="+path;
	  document.cookie = cook;
	  return cook;
	};

	/**
	 * Fetches the value of a cookie.
	 * @param {string} name The name of the cookie to fetch.
	 * @return {string} value The value of the cookie or null if no cookie was set. 
	 */
	that.readCookie = function (name) {
	  var nameEQ = name + "=";
	  var ca = document.cookie.split(';');
	  for(var i=0;i < ca.length;i++) {
	    var c = ca[i];
	    while (c.charAt(0)==' ') c = c.substring(1,c.length);
	    if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	  }
	  return null;
	};
	/**
	 * "erases" a cookie.  This sets the cookie's value to the empty string and set it to expire yesterday.
	 * @param name
	 */
	that.eraseCookie = function (name) 
	{
	  that.createCookie(name,"",-1);
	};
	
	return that;
}();
CF.session = {};

/**
* An object that holds all of the currently registered persistence providers
*/

/**
 * The session provider for the HTML 5 localStorage, FF2+ Safari 4+, and IE 8+
 */
CF.session.localStorageProvider = function ()
{
	var store;
	var that = {};
	that.supported = function()
	{
		if (window.localStorage || window.globalStorage)
		{
			return true;
		}
		return false;
	};
	that.start = function (fx)
	{
		var locnoport = window.location.host.split(":")[0];
		store = window.localStorage || window.globalStorage[locnoport];
		var res= store["CF_store"];
		if(res)
			fx(res);
		else
			fx(null);
	};
	that.persist = function (val)
	{
		store["CF_store"]=val;
	};
	return that;
};

/**
 * A Session provider that uses Microsoft's userData behavior to store session data.
 * Should be used with IE 6 and 7.
 */
CF.session.msUserDataProvider = function ()
{
	var that = {};
	that.start = function (fx)
	{
		//start this after document.ready.
		jQuery(function (){
			jQuery("body").get(0).style.behavior = "url('#default#userData')";
			var elem = jQuery("body").get(0);
			var ok = false;
			if (elem)
			{
				elem.load("CF_Store");
				var res = elem.getAttribute("sPersist");
				if(res)
				{
					fx(res);
					ok = true;
				}
			}
			if (!ok)
				fx(null);
		});
	};
	that.supported = function ()
	{
		return jQuery.browser.msie && !window.localStorage;
	};	
	that.persist = function (val)
	{
		var elem = jQuery("body").get(0);
		if (elem)
		{
			elem.setAttribute("sPersist", val);
			elem.save("CF_Store");
		}
	};
	return that;
};
/**
 * The session providers for browsers that have a loaded copy of google gears (All versions of chrome do by default).
 */
CF.session.gearsProvider = function ()
{
	var that = {};
	var conn;
	that.start = function (fx)
	{
		conn = google.gears.factory.create('beta.database');
		conn.open('CF_Store');
		conn.execute( 'CREATE TABLE IF NOT EXISTS CF_Store (k TEXT UNIQUE NOT NULL PRIMARY KEY, v TEXT NOT NULL)' );
		var result = conn.execute( 'SELECT v FROM CF_Store where k = ?', ['CF_Store']);
		if (result && result.isValidRow())
		{
			fx(result.field(0));			
		}else
		{
			fx(null);
		}
		result.close();
	};
	that.supported = function ()
	{
		if(window.google && google.gears)
		{
			return true;
		}
		return false;
	};	
	that.persist = function (val)
	{
		conn.execute( 'BEGIN' );
		conn.execute( 'INSERT OR REPLACE INTO CF_Store(k, v) VALUES (?, ?)', ['CF_Store',val] );
		conn.execute( 'COMMIT' );
	};
	return that;
};


/**
 * The session provider for browsers that support sqlLite (Safari 3.1+,  FF 3.1+)
 */
CF.session.sqlLiteHtml5Provider = function ()
{
	var conn;
	var that = {};
	that.supported = function()
	{
		if (window.openDatabase)
		{
			return true;
		}
		return false;
	};
	that.start = function (fx)
	{
		conn = window.openDatabase("CF_store", "1.0", "CF_Store", 1024 * 200);
		if (conn)
		{
			conn.transaction(function(conn){
				conn.executeSql( 'CREATE TABLE IF NOT EXISTS CF_Store (k TEXT UNIQUE NOT NULL PRIMARY KEY, v TEXT NOT NULL)');
				conn.executeSql( 'SELECT k,v FROM CF_Store where k=?', ["CF_store"], function (conn, result)
						{
							if (result.rows && result.rows.length > 0)
							{
								fx(result.rows.item(0).v);
							}
							else
								fx(null);						
						});
			});
		}
	};
	that.persist = function (val)
	{
		conn.transaction(function(db){
			db.executeSql( 'INSERT OR REPLACE INTO CF_Store(k, v) VALUES (?, ?)', ["CF_store",val]);
		});
	};
	that.forceAtomic = true;
	return that;
};

CF.session.providers = [CF.session.localStorageProvider(), CF.session.gearsProvider(), CF.session.sqlLiteHtml5Provider(), CF.session.msUserDataProvider()];
CF.session.currentProvider = function ()
{
	return CF.arrayFind(CF.session.providers, function (i, prov){
				return prov.supported();
	});
}();

//= require <CF.cookie.js>
/**
 * @static
 * @class
 * <p>
 *  The CF.session.instance object gives you access to client-side persisted storage.
 *  You can use this storage to store arbitrary data in order to avoid repeated fetching of the same data on every page.
 *  </p><p>
 *  Since some persistence providers require time to load, this function has an event that is called when the session has been 
 *  loaded completely.  You can either use .isLoaded and .events properties to listen for the "session_loaded" event, or just call the whenLoaded
 *  function passing your callback function, this will ensure that you didn't miss the "session_loaded" event.
 *  </p><p>
 *  The data in the session will be preserved for one session length, where a session is defined by the lifetime of a cookie set without an expiration date
 *  on the client browser.   This will usually map to one open browser window's lifespan.
 *  </p><p>
 *  In the case that the browser does not support any of the mechanisms for keeping client side persistence, the .noPersist property will be set to true.
 *  The session instance will still allow you to add and remove values, and use it as a current page memory cache, but they will not be available on subsequent pages.
 *  </p><p>
 *  This session persistence can be reset at any time by the client clearing their cookies or clearing private data.  Do not store anything in the 
 *  session that you cannot refetch later from the server.  This is meant to be used as a cache of JSON data between page loads to avoid redundant REST calls
 *  it is not a general persistence mechanism.
 *  </p><p>
 *  <em>NOTE:</em> the current maximum size of data that can be stored is 64kb.  This is the size AFTER the data has been JSON encoded.  Errors will be thrown 
 *  if more than 64kb is stored.
 *  </p>
 */

CF.session.instance = function ()
{
	var maxSize = 64 * 1024; //The IE userData's restricted zone only allows for 64kb storage.  We make that our worst case, and thus our max supported size.
	var that = {};
	var prov = CF.session.currentProvider;
	var sessObj = {};
	that.events = CF.EventPublisher();
	/**
	 * This is the function that is called when the initial load of session data is completed.
	 */
	that.loadComplete = function (obj)
	{
		if(obj)
			sessObj = CF.evalFx(obj) || {};
		else
			sessObj = {};
		
		that.isLoaded = true;
		that.events.fire("session_loaded");		
	};
	/**
	 * A convenience method that takes a callback function that will be called as soon as the session is fully loaded.
	 * If the session is already loaded the callback will be called immediately.
	 * 
	 * @param {function} fx the function that will be called once the session is configured and its data is loaded.
	 * @param {boolean} once If true, the fx will only be called on the first "context_loaded" event.
	 */
	that.whenLoaded = function (fx, once)
	{
		if(that.isLoaded)
		{
			fx();
			if (!once)
				that.events.listen("session_loaded", fx, once);
		}
		else
			that.events.listen("session_loaded", fx, once);
	};
	/**
	 * gets a value from the session by key.
	 * @param {string} key The key to fetch data from
	 */
	that.get = function (key)
	{
		return sessObj[key];		
	};

	/**
	 * adds a key value pair to the session.
	 * @param {string} key 
	 * @param {object} value A value.  Values may be any object that can be JSON Serialized.
	 */	
	that.put = function (key, value)
	{
		sessObj[key] = value;
		that.afterAction();
	};
	
	/**
	 * Some persistence providers have persistence that happens asynchronously and thus
	 * cannot be handled on the unload event.  If the provider specifies that its persists need to 
	 * be forced atomically, we will persist after every change that happens to the session object
	 * Otherwise we rely on the window unload event to persist when the page is left.
	 */
	that.afterAction = function ()
	{
		if(prov && prov.forceAtomic)
		{
			that.persist();
		}	
	};
	/**
	 * Removes a value from the session by key, returning the value.
	 * @param {string} key The object to remove from the session.
	 */
	that.remove= function (key)
	{
		var v = sessObj[key];
		sessObj[key] = null;
		that.afterAction();
		return v;
	};
	/**
	 * Removes all values from the session.
	 * Useful for logouts, etc.
	 */
	that.clear = function ()
	{
		sessObj = {};
		that.afterAction();
	};
	/**
	 * Performs the actual persistence operation.  Under normal conditions, this is called automatically 
	 * on the window object's unload event, but under certain conditions, you may want to to persist the data
	 * manually.
	 */
	that.persist = function ()
	{
		var str = CF.toJSON(sessObj);
		
		if(str.length > maxSize)
			CF.error("Error persisting session.  Too much data");
		else
		{
			if(prov)
			{
				prov.persist(str);
			}
		}					
	};
	/**
	 * Detects if there is an active client side session, by checking for the cookie.
	 */
	that.hasSession = function ()
	{
		return CF.cookie.readCookie("CF_cSess") == "a";
	};
	
	/**
	 * This function makes sure that the clientSession data is not preserved across multiple sessions.
	 * It does this by setting a small cookie. If the cookie does not exist, the client session is cleared. 
	 * This way, the session follows normal cookie-based rules for session, regardless of which Session provider is used.
	 */
	that.ensureSession = function ()
	{
		that.whenLoaded(function ()
			{
				if(prov && !that.hasSession())
				{
					that.clear();
					CF.cookie.createCookie("CF_cSess", "a");
				}
			}
		);
	};
	
	that.ensureSession();
	if(prov)
	{
		prov.start(that.loadComplete);
	}
	else
	{
		that.noPersist = true;
		CF.error("No persistence provider found.");
		that.loadComplete(null);
	}
	/**
	 * We actually persist the data to the persistence provider when the page is unloaded.  This prevents unnecessary 
	 * writes to the persistence provider.
	 */
	jQuery(window).unload(that.persist); 
	
	return that;
	
}();
//= require <CF.session.js>

/**
 * @class
 * @static
 * CF.context holds on to the current user context and the current API instance.  It stores the current user
 * on the client side whenever possible to avoid multiple trips to fetch the current user.
 * 
 * For it to complete its startup, you need to pass it a RestV1 object configured appropriately for your endpoint.
 * @see CF.RestV1 
 * 
 * @Usage 
 * var myRest =  CF.RestV1("http://www.mydomain.com", "rest", {product:myproduct, subscriber:mysubscriber, topcommunity:mytopcommunity, pretty:1}); 
 * CF.context.setApi(myRest);
 * CF.context.whenLoaded(function ()
 * 	{
 * 		//At this point, the 
 * 	    //CF.context.auth_user will be set to the current logged in user or null if there was no logged in user
 *  });
 * CF.context.api_v1.entity_get(myCompleteFx, "myEntityId");
 * 
 */
CF.context = function ()
{
	var that = {};
	var sess = CF.session.instance;
	/**
	 * @type CF.EventPublisher
	 */
	that.events = CF.EventPublisher();	
	/**
	 * @type CF.RestV1
	 */
	that.api_v1;
		
	that.whenLoaded = function (fx, once)
	{
		if (that.isLoaded)
		{
			fx(that);
			if (!once)
				that.events.listen("context_loaded", fx, once);
		}
		else
			that.events.listen("context_loaded", fx, once);
	};
	that.userFetched = function (user, error)
	{
		if(error)
			that.auth_user = null;
		else
		{
			that.auth_user = user;
			sess.put("auth_user", user);
		}
		that.loadComplete();
	};
	that.loadComplete = function ()
	{
		that.isLoaded = true;
		that.events.fire("context_loaded", that);
	};
	that.load = function ()
	{
		if (!that.api_v1)
		{
			that.waitingForApi = true;
			return;
		}
		var usr = sess.get("auth_user");
		that.auth_user = usr;
		if (!usr)
			that.reload();
		else
			that.loadComplete();
	};
	that.invalidate = function ()
	{
		that.auth_user = null;
		sess.remove("auth_user");
		that.events.fire("context_loaded");
	};
	that.reload = function (fx)
	{
		that.isLoaded = false;
		if (fx)
			that.whenLoaded(fx, true);
		that.api_v1.user_get(that.userFetched);
	};
	
	that.setApi = function (api_v1)
	{
		that.api_v1 = api_v1;
		if (that.waitingForApi)
		{
			that.waitingForApi=false;
			that.load();
		}
	};
	/**
	 * This calls to the REST API every 20 minutes to keep the session fresh.
	 * 
	 */
	that.startPinger = function ()
	{
		setTimeout(
				function() {
					if (that.api_v1)
						that.api_v1.user_get(that.pinged);
				},
				20*60*1000 
		);
	};
	that.pinged = function (user, isError)
	{
		if(isError)
			that.invalidate();  //when there's an error on a ping we need to invalidate the context.
		else
		{
			that.auth_user = user;
			that.startPinger();
		}
	};
	sess.whenLoaded(that.load);
	that.startPinger();
	return that;
}();
//= require <CF.context.js>
//= require <CF.url.js>
/**
 * @static
 * @class
 * A helper object for managing login/logout and the resulting state and events that occur along the way
 * */
CF.login = function ()
{
	var ctx = CF.context;
	var that = {};
	that.loginProviderParam="cf_provider";
	that.loginTokenNameParam="cf_token_name";
	
	that.events = CF.EventPublisher();

	that.login = function (userName, password)
	{
		ctx.api_v1.login(that.handleLogin, userName, password);
	};
	/**
	 * Manually sets the current session cookie
	 * It will only work if the rest endpoint you are requesting is the same domain as
	 * current location.href 
	 */
	that.tokenLogin = function (jsessionId, cookieName)
	{
		CF.cookie.createCookie(cookieName || "JSESSIONID", jsessionId);
		ctx.reload();
	};
	that.logout = function (afterFx)
	{
		ctx.api_v1.logout(that.handleLogout);
	};
	that.handleLogout = function (result)
	{
		/**
		 * @name CF.login#logout_success
		 * @event
		 * @description
		 * Fired when a user has been logged out of the CrowdFactory platform but before
		 * the CF.context has been invalidated.
		 */
		
		that.events.fire("logout_success");
		ctx.invalidate();
		that.logoutCleanParams();
	};
	that.handleLogin = function (result,error)
	{
		if (!error)
		{
			ctx.reload();
			/**
			 * @name CF.login#login_success
			 * @event
			 * @description
			 * Fired when a user has been logged into the CrowdFactory platform.
			 * @param jsessionid The jsessionid of the newly logged-in request.
			 */
			that.events.fire("login_success", result.jsessionid);
		}
		else
		{
			/**
			 * @name CF.login#login_fail
			 * @event
			 * @description
			 * Fired when a user has failed to login successfully
			 * @param error The error object containing the failure reason.
			 */
			that.events.fire("login_fail", error);
		}
	};
	
	/**
	 * Call this method after each page load if you want to have the library
	 * set and clear cookies when the user has logged-in or logged-out.
	 * The cookieName defaults to "CF_JSESSIONID" if unset.  
	 */
	that.processLoginJsessionIds = function (reloadPage, cookieName)
	{
		cookieName = cookieName || "CF_JSESSIONID";
		that.events.listen("login_success", function (evt, jsessionid)
		{
			CF.cookie.createCookie(cookieName, jsessionid);
			if (!that.cleanParams() && reloadPage)
				location.reload();
		});
		that.events.listen("logout_success", function (evt, jsessionid)
		{
			CF.cookie.eraseCookie(cookieName);
			if (!that.logoutCleanParams() && reloadPage)
				location.reload();
		});		
	};
	
	/**
	 * Call this method after each page load if you want to have the library 
	 * process redirects from External Authentication providers (EG: RPX).
	 * The opts param currently only has two parameters "cleanParams" and "logoutCleanParams" which if set true
	 * will cause the library to do extra redirects to remove the token passing urls
	 * that may end up on the page, either at the login phase or at the logoutPhase
	 */
	that.processRemoteAuthToken = function (opts)
	{
		opts = CF.extend({cleanParams:false}, (opts || {}));
		that._authTokenOpts = opts;
		var params = CF.url.params();
		var provider = params[that.loginProviderParam];
		if(provider)
		{
			var token = params[params[that.loginTokenNameParam]];
			loginInProg = CF.build(".cf_login_in_progress", [CF.build("h2", "We are processing your login request"), 
				                                                 CF.build("p", "Please wait...")
				                                                 ]);
			CF.context.whenLoaded(function (){ 
				if (!CF.context.auth_user)
				{
					jQuery(function (){
						CF.modal.show(loginInProg, null, {width:500});
						ctx.api_v1.loginreg_auth(that.tokenValidated, token, provider);
					});	
				}
			}, true);
		}
	};
	/**
	 * Takes a regForm that will be used when the registration flow is activated.
	 * @param {Object} form
	 */
	that.setRegForm = function (form)
	{
		that.regForm = form;
	};

	 /**
	  * Reloads the context and hides the modal on completion.  Call this after a login / registration is 
	  * complete.  
	  */
	that.hideModalOnSuccess = function(jsessionid)
	{
		//Disarm the logout on modal hide event.
		if(that.modalHiddenEvt)
		{
			CF.modal.events.unlisten(that.modalHiddenEvt);
		}
		that.events.fire("login_success", jsessionid);
		ctx.reload(function (){
				CF.modal.hide();		
		});
	};
	/**
	 * When the modal is canceled while the regform is showing, this should be called
	 * it logs the user out and after that cleans the params if required.
	 */
	that.hideModalOnCancel = function ()
	{
		that.logout();
	};
	
	/**
	 * Strips out any of the token passing parameters
	 * returns true if the params were altered.  False otherwise.
	 */
	that._cleanParams = function ()
	{
		var old = location.href;
		location.href = CF.url.removeParam([that.loginProviderParam, that.loginTokenNameParam, CF.url.params(that.loginTokenNameParam)]);
		return old != location.href;
	};
	/**
	 * Strips out any of the token passing parameters 
	 * returns true if the params were altered.  False otherwise.
	 */
	that.logoutCleanParams = function ()
	{
		if(that._authTokenOpts && that._authTokenOpts.logoutCleanParams)
			return that._cleanParams();
		return false;
	};
	/**
	 * Strips out any of the token passing parameters
	 * returns true if the params were altered.  False otherwise.
	 */
	that.cleanParams = function ()
	{
		if (that._authTokenOpts && that._authTokenOpts.cleanParams) 
			return that._cleanParams();
		return false;
	};
	
	/**
	 * Handles the loginreg/auth result, showing the reg form if necessary. 
	 * @param {Object} result
	 * @param {Object} error
	 */	
	that.tokenValidated = function (result, error)
	{
		if (error) {
			CF.modal.show(CF.build(".cf_login_in_progress", [CF.build("h2", "We were unable to log you in"), CF.build("p", "Please try again"), CF.build('a', "Close").click(CF.modal.hide)]));
		}
		else if (result.loginSuccess) {
			that.hideModalOnSuccess(result.jsessionid);
		}
		else if (result.user) {
			var params = CF.url.params();
			var provider = params[that.loginProviderParam];
			if (provider) {
				that.showRegForm({
					user: result.user,
					requirePass: false,
					provider:provider
				}, result.jsessionid);
			}
		}
	};
	/**
	 * Shows a registration form in a modal.   Will show the form set with 
	 * CF.login.setRegForm or if unset, will construct a new CF.widget.RegForm instance.
	 * @param {Object} data The data to pass to the form widget
	 */
	that.showRegForm = function(data, jsessionid){
		if (data.user)
		{
			CF.context.auth_user = data.user;
		}
		CF.modal.show(that.regForm || "<div class='cf_widgetLoader' widgettype='CF.widget.RegForm' options='{syndicate:true}'></div>", data);
		//If the user closes the modal, they should be logged out immediately.  We will have to disable this event when they 
		//complete the form successfully.
		that.modalHiddenEvt = CF.modal.events.listen("modal_hidden", that.hideModalOnCancel, true);
		CF.widget.registry.listenType("CF.widget.RegForm", "regform_complete", function (){
			that.hideModalOnSuccess(jsessionid);
		});
	};
	return that;
}();

//Including: CF.template.js
/** @class */
CF.template = {};

//TODO: refactor all cf_tag processing to a plugin architecture to enable 
//dynamic adding of tags.
CF.template.Engine = function ()
{
	var that = {};
	
	that.getDefaultLoading = function ()
	{
		return CF.build(".cf_loading", "Loading...");
	};
	
	that.showLoadingMessage = function (targetElem, template, widgetLoadingMsg)
	{
		var loadingMsg = template.find(".cf_loading:first").remove();
		if(!loadingMsg || loadingMsg.length == 0)
	    {
	    	if (widgetLoadingMsg)
	    	{
	    		loadingMsg = widgetLoadingMsg;
	    	}
	    	else
	    	{
	    		loadingMsg = that.getDefaultLoading();
	    	}
		}
		targetElem.html(loadingMsg);
	};
	
	that._applyData = function (str, data)
	{
		if (str.indexOf("[%") == -1)
		{
			return str;
		}
		
		var len = str.length;
		var startExprPos = -1;
		var newStr = "";
		for (var i = 0; i < len; i++) 
        {
            var c = str.charAt(i);
            var peek = "";
            if ((i + 1) < len)
            {
            	peek = str.charAt(i+1);
            }
            
            var reset= false;
            
            if (c == '[' && peek == "%" )
            {
             	if (startExprPos !=  -1)
             		CF.error("Template: Cannot nest [% statements ");
                startExprPos = i;             	
            }
            else if (c == '%' && peek == "]")
         	{
         		var evalStr = str.substring(startExprPos+2, i);
         		var evaled = CF.evalFx(evalStr, data);
         		if(evaled !== null && evaled !== undefined)
         		{
         			newStr += evaled;             		
         		}
         		reset = true;
         		i++;
         	}    
	        if (startExprPos == -1)
	        {
	            newStr += c;
	        }
	        if (reset)
	        {
	        	startExprPos = -1;	
	        }
        }
        return newStr;
	};
	
	
    /**
    * Returns a jQuery wrapped node that has the rendered template DOM tree.
    * A possible speed enhancement... Stop using CF.build in walkDom and do the jQuery wrapping here instead of at 
    * each step in the walkDom rendering process.  Anything that uses the template engine wouldn't be able to tell the difference
    * 
    */
    
	that.render = function (template, data, isNotWidget)
	{
		that._idOffset = 0;
		that.subWidgets = [];
		var toRen = template;
		if (!isNotWidget)
		{
			if(template.jquery)
				template = template.get(0);
			toRen = template.childNodes;
		}
		return that._walkDom(data, toRen, true, false, true);
	};
	
	that.startSubWidgets = function ()
	{
		jQuery.each(that.subWidgets, 
		function(i, obj)
		{
			obj.widget.start();
		});
		return that.subWidgets;
	};
	
	that._processIf = function (ifNode, nodeType, nAttrs, data, inLoop)
	{
		var binding = nAttrs.binding;
		if (binding)
		{
			var val = CF.evalFx(binding, data, true);
			if(val)
			{
				var assign = nAttrs.assign === 'true';
				if (assign)
				{
					data = val;
				}				
				var nonElse = ifNode.clone();
				nonElse.children().remove(".cf_else");
				return that._walkDom(data, nonElse, false, inLoop, false, true);
			}
			else
			{
				var elseNode = CF.arrayFind(ifNode.get(0).childNodes, function (i, n){
					return CF.hasClass(n, "cf_else");
				});
				if (elseNode)
				{
					return that._walkDom(data, elseNode, false, inLoop, false);
				}
			}
		}
		else
		{
			CF.error("No binding attribute on cf_if node");
		}
		return null;
	};
	
	that._processChoice = function (choiceNode, nodeType, nAttrs, data, inLoop)
	{
		var binding = nAttrs.binding;
		if (binding)
		{
			var val = CF.evalFx(binding, data, true);
			var res = CF.first(choiceNode.find(".cf_condition"), function (i, node) 
			{
					var jqn = jQuery(node);
					var walknode = false;
					var hitone = false;
					
					var eq = jqn.attr("eq");
					var eq_s = jqn.attr("eq_s");
					var lt = jqn.attr("lt");
					var gt = jqn.attr("gt");
					if(eq !== undefined)
					{
						hitone = true;
						eq = CF.evalFx(eq, data, true);
						if(eq === val)
							walknode = true;							
					}
					if(eq_s !== undefined)
					{
						hitone = true;
						if(eq_s === val)
							walknode = true;							
					}	
					if(lt !== undefined)
					{
						hitone = true;
						lt = CF.evalFx(lt, data, true);
						if(val < lt)
							walknode = true;							
					}
					if(gt !== undefined)
					{
						hitone = true;
						gt = CF.evalFx(gt, data, true);
						if(val > gt)
							walknode = true;							
					}
					if(!hitone)
					{
						CF.error("No eq, eq_s, lt, or gt attribute specified for cf_condition node");
					}
					if (walknode)
						return that._walkDom(data, node, false, inLoop, false);
					return undefined;
				});
			if (res) return res;
			
			var otherwise = choiceNode.find(".cf_otherwise");
			if(otherwise && otherwise.length > 0)
			{
				return that._walkDom(data, otherwise.get(0), false, inLoop, false);
			}			
		}
		else
		{
			CF.error("No binding attribute on choice ");
		}
		return null;
	};
	
	that._processLoop = function (forNode, nodeType, nAttrs, data, inLoop)
	{
		var items = [];
		var binding = nAttrs.binding;
		if(binding)
		{
			var list = CF.evalFx(binding, data, true);
			if(!list)
			{
				CF.log("For loop binding failed or was undefined. Using empty array instead.", binding);
				list = [];
			}
			//Convenience binding to list when singular item bound.
			if (!jQuery.isArray(list))
			{
				list = [list];
			}
			
			var listNode = forNode.children(".cf_item");
			if(listNode.length == 0)
			{
				CF.error("For loop has no child node cf_item");
			}
			else
			{
				//Handle empty list
				if(list.length == 0)
				{
					var emptyItem = forNode.children(".cf_item_empty");
					if (emptyItem.length > 0)
					{
						items.push(that._walkDom(data, emptyItem, false, true, true));
					}	
				}
				else
				{
					var sep = forNode.children(".cf_item_sep");
					var last = forNode.children(".cf_item_last");
					var alt = forNode.children(".cf_item_alt");
					listNode = listNode.get(0);
					sep = sep.length == 0 ? null : sep.get(0);
					last = last.length == 0 ? null : last.get(0);
					alt = alt.length == 0 ? null : alt.get(0);
					var l = list.length;
					for(var i =0; i < l ; i++)
					{
						var d = {index:i, length: l, item:list[i], parent:data, idOffset:that._idOffset};
						if (alt && i % 2 == 1)
						{
							items.push(that._walkDom(d, alt.cloneNode(true), false, true, true));
						}
						else
						{
							items.push(that._walkDom(d, listNode.cloneNode(true), false, true, true));
						}
						//process separator node
						if(sep && i < l-1)
						{
							items.push(that._walkDom(d, sep.cloneNode(true), false, true, true));
						}
						//Process last node only on last 
						if(last && i == l-1)
						{
							items.push(that._walkDom(d, last.cloneNode(true), false, true, true));
						}
					}
				}
			}		
		}
		else
		{
			CF.error("No binding attribute on for loop");
		}
		//For loops render tags by default.
	   var tagRen = nAttrs.rendertag === undefined ? true : nAttrs.rendertag === 'true';  		
	   return that._makeNode(forNode, nodeType, nAttrs, items, inLoop, tagRen); 

	};
	
	/**
	 * IE 6, 7, and IE 8 when in IE 7 mode are stupid and don't have a real way to get node attributes, 
	 * and they stuff a bunch of crap into the attributes array.
	 * This method uses the IE proprietary outerHTML property to and then parses the attributes out of the outerHTML string.
	 * Using this instead of iterating through all the IE junk in the attributes array to determine what was a real attribute
	 * cut 1/3 of the time off rendering in IE 7.
	 * Amazing.
	 */
	that._ieOuterHTMLAttribParse = function (node, data)
	{
		
		var firstPart = node.outerHTML.split(">")[0].replace("<", "");
		//CF.log("OuterHTML: ", node.outerHTML);
		var len = firstPart.length;
		var nameStart = 0;
		var valStart = 0;
		var sep= "";
		var name = "";
		var result = {};
		var afterEq = false;
		for (var i = 0; i <= len; i++) 
        {
		    var c = firstPart.charAt(i);
            if (!afterEq)
            {
            	if(c == "=")
            	{
	            	afterEq = true;
	            	name = firstPart.substring(nameStart, i).replace(/\s/g, "");
            	}
            	else if(c == " ")
            	{
            	   nameStart = i;
            	}
            }
            else 
            {
            	if(!sep)
            	{
	            	if(c == "'" || c == '"')
	            	{
	            		sep = c;
	            		valStart = i+1;
	            	}
            		else
	            	{
            			sep = " ";
            			valStart = i;
	            	}
            	}
            	else if(c == sep || i == len)
            	{
            		append = false;
            		val = firstPart.substring(valStart, i);
            		nameStart = i + 1;
            		if (name.indexOf("cf_") == 0)
        			{
        				name = name.replace("cf_", "");
        			}
            		//CF.log("outerhtml attrib found", name, val);
            		result[name] = that._applyData(val, data);
            		sep = "";
            		afterEq = false;
            	}
            }
        }
		//HACK.  The outerhtml code above replaces & with &amp; which causes things to evaluate improperly.
		//Binding is especially prone to this.  TODO: figure out a way to solve this problem generically.
		if (result.binding)
		{
			result.binding = node['binding'];
		}
		return result;
    };
	
	
	that._getAttributes = function (node, data)
	{
		if(CF.isIE6() || CF.isIE7())
		{
			return that._ieOuterHTMLAttribParse(node, data);
		}
		var result= {};
		jQuery.each(node.attributes, function (i, attrib) {
			var name = attrib.name;
			if (name.indexOf("cf_") == 0)
			{
				name = name.replace("cf_", "");
			}
			var val = attrib.value;
			if(val && typeof val == "string")
			{
			  val = that._applyData(val, data);
	          result[name] = val;
			}
		});
		return result;
		
	};
	
	/**
	 * Returns either a document fragment or a jQuery wrapped element node
	 */
	that._makeNode = function (node, nodeType, nAttrs, nodeChildren, inLoop, tagRen)
	{
	   //rationalize the id node if in a loop.
	   if(tagRen && nodeType && nAttrs)
	   {
		   if (inLoop && nAttrs.id)
		   {
			   that._idOffset++;
			   nAttrs.id += "_" + that._idOffset;
		   }
		   //TODO: at this point, it might be possible clone the template node and then set the attrs 
		   //and append the nodeChildren as a document fragment. Instead of creating 
		   //a new node with CF.build.  Would this perhaps be faster, less prone to IE errors?
		   //Needs thorough investigation.
//		   if (node && node.cloneNode)
//			   return jQuery(node.cloneNode(false)).attr(nAttrs).append(CF.docFrag(nodeChildren));
		   return CF.build(nodeType, nAttrs, nodeChildren);	
	   }
	   return nodeChildren;	
	};
	
/**
 * The main recursive method of the template rendering engine.  This looks at an individual dom node in the template
 * applys any data expansions then calls itself for all child nodes, once all children have been processed, it rendered object.  
 * 
 * It may return a jQuery wrapped element node, a DocumentFragment node, or a string, depending on what is passed in.
 * The topNode passed in needs to always be an Element node, and because of this, the completed output will always be a jQuery wrapped Element node.
 * 
 */
	that._walkDom = function (data, node, isTop, inLoop, tagRen, ignoreTag)
	{
		var jqNode = null;
		var childNodes = [];
		var tagName = null;
		var hasAttributes = false;
		var nAttrs = {};
				
		//The valid inputs for node are :
		//DocumentFragment, TextNode, Element, jQuery wrapped element, array of nodes, NodeList?
		if(node.nodeType == 3)
		{	//Text node.  just run replace on the value and return the string representation.
			return that._applyData(node.data, data); 		
		}
		else if(node.jquery)
		{
			jqNode = node;
			node = jqNode.get(0);
		 	childNodes = node.childNodes;
			hasAttributes = true;
		}
		else if (node.nodeType == 11)
		{
			childNodes = node.childNodes;
		}
		else if (node.tagName)
		{//Element node.
			childNodes = node.childNodes;
			hasAttributes = true;
		}
		else if (jQuery.isArray(node))
		{
			childNodes = node;
		}
		//NodeList is hard to detect so it's the last in the list.
		else if(node.item)
		{
			childNodes = node;
		}
		
		if (hasAttributes)
		{
			if (!jqNode)
			{
				jqNode = jQuery(node);
			}
			if (CF.hasClass(node, "cf_noprocess"))
			{
				//The noprocess directive tells us that the code is meant to be passed through with 
				//no expansions or evaluation of the template.  This is useful when you want to write part of your widget
				//to be hidden and evaluated later on (IE: to keep in in the dom).
				//Basically, we can just return this node and be done.
				return jqNode.clone();
			}
			var nodeType = node.tagName.toLowerCase();
			//This sets up all of the attributes with any expansions that they need
			nAttrs = that._getAttributes(node, data);    
			
			//We want to allow the individual node's rendertag attribute to override whatever was passed in as the tagRen value.
			if (nAttrs.rendertag != undefined)
			{
				tagRen = nAttrs.rendertag === 'true'; 
			}
			
			if(!ignoreTag)
			{
				//This handles the case where there is a subwidget
				if (CF.hasClass(node, "cf_widgetLoader"))
				{
					CF.log("Found nested widget: " + jqNode.attr("widgettype"), node.className);
					var engineRes = CF.widget.process(jqNode, data, nAttrs.id);
					if (engineRes)
					{
						that.subWidgets.push(CF.extend({}, engineRes));
						return engineRes.targetElem;
					}
				}
				else if (CF.hasClass(node,"cf_for"))
				{
					return that._processLoop(jqNode,nodeType, nAttrs, data, inLoop); 
				}
				else if (CF.hasClass(node, "cf_choice"))
				{
					return that._processChoice(jqNode, nodeType, nAttrs, data, inLoop);
				}
				else if (CF.hasClass(node, "cf_if"))
				{
					return that._processIf(jqNode, nodeType, nAttrs, data, inLoop);
				}
			}
		}
		
	   //This is the case of standard node children
		var len = childNodes.length;
		var resArr= [];
		for(var i=0; i< len; i++)
		{
			var n = childNodes[i];
			resArr.push(that._walkDom(data, n, false, inLoop, true));
		}
	   return that._makeNode(node, nodeType, nAttrs, CF.docFrag(resArr), inLoop, tagRen); 
	};
	
	return that;
};

//Including: CF.date.js

/*
Date Format 1.1
(c) 2007 Steven Levithan <stevenlevithan.com>
MIT license
With code by Scott Trenda (Z and o flags, and enhanced brevity)

http://blog.stevenlevithan.com/archives/date-time-format
*/

/*** dateFormat
Accepts a date, a mask, or a date and a mask.
Returns a formatted version of the given date.
The date defaults to the current date/time.
The mask defaults ``"ddd mmm d yyyy HH:MM:ss"``.

*/

/**
 * @function
 */
CF.dateFormat = function () {
	var	token        = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloZ]|"[^"]*"|'[^']*'/g;
	var timezone     = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g;
	var timezoneClip = /[^-+\dA-Z]/g;
	var pad = function (value, length) {
		value = String(value);
		length = parseInt(length) || 2;
		while (value.length < length)
			value = "0" + value;
		return value;
	};

// Regexes and supporting functions are cached through closure
return function (date, mask) {
	// Treat the first argument as a mask if it doesn't contain any numbers
	if (
		arguments.length == 1 &&
		(typeof date == "string" || date instanceof String) &&
		!/\d/.test(date)
	) {
		mask = date;
		date = undefined;
	}

	date = date ? new Date(date) : new Date();
	if (isNaN(date))
		throw "invalid date";

	var dF = CF.dateFormat;
	mask   = String(dF.masks[mask] || mask || dF.masks["default"]);

	var	d = date.getDate(),
		D = date.getDay(),
		m = date.getMonth(),
		y = date.getFullYear(),
		H = date.getHours(),
		M = date.getMinutes(),
		s = date.getSeconds(),
		L = date.getMilliseconds(),
		o = date.getTimezoneOffset(),
		flags = {
			d:    d,
			dd:   pad(d),
			ddd:  dF.i18n.dayNames[D],
			dddd: dF.i18n.dayNames[D + 7],
			m:    m + 1,
			mm:   pad(m + 1),
			mmm:  dF.i18n.monthNames[m],
			mmmm: dF.i18n.monthNames[m + 12],
			yy:   String(y).slice(2),
			yyyy: y,
			h:    H % 12 || 12,
			hh:   pad(H % 12 || 12),
			H:    H,
			HH:   pad(H),
			M:    M,
			MM:   pad(M),
			s:    s,
			ss:   pad(s),
			l:    pad(L, 3),
			L:    pad(L > 99 ? Math.round(L / 10) : L),
			t:    H < 12 ? "a"  : "p",
			tt:   H < 12 ? "am" : "pm",
			T:    H < 12 ? "A"  : "P",
			TT:   H < 12 ? "AM" : "PM",
			Z:    (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
			o:    (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4)
		};

	return mask.replace(token, function ($0) {
		return ($0 in flags) ? flags[$0] : $0.slice(1, $0.length - 1);
	});
};
}();

//Some common format strings
CF.dateFormat.masks = {
"default":       "ddd mmm d yyyy HH:MM:ss",
shortDate:       "m/d/yy",
mediumDate:      "mmm d, yyyy",
longDate:        "mmmm d, yyyy",
fullDate:        "dddd, mmmm d, yyyy",
shortTime:       "h:MM TT",
mediumTime:      "h:MM:ss TT",
longTime:        "h:MM:ss TT Z",
isoDate:         "yyyy-mm-dd",
isoTime:         "HH:MM:ss",
isoDateTime:     "yyyy-mm-dd'T'HH:MM:ss",
isoFullDateTime: "yyyy-mm-dd'T'HH:MM:ss.lo"
};

//Internationalization strings
CF.dateFormat.i18n = {
dayNames: [
	"Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat",
	"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
],
monthNames: [
	"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
	"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
]
};


/**
 * Creates a "friendly" date 
 * If the date was less than a moment ago it displays "Moments ago"
 * If the date was less than an hour ago it displays "n minuntes ago"
 * If the date was today it displays "Today @ h:MM TT"
 * If the date was yesterday it displays "Yesterday @ h:MM TT"
 * Otherwise it displays "mmm d, yyyy @ h:MM TT"
 * 
 * This relies on the date format code above to format the dates. 
 * 
 * @param date a javascript date object.  Easily created from a Crowd Factory REST date by using new Date() 
 * Eg for an external entity ent:
 * 
 * var txtDate = CF.friendlyDate(new Date(ent.created));
 */
CF.friendlyDate = function(date)
{
	if(typeof date != 'date')
	{
		date = new Date(date);
	}
	var now = new Date();
	var hourAgo = new Date(now.getTime() - (60*60*1000));
	if (date.getTime() > hourAgo.getTime())
	{
	  var n = (now.getTime() - date.getTime()) / (60 * 1000);
	  if (n <= 1)
	  {
	    return "Moments ago";
	  }
	  return "" + Math.ceil(n) + " minutes ago"; 
	}
	if (date.toDateString() == now.toDateString())
	{
		return "Today @ " + CF.dateFormat(date, "h:MM TT"); 
	}
	var yesterday = new Date();
	yesterday.setDate(now.getDate()-1);
	if (yesterday.toDateString() == date.toDateString())
	{
		return "Yesterday @ " + CF.dateFormat(date, "h:MM TT");
	}
	return CF.dateFormat(date, "mmm d, yyyy @ h:MM TT");	
};

//Including: CF.validate.js
/**
 * @static
 * @class
 * An extremely simplistic form validator
 * Pass it an element in the run method and it will validate any fields that have 
 * cf_validate class, and display their validator_msg attribute if they've failed.
 */
CF.validate = function(){
	
	var that = {};
	
	that.events = CF.EventPublisher();
	
	that.hideAll = function (elem)
	{
		elem.find(".cf_validate_fail").removeClass("cf_validate_fail");
		elem.find(".cf_validate_success").removeClass("cf_validate_success");
		elem.find(".cf_validate_fail_msg").remove();
	};
	
	that.validators = {
		required:function (v){return jQuery.trim(v).length > 0;},
		email:/\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/,
		alpha:/^[a-zA-Z]+$/,
		url:/^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i,
		urlSafe:/^[a-zA-Z0-9_\-,\.]+$/,
		imageUrl:function (v) {return that.validators.url(v) && v.split('?')[0].toLowerCase().match(/.(\.jpg|\.gif|\.png|.jpeg)/);},
		number:function (v){return !isNaN(v);}
	};
	
	that.addValidator = function (name, v)
	{
		that.validators[name] = v;
	};
	
	that.showError = function (e, msg)
	{
		if(msg)
		{
			var elem = CF.build(".cf_validate_fail_msg", msg).hide();
			e.after(elem);
			elem.fadeIn();		
		}
		e.addClass("cf_validate_fail");		
	};
	that.test = function (v, val)
	{
		if (typeof v == "function")
		{
			return v(val);
		}
		return v.test(val);
	};	
	that.run = function (elem)
	{
		that.hideAll(elem);
		var elems = elem.find(".cf_validate");
		if(elem.is(".cf_validate"))
			elems = elems.andSelf();
		var failElems = CF.collect(elems, function (i, e)
		{
			e = jQuery(e);
			var req = CF.hasClass(e, "cf_required");
			var val = e.attr("validator");
			var msg = e.attr("validator_msg");
			var v = that.validators[val];
			if(!v && val)
			{
				v = CF.evalFx(val, null, false, "Error evaluating validator expression");
			}
			req= (req || (val == 'required'));
			var value = e.val();
			var hasValue = that.validators.required(value); 
			if (hasValue || req) {			
				var result = that.test(v, value) && hasValue;
				if(!result)
			 	{
					that.events.fire("validate_elem_failed", e, msg);
					that.showError(e, msg);
					return {elem:e, msg:msg};
				}	
			}
			that.events.fire("validate_elem_success", e);
			e.addClass("cf_validate_success");			
			return null;						
		});
		if(failElems.length == 0)
		{
			that.events.fire("validate_success", elem, failElems);
			return true;
		}
		that.events.fire("validate_failed", elem);
		return false;
	};
	return that;
}();

//Including: CF.widget.AddToCrowdList.js

//Including: CF.widget.js
//= require <CF.template.js>
/** 
 *  @namespace
 *  The top level widget namespace which holds some function for starting the rendering of widgets.
 */
CF.widget = {};

/**
*  @static
*  @description
*  Starts all top-level widgets.  A widget is an element of class cf_widgetLoader that has a widgettype attribute.
*  Calling this function is the simplest way to start all the widgets on a page.
*  It will wait until the DOM is fully loaded and the CF.context has been properly initialized before 
*  attempting to start widgets.
*/
CF.widget.startAll =  function (autoReload)
{
	jQuery(function()
	{	
		CF.context.whenLoaded(function ()
		{
		//Not really sure if this is faster than doing two queries and comparing the results...
		var topLevels = jQuery(".cf_widgetLoader:not(.cf_widgetLoader .cf_widgetLoader):not(.cf_noprocess .cf_widgetLoader)");
			CF.widget.start(topLevels);
			if (autoReload)
			{
				CF.context.events.listen("context_loaded", CF.widget.reloadAll);
			}
		});
	});
};

/**
 * Reloads all the top level widgets
 * Very useful to call after a login/logout where you don't want to reload the page.
 */
CF.widget.reloadAll = function ()
{
	//Not really sure if this is faster than doing two queries and comparing the results...
	var topLevels = jQuery(".cf_widget:not(.cf_widget .cf_widget)");
	jQuery.each(topLevels, function (i, elem)
	{
		var id = elem.id;
		if(id)
		{
			var w = CF.widget.registry.getId(id);
			if(w)
			{
				w.widget.reload();			
			}
		}	
	});
};


CF.widget.start = function (el)
{
	jQuery(el).each(function (i, widg)
	{
		widg = jQuery(widg);
		if (!CF.hasClass(widg, "cf_noprocess")) {
			var res = CF.widget.process(widg, null, widg.attr('id'));
			if (res) {
				res.widget.start();
			}
		}	
	});	
};

/**
 * @static
 * @description
 * Processes a widget, creating a new template Engine, reading in any data parameters,
 * initializing the widget, and adding it to the widget registry.  
 * Returns an object containing 
 * Does not call widget.start().
 * @returns {widget:widgetObj, id:id, type:widgetType, targetElem:targetElem} or null on failure.
 * @param {Element} template the template element node to render
 * @param {Object} data An object to use as the data parameter for the evaluation of the widget.
 * @param {String} id An id to use as the seed for the new widget id.
 */
CF.widget.process = 
function(template, data, id)
{
	id = CF.widget.registry.nextWidgetId(id);
	template = jQuery(template);
	var tNode = template.get(0);
	var tClass = "cf_widget "+ tNode.className.replace("cf_widgetLoader", "");
	var targetElem =CF.build(tNode.tagName, {className:tClass, id:id});
	template = template.replaceWith(targetElem);
	
	var widgetType = template.attr("widgettype");
	if(!widgetType)
		widgetType = "CF.widget.SimpleWidget";
	targetElem.attr("widgettype", widgetType);
	var templateEngine = CF.template.Engine();
	if (widgetType)
	{
		var widgetFx = CF.evalFx(widgetType);
		if (!widgetFx)
		{
			CF.error("The widgetType " + widgetType + "is not defined");
			return null;
		}
		
		var options = template.attr("options");
		if(options)
		{
			options = CF.evalFx(options, data);
		}
		var attr_data = template.attr("data");
	    if (attr_data)
	    {
	    	data = CF.evalFx(attr_data, data);
	    }
	    //Grab the template to use from some other node by id
	    var altTemplateId = template.attr("alt_template_id");
	    if (altTemplateId)
	    {
	    	var alt = jQuery("#"+altTemplateId);
	    	if (alt.length > 0 )
	    	{
	    		template.html(alt.html());
	    	}	    	
	    }
		//put up the loading widget.
		var widgetObj = widgetFx.apply(null, [targetElem, template, templateEngine, data, options]);
		if (!widgetObj)
			return null;
		//Apply the default template if no template was specified.
		if (jQuery.trim(template.html()).length == 0)
		{
			var defTmpl = widgetObj.getDefaultTemplateBody();
			if (defTmpl)
			{ 
				template.html(defTmpl);
			}
		}
		templateEngine.showLoadingMessage(targetElem, template, widgetObj.loadingMessage);		    
		CF.widget.registry.add(widgetType, id, widgetObj);
	    return {widget:widgetObj, id:id, type:widgetType, targetElem:targetElem};		
	}
	else
	{
		CF.error("Missing widget type on top level widget");
	}
	return null;
};

/**
 * 
 * @static
 * @class
 * The widget registry keeps track of all the rendered widgets.  It allows you get access to widgets by id or type.
* Once you have a widget instance, you can call reload or any other methods on them, and you can subscribe to events using 
* each widget's .events property, or if you are interested in all widgets of a given type you can use the listenType method to subscribe to 
* events on all widgets of that type (widgets that are not created yet will be automatically bound when they are added to the registry).
* 
* Events: widget_added - Fired after a widget is added to the registry.
*         widget_removed - Fired when a widget is removed from the registry.\
*/
CF.widget.registry = function ()
{
	var that = {};
	that._widgets = [];
	/**@type CF.EventPublisher*/
	that.events = CF.EventPublisher();
	that.listenedEvents = [];
	
	//This is the count of how many widgets have been allocated that will be used by the Template and Widget engines when they create a new
	//widget and need to give it a unique id.
	var count = 0;
	
	/**
	 * Adds a widget to the registry.  This should be called inside of the CF.widget.process function
	 */
	that.add = function (type, id, widget)
	{
		var o ={type:type, id:id, widget:widget};
		that._widgets.push(o);
		jQuery.each(that.listenedEvents, function (i, e) 
				{
					if(e.type === type && widget && widget.events)
					{
						widget.events.listen(e.event, e.fx);
					}
				});
		/**
		 * @name CF.widget.registry#widget_added
		 * @event
		 * @description
		 * Fired when a widget is added to the registry
		 * @param {Object} o The widget that was added to the registry
		 */
		that.events.fire("widget_added", o);
	};
	that._getCurrentCount = function ()
	{
		return count;
	};
	that._usedIds = {};
	/**
	 * Gets the next unique widget id by incrementing.
	 */
	that.nextWidgetId = function(id)
	{
		count++;
		if(!id)
			return "cf_w_" + count;
		if(!that._usedIds[id])
		{
			that._usedIds[id]= true;
			return id;
		}
		return id + count;
	};
	/***
	 * Removes a widget from the registry.  
	 * @param v A typeString, widgetId, or widgetObject to remove from the registry. 
	 */
	that.remove = function(v)
	{
		var removed = [];
		that._widgets = CF.arrayReject(that._widgets, function (i, o){
				var remove = (o.type === v || o.id === v || o.widget === v);
				if(remove)
					removed.push(o);
				return remove;
		});
		/**
		 * @name CF.widget.registry#widget_removed
		 * @event
		 * @description
		 * Fired when a widget is removed from the registry
		 * @param {Object} o The widget that was removed from the registry
		 */
		jQuery.each(removed, function (i, o){ that.events.fire("widget_removed", o);});
	};
	/**
	 * Fetches a widget container by id
	 */
	that.getId = function (id)
	{
		return CF.arrayFind(that._widgets, function (i, o){ return id === o.id; });
	};
	/**
	 * Fetches an array of widget containers by type
	 */
	that.getType = function (t)
	{
		return CF.collect(that._widgets, function (i, o) { if(t === o.type) return o; return null;});
	};
	/**
	 * Fetches a widget container by widget object
	 */
	that.getWidget = function (widget)
	{
		return CF.arrayFind(that._widgets, function (i, o) {return widget === o.widget;});
	};
	
	/**
	 * Adds a function as a listener to all widgets of a given type.  This will automatically take care of rebinding the 
	 * event when new widgets are added to the registry. 
	 */
	that.listenType = function (t, event, fx)
	{
		that.listenedEvents.push({type:t, event:event, fx:fx});
		var wgts = that.getType(t);
		jQuery.each(wgts, function (i, o)
		{
			if(o.widget && o.widget.events)
			{
				o.widget.events.listen(event, fx);
			}
		});
	};
	return that;
}();



//Including: CF.widget.SimpleWidget.js
/**
 * @class
 * This widget contains basically no business logic and serves only to render templates based on data passed in through the data 
* Attribute of the template.  Its most common use is as a base class for other widgets to inherit basic widget structure and methods from
* 
* Events:
* widget_started fired when start() is called
* widget_reloaded fired when reload() is called
* widget_removed fired when remove() is called
* widget_drawn fired after the widget is added to the DOM.
* 
* @param {element} targetElem   The element or jQuery object that the widget will be rendered into.
* @param {element} template  The node or jQuery object that will be used as the template for this widget. 
* @param {CF.template.Engine} templateEngine  An instance of the templateEngine that will be used to render this widget
* @param {object} data  Any data that the widget needs to render or to fetch the data that will be rendered.
* @param {object} options  Any options that the widget needs to render the template.
*/
CF.widget.SimpleWidget = function (targetElem, template, templateEngine, data, options)
{
	if (!template)
	{
		template = CF.build("div");
	}
	var that = {};
	
	/**
	 * @type CF.EventPublisher
	 * @description The events property is a way for portlets to register event based communication with other widgets or non-widget code.
	 */
	that.events = CF.EventPublisher();
	
	/**
	 * @name CF.widget.SimpleWidget#widget_started
	 * @event
	 * @description
	 * Fired when the start() method is called.  Fires before onStart is called.
	 * @param {CF.widget.SimpleWidget} widget The current widget object.
	 */
	
	/**
	 * @name CF.widget.SimpleWidget#widget_reloaded
	 * @event
	 * @description
	 * Fired when the reload() method is called.  Fires before onReload is called.
	 * @param {CF.widget.SimpleWidget} widget The current widget object.
	 */
	
	/**
	 * @name CF.widget.SimpleWidget#widget_drawn
	 * @event
	 * @description
	 * Fired after the widget has been rendered and all of its subwidgets have been started.
	 * @param {CF.widget.SimpleWidget} widget The current widget object.
	 */

	/**
	 * @name CF.widget.SimpleWidget#widget_removed
	 * @event
	 * @description
	 * Fired when reload() is called.  Fires before the widget is removed from the registry.
	 * @param {CF.widget.SimpleWidget} widget The current widget object.
	 */
	
	
	/**
	* @description The getDefaultTemplateBody function allows you to provide a template that will be used to render a widget if the template is passed in empty.
	* This is especially useful for creating widgets that may have complex interactions that you want organize into a widget,
	* but that don't usually need to have their structure modified with a template because they don't display large amounts of data.
	* This can return either a HTML string, a node, a jQuery wrapped node, or null.  It will be called by the render method.
	*/
	that.getDefaultTemplateBody = function()
	{
		return null; //This widget does not have a default template body	
	};
	
 	/**
 	* @description returns the data that will be passed into the template render function.  
 	* If your widget fetched data via ajax in the start or reload function, you will probably want to pass the data through here.
 	*/
 	that.getData = function ()
	{
		return data;
	};
	
	/**
	* The start function kicks off the start stage of the widget lifecyle. 
	* @returns {object} "that" (the widget object) to enable chaining.
	*/
	that.start = function ()
	{
		that.events.fire("widget_started", that);
		that.onStart();
		return that;
	};
	
	/**
	 * @description
	 * Override this method to do any fetching of data via AJAX or other methods that your widget needs in order to be rendered
	 * When your data fetch is complete, call the that.draw() function.
	 */	
	that.onStart = function ()
	{
		//This widget doesn't fetch any data so it can call that.draw immediately.
		that.draw();
	};	
	
	/**
	* The draw function takes the rendered template and adds it to the targetElem.
	* For the top level widget in a nested widget heirarchy this will add it to the viewable DOM.  For nested widgets,
	* it will add the widget to the nested DOM.  Then, after it has been appended to the DOM, it starts its subWidgets
	*/
	that.draw = function ()
	{
		var frag = that.render();
		//hold on to any subwidgets that we may have, for later when we may need to remove them.
	    that.subWidgets = templateEngine.subWidgets;
	    targetElem.empty();
		targetElem.append(frag);
		that.bindEvents(targetElem, that.subWidgets);	
		templateEngine.startSubWidgets();
		that.events.fire("widget_drawn", that);
	};
	
	/**
	* Override this method to wire up DOM nodes to events using jQuery's find function.
	* This is the bindEvents function.  It is called by the draw function after rendering is complete but before the 
	* element has been appended to the dom.  
	* @param elem A jQuery wrapped node containing a rendered widget which has been rendered but not yet inserted into the DOM.
	* @param subWidgets A list of subWidgets and their wrappers that were rendered in the creation of this widget.  They will not have been started or rendered yet, 
	* but you may bind to their events
	*/	
	that.bindEvents = function (elem, subWidgets)
	{
		//Does nothing in this widget, you may wish to override this method with something like the below
		
		//The below will alert a message whenever an element of class "hoverable" inside the rendered template is hovered. 
		//elem.find(".hoverable").hover(function(){alert('You hovered a hoverable')});
		
		//The below will reload the widget whenever anyone clicks on a dom element with a class of "reload"
		//elem.find(".reload").click(that.reload); 
	};
	
	/**
	* Render calls to the templateEngine instance that was passed in at widget creation and gets back a jQuery wrapped 
	* dom node that has in it the entire DOM of the rendered template.  This template is not yet bound to the viewable DOM, 
	* so it may be manipulated easily and inexpensively.
	*/
	that.render = function ()
	{
		//Before rendering we need to call remove on all subwidgets that we have rendered, 
		// as they will be removed from the and recreated.
		if(that.subWidgets)
		{
			jQuery.each(that.subWidgets, function (i, o) {o.widget.remove();});
		}
		//I'm doing this clone here because the template engine is replacing the 
		//cf_widgetLoader statements with cf_widget statements.  This is problematic because it
		//modifies the original template... which means that on a reload, there are no cf_widgetLoader
		//statements. This is obviously slower, though.
		//I can think of a few fixes (not replacing cf_widgetLoader, adding it back in on reload), but 
		//they all have side-effects that may be unpleasant. 
		var elem = templateEngine.render(template.clone(), that.getData());
		return elem;
	};
	
	/**
	 * This method should be called when a widget is about to be removed from the DOM by a parent widget or another.
	 * It needs to clean up anything that holds a reference to it and detach any events that were bound.
	 * If your widget has listed to another widget's events, you may need to override this method, and unlisten to them here.
	 */
	that.remove = function ()
	{
		that.events.fire("widget_removed", that);
		CF.widget.registry.remove(that);
		if(that.events)
		{
			that.events.unlistenAll();
		}
		if(that.subWidgets)
		{
			jQuery.each(that.subWidgets, function (i,o) 
					{o.widget.remove();});
		}
		that.onRemove();
	};
	
	/**
	 * Override this method to do any additional cleanup that was not performed by the that.remove function.
	 * Typically, this is used to unlisten on any events from other widgets that this widget has listened to.
	 * The default implementation does nothing.
	 */
	that.onRemove = function ()
	{
	
	};
	
	/**
	* The reload function should fetch any data that needs fetched via AJAX and when complete call that.render().
	* It should return "that" (the simpleWidget object) to enable chaining.
	* This method is typically overridden.
	*/
	that.reload  = function ()
	{
		that.events.fire("widget_reloaded", that);
		that.onReload();	
		return that;
	};
	
	/**
	 * Override this method to do any fetching of data via AJAX or other methods that your widget needs in order to be re-rendered
	 * When your data fetch is complete, call the that.draw() function.
	 */	
	that.onReload = function ()
	{
		//This widget doesn't fetch any data via ajax, so it can call that.draw immediately
		that.draw();
	};
	
	return that;
};


//Including: CF.widget.SyndicationMixin.js

/**
 * @class
 * A mixin class that allows for detection of which service providers are active (based off auth_user's alt ids).
 * @extends CF.widget.SimpleWidget
 */

CF.widget.SyndicationMixin = function (that)
{	
	that = that || {};
	that.syndProvs = {
					"facebook": {
						className: "cf_synd_icon_fb",
						provider:"facebook"
					},
					"twitter": {
						className: "cf_synd_icon_tw",
						provider:"twitter"
					},
					"myspace": {
						className: "cf_synd_icon_ms",
						provider:"myspace"
					}
				};
	that.getSyndProviders = function()
	{
		return that.syndProvs;
	};
	that.getSyndProviderNames = function ()
	{
		return CF.keys(that.syndProvs);
	};
	that.getActiveSyndProviderNames = function ()
	{
		var provs = that.getActiveSyndProviders();
		return CF.collect(provs, function (i, p)
		{
			return p.provider;
		});
	};	
	that.getActiveSyndProviders = function ()
	{
		var u = CF.context.auth_user;
		if(!u || !u.alt_ids)
		{
			return [];
		}
		var ids = CF.pluck(u.alt_ids, "provider");
		return CF.collect(ids, function(i, p){
			if (that.syndProvs[p]) 
				return that.syndProvs[p];
		});
	};
	that.canSyndicate = function ()
	{
		return that.getActiveSyndProviders().length > 0;
	};
	that.syndicate = function (providers, category, target, urls, value, completeFx)
	{
		completeFx = completeFx || function (){};
		if(!jQuery.isArray(providers))
			providers = [providers];
		var count = providers.length;
		var results = [];
		var errors = [];
		
		var wrapComplete = function (result, error){
			count--;
			if(error)
				errors.push(error);
			else
				results.push(result);
			if(count <= 0)
			{
				errors = errors.length == 0 ? null : errors;
				completeFx(results, errors);
			}
		};
		var params = {};
		if (value)
			params.value = value;
		jQuery.each(providers, function (i, prov){
			CF.context.api_v1.syndication_create(wrapComplete, 
						(prov.provider || prov),
						category,
						target,
						urls, params);
				});
	};
	return that;	
};
//= require <CF.widget.SimpleWidget.js>
//= require <CF.widget.SyndicationMixin.js>
//= require <CF.context.js>

/**
 * @class
 * A widget for adding items to a crowd list.  The user must be the owner of the list. 
 * The list will be created if it does not already exist.
 * 
 * @extends CF.widget.SimpleWidget
 * @extends CF.widget.SyndicationMixin
 * 
 */
CF.widget.AddToCrowdList = function (targetElem, template, templateEngine, data, opts)
{
	opts = CF.extend({name:"Favorites", category:"FavoritesList", checkExists:true, syndicate:true, syndicationUrl:location.href, syndicationCategory:"favorite", hovertime:5000}, opts);
	
	if (!data) {
		CF.error("AddToCrowdList: The data parameter must be set to an entity or entityId");
		return null;
	}
	var entityId = data.uid || data;
		
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	CF.widget.SyndicationMixin(that);	
	that.exists = null;
	
	that.onReload = function()
	{
		//only check for existance once.  After that, just toggle it in the widget.
		if (opts.checkExists && that.exists === null) 
			that.getExists();
		else 
			that.draw();
	};
	that.onStart = that.onReload;
	
	that.getExists = function ()
	{
		CF.context.api_v1.list_entity_exists(that.setExists, entityId, opts.category, opts.name);
	};
	
	that.setExists = function (exists, error)
	{
		if(!error)
			that.exists = exists;
		else
			that.exists = false;
		that.draw();
	};
		
	that.getData = function()
	{
		return {user:CF.context.auth_user, opts:opts, exists:that.exists};
	};
	that.getDefaultTemplateBody = function ()
	{
		var templ = 
		"<div class='cf_addlist cf_if' binding='user' rendertag='true'>\
			<span class='cf_if' binding='exists'>\
				<span class='cf_addlist_exists'>One of your favorites <a class='cf_removelist_btn'>Unfavorite</a></span>\
				<span class='cf_else'>\
					<a class='cf_addlist_btn'>Save as a favorite</a>\
				</span>\
			</span>\
			<div class='cf_addlist_hoverbox' style='display:none'>\
				<h4>Saved to your favorites list</h4>\
				<a class='cf_addlist_synd_btn'>Publish to: </a>\
				<span class='cf_widgetLoader cf_syndication_icons' widgetType='CF.widget.SyndicationIcons'></span>\
			</div>\
			<div class='cf_else'>\
				Sign in to save favorites\
			</div>\
		</div>\
		";		
		return templ;
	};
	that.bindEvents = function (elem)
	{
		elem.find(".cf_addlist_btn").click(that.addToList);
		elem.find(".cf_removelist_btn").click(that.removeFromList);
		that.hoverbox = elem.find(".cf_addlist_hoverbox").hover(that.stopHoverTimer, that.startHoverTimer);
		that.syndBtn = elem.find(".cf_addlist_synd_btn").click(that.publish);
	};
	that.addToList = function ()
	{
		CF.context.api_v1.list_entity_add(that.handleAdded, entityId, opts.category, opts.name);
	};
	that.removeFromList = function ()
	{
		CF.context.api_v1.list_entity_remove(that.handleRemoved, entityId, opts.category, opts.name);
	};
	that.startHoverTimer = function ()
	{
		if(!that.timer)
			that.timer = setTimeout(that.hideHoverBox,opts.hovertime);
	};
	that.stopHoverTimer = function ()
	{
		if (that.timer)
			clearTimeout(that.timer);
		that.timer = null;
	};
	that.hideHoverBox = function(){
		that.timer = null;
		that.hoverbox.fadeOut(that.reload);
	};
	that.handleRemoved = function (result, error)
	{
		/**
		 * @name CF.widget.AddToCrowdList#listitem_removed
		 * @event
		 * @description
		 * The listitem_removed event is fired when an entity has been removed from the list.
		 * @parameter {String} entityId The entity id of the entity that was removed
		 * @parameter {AddToCrowdList} that The current widget object.
		 */
		that.events.fire("listitem_removed", entityId, that);
		that.exists = false;
		that.reload();
	};
	that.handleAdded = function (result, error)
	{
		if (!error)
		{
			/**
			 * @name CF.widget.AddToCrowdList#listitem_added
			 * @event
			 * @description
			 * The listitem_added event is fired when an entity has been added to the list.
			 * @parameter {String} entityId The entity id of the entity that was added
			 * @parameter {AddToCrowdList} that The current widget object.
			 */
			that.events.fire("listitem_added", entityId, that);
			that.exists = true;
			if(opts.syndicate && that.canSyndicate())
			{
				that.startHoverTimer();
				that.hoverbox.fadeIn();
			}
			else
			{
				that.reload();
			}
		}		
	};
	that.publish = function ()
	{
		var providers = that.getActiveSyndProviders();
		that.syndicate(providers, opts.syndicationCategory, entityId, opts.syndicationUrl, opts.name, that.syndComplete);
		that.hideHoverBox();
	};
	that.syndComplete = function (result, error)
	{
		
	};
	return that;
};

//Including: CF.widget.EntityComments.js

//Including: CF.widget.BaseComments.js

//Including: CF.widget.Pageable.js
/** 
 * @class
 * A class that adds in basic cursor support.  This is intended to be either used as a Mixin, or as an instance variable inside of a widget class.
 * You need to override the pageChanged method and wire it to the logic that fetches your data.
 * 
* @behavior {click} .cf_next_page Increments the offset by one page, and calls pageChanged
* @behavior {click} .cf_prev_page Decrements the offest by one page, and calls pageChanged
* @behavior {click} .cf_first_page Sets the offset to zero and calls pageChanged
* @behavior .cf_num_page The number of the current page will be placed in these elements
* @behavior .cf_num_items The number of items on the current page will be placed in to this element
* 
*/
CF.widget.Pageable = function (opts, that)
{
	var defOpts = { max_return:20, offset:0};
	opts = CF.extend(defOpts, (opts || {}));
	
	var p = {}; //private methods and vars
	p.maxReturn = opts.max_return;
	p.offset = opts.offset;
	p.lastReturnedLen = 0;

	that = that || {};
	
	that.getOffset = function ()
	{
		return p.offset;	
	};
	that.getMaxReturn = function ()
	{
		return p.maxReturn;
	};
	that.nextPage = function()
	{
		if (!that.isLastPage())
		{
			p.offset += Math.min(p.maxReturn, p.lastReturnedLen);
			that.pageChanged();

		}
	};
	that.toParams = function ()
	{
		return {max_return:p.maxReturn, offset:p.offset};
	};
	that.firstPage = function ()
	{
		p.offset = 0;	
		that.pageChanged();
	};
	that.prevPage = function ()
	{
		p.offset = Math.max(0, p.offset - p.maxReturn);
		that.pageChanged();
	};
	that.updateParams = function (params)
	{
		params = params || {};
		return CF.extend(params, that.toParams());	
	};
	that.getPageNum = function ()
	{
		if(that.isFirstPage())
		{
			return 1;
		}
		return 1 + Math.ceil(p.offset/p.maxReturn);
	};
	that.updatePager = function(itemList)
	{
		if (jQuery.isArray(itemList))
		{
			p.lastReturnedLen = itemList.length;
			that.updatePageNum(p.countElems);
			that.updateItemNum(p.countItemElems);
		}
	};
	that.updatePageNum = function (elems)
	{
		if (elems)
		{
			elems.html(that.getPageNum());
		}
	};
	that.updateItemNum = function (elems)
	{
		if(elems)
		{
			elems.html(p.lastReturnedLen);
		}
	};
	that.isLastPage = function ()
	{
		return  p.lastReturnedLen && (p.lastReturnedLen < p.maxReturn);	
	};
	that.isFirstPage = function ()
	{
		return p.offset == 0;
	};
	that.pageChanged = function ()
	{
		throw ("Pageable: pageChanged implementation required");
	};
	that.bindPagerEvents = function (elem)
	{
		p.nextElems = elem.find(".cf_next_page");
		p.prevElems = elem.find(".cf_prev_page");
		p.firstElems = elem.find(".cf_first_page");
		p.countElems = elem.find(".cf_num_page");
		p.countItemElems = elem.find(".cf_num_items");
		
		if (!that.isLastPage())
		{
			p.nextElems.click(CF.once(that.nextPage));
		}
		else
		{
			p.nextElems.addClass("cf_disabled_page");
		}
		if (!that.isFirstPage())
		{
			p.firstElems.click(CF.once(that.firstPage));
			p.prevElems.click(CF.once(that.prevPage));
		}
		else
		{
			p.firstElems.addClass("cf_disabled_page");
			p.prevElems.addClass("cf_disabled_page");
		}		
		that.updatePageNum(p.countElems);
		that.updateItemNum(p.countItemElems);
	};
	return that;	
};
//= require <CF.context.js>
//= require <CF.widget.SimpleWidget.js>
//= require <CF.widget.Pageable.js>

/** @class 
 * The base class for comments.
 * Only allows logged in users to comment, other users are show It wires up behaviors for having a comment text body and a post comments button, as well as
 * listening for comment_reply_activated events on nested CF.widget.Comment widgets.
 * You need to override the fetchComments and postComments methods to use this class.
 * 
 *  @extends CF.widget.SimpleWidget
 *  @extends CF.widget.Pageable
 *  @extends CF.widget.SyndicationMixin
 *  
 *  @behavior {click} .cf_response_cancel Cancels a response to a nested comment.
 *  @behavior {click} .cf_comment_post Posts a comment from the currently logged in user with the 
 *  text in the .cf_comment_txt textarea.
 *  
 *  
 * */

CF.widget.BaseComments = function (targetElem, template, templateEngine, data, opts)
{
	opts = CF.extend({nested:false, syndicate:false, syndicationCategory:"comment", syndicationUrl:location.href }, opts); 
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	CF.widget.SyndicationMixin(that);
	CF.widget.Pageable(opts, that);
	
    that.setupParams = function ()
	{
		var params = CF.extend({}, opts);
		delete params['nested']; //These options aren't actual parameter options for the rest call.
		delete params['syndicate'];
		delete params['syndicationUrl'];
		delete params['syndicationCategory'];
		if(opts.nested && !params.depth)
		{
			params.order = "LineageLeastRecentFirst";
			params.depth = 10;// default 10 depth for nested.
		}
		params.nest = false; //we draw nesting with the depth property, not recursion.
		return params;
		//Test
	};
	
	that.params = that.setupParams();
	
	that.getDefaultTemplateBody = function ()
	{
		return "<div class='cf_comment_message'></div>\
		<div class='cf_comment_page'> \
			Showing (<span class='cf_num_items'></span>) comments \
		</div> \
		<ol class='cf_for cf_comments' binding='data.list'> \
        <li class='cf_item cf_comment cf_comment_depth[% (item.posting_depth || 0) %]'> \
        	<div class='cf_widgetLoader' widgettype='CF.widget.Comment' data='item' options='parent.opts'></div> \
        </li> \
         <li class='cf_item_empty'> \
         	No one has commented yet. \
         	<div class ='cf_if' binding='authUser'> \
         		 Be the first to comment! \
         	</div> \
         </li> \
       </ol> \
       <div class='cf_pager_row'> \
	   	Page (<span class='cf_num_page'></span>) <a class='cf_first_page'>First</a> <a class='cf_prev_page'>Prev</a> <a class='cf_next_page'>Next</a> \
	   </div> \
	    <div class='cf_if' binding='authUser'> \
		    <div> \
		    	<label for='cf_commentTxt'>Add your comment:</label> \
		    </div> \
	    <div class='cf_response' style='display:none;'> \
      		<span>In response to:</span> \
      		<span class='cf_response_to'></span> \
      		<a class='cf_response_cancel'> cancel</a> \
      	</div> \
		<div> \
      		<textarea class='cf_comment_txt'></textarea> \
      	</div> \
      	<div class='cf_comment_button_row'> \
			<div class='cf_comment_syndicate' style='display:none;'>\
				<div class='cf_syndicate_text'>Publish to:</div>\
				<div class='cf_widgetLoader cf_syndication_icons' widgettype='CF.widget.SyndicationIcons' options='{checkbox:true}'></div>\
				<div class='cf_clear'> </div>\
			</div>\
			<button class='cf_comment_post' type='button'>Post comment</button> \
      	</div> \
      	<div class='cf_else'> \
      		Please log in to start commenting. \
      	</div> \
      </div> \
     ";		            
	};            
	that.responseTo = function (evt, comment, w)
	{
		if (that.responseElem)
		{
			that.responseToComment = comment;
			that.responseElem.find(".cf_response_to").html(comment.user.display_name);
			that.responseElem.show();
			that.focusReply();
		}
	};
	that.cancelResponseTo = function()
	{
		that.responseToComment = null;
		that.responseElem.hide();
	};
	that.focusReply = function ()
	{
		if (that.commentTxt)
		{
			CF.focusLater(that.commentTxt, 20);
		}
	};
	that.bindEvents = function (elem, subWidgets)
    {
		that.responseElem = elem.find(".cf_response");
    	jQuery.each(subWidgets, function (i, w){ 
    		if (w.type === "CF.widget.Comment")
    		{
    			w.widget.events.listen("comment_reply_activated", that.responseTo);
    		}
    	});
		elem.find(".cf_focus_reply").click(that.focusReply);
    	elem.find(".cf_response_cancel").click(that.cancelResponseTo);
    	elem.find(".cf_comment_post").click(that.postComment);
    	that.commentTxt = elem.find(".cf_comment_txt");
    	that.commentMsg = elem.find(".cf_comment_message");
    	that.bindPagerEvents(elem);
		if (opts.syndicate && that.canSyndicate())
		{
			elem.find(".cf_comment_syndicate").show();
			that.syndIcons = CF.collect(subWidgets, function (i, w){ 
			if (w.type== 'CF.widget.SyndicationIcons')
				return w.widget;	
			});
		}
		
    };
    that.pageChanged = function ()
    {
    	that.params = that.updateParams(that.params);
    	that.reload();
    };
    that.onReload = function ()
    {
		that.fetchComments();
	};
	that.onStart = function ()
	{
		that.fetchComments();
	};
	that.fetchComments = function()
	{
		throw ("Implementation required for fetchComments.");
	};
	that.postComment = function ()
	{
		throw ("Implementation required for postComment");
	};
	
	/**
	 * @name CF.widget.BaseComments#comment_posted
	 * @event
	 * @description
	 * Fired when a comment is posted. 
	 * @param {Object} result The result of the post comment call.
	 * @param {CF.widget.Entity} that The current widget object.
	 */
	
	
	that.commentPosted = function (result, error)
	{
		that.events.fire("comment_posted", result, that);
	
		if (error)
		{
			CF.error("Error posting comment", error);
		}
		else
		{
			if (opts.syndicate && that.canSyndicate())
			{
				var providers = [];
				jQuery.each(that.syndIcons, function(i, w){
						providers = CF.keys(w.checkedToggles);
					});
				that.syndicate(providers, opts.syndicationCategory, result.id, opts.syndicationUrl, null);
			}
			
			that.responseToComment = null;
			if (that.commentMsg)
			{
				that.commentMsg.show();
				that.commentMsg.html("Your comment was added.");
			}
			that.reload();
		}
	};
    that.commentListLoaded = function (comments, error)
    {
    	if (error)
    	{
    		comments = [];
    		CF.error("Error fetching comments", error);
    	}
    	that.commentList = comments;
    	that.updatePager(that.commentList);
    	that.draw();
    };
    that.getData = function ()
    {
    	return {
    		opts:opts,
    		list: that.commentList,
    		authUser : CF.context.auth_user
    	};
    };
    return that;
};
//= require <CF.widget.BaseComments.js>

/**
 * @class
 * A widget for creating lists of comments on entities.
 * @extends CF.widget.BaseComments
 * 
 * */
CF.widget.EntityComments = function (targetElem, template, templateEngine, data, opts)
{
	var entity= null;
	if (data && typeof data === 'object') {
		entity = data;
		data = data.uid;
	}
	if(!data)
	{
		CF.error("EntityComments: the data parameter must be set to an entity or entityId", data);
		return null;
	}
	var that = CF.widget.BaseComments(targetElem, template, templateEngine, data, opts);
	
	that.fetchComments = function()
	{
		CF.context.api_v1.comment_entity_get(that.commentListLoaded, data, that.updateParams(that.params));
	};
	that.postComment = function ()
	{
		var commentBody = that.commentTxt.val();
		if (commentBody)
		{
			var params = {};
			if (that.responseToComment)
			{
				params.parent= that.responseToComment.id;	
			}
			CF.context.api_v1.comment_entity_create(that.commentPosted, data, commentBody, params);
		}
	};
	that.superGetData = that.getData;
	that.getData = function ()
	{
		var o = that.superGetData();
		if (entity)
			o.entity = entity;
		return o;
	};
	return that;
};

//Including: CF.widget.RPXLogin.js
//= require <CF.widget.SimpleWidget.js>

/**
 * @class
 * A widget for displaying the RPX login iframe and triggering the 
 * CF.login.remoteAuthVerify code flow.
 * 
 * @extends CF.widget.SimpleWidget
 */

CF.widget.RPXLogin = function (targetElem, template, templateEngine, data, opts)
{
	if (!opts.rpxUrl)
	{
		CF.error("You must have the rpxUrl option set.");
		return;
	}
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	that.onReload = function ()
    {
    	that.draw();
	};
	that.onStart = function ()
	{
		that.draw();
	};
	that.getDefaultTemplateBody = function ()
	{
		return "<iframe	src='[% iframeUrl %]' scrolling='no' frameBorder='no' style='width: 400px; height: 240px;'></iframe>";
	};
	that.getData = function ()
	{
		var l = CF.login;
		var toAdd = {};
		toAdd[l.loginTokenNameParam] = "token";
		toAdd[l.loginProviderParam] = "rpx";
		
		var redirectUrl = location.href;
		//Current page url. Where we will eventually end up.  Strip off any tokens that were there 
		//and not cleaned up.
		redirectUrl = CF.url.removeParam([l.loginProviderParam, l.loginTokenNameParam, "token"], redirectUrl);
		//Add in the token provider and param name parameters.
		redirectUrl = CF.url.addParams(toAdd, redirectUrl);
		//Strip out any url fragments that might be in it.
		redirectUrl = CF.url.removeHash(redirectUrl);
	
		//Get the token url 
		var tokenUrl = CF.context.api_v1.loginreg_rpxforward_url(redirectUrl);
		
		//Add the token URL as a parameter to the rpx Login url that was passed in.
		return {iframeUrl: CF.url.addParam("token_url", tokenUrl, opts.rpxUrl)};
	};
	return that;	
};

//Including: CF.widget.ImageUpload.js
//= require <CF.widget.SimpleWidget.js>
/**
 * @class
 * A widget used upload images to the Image CMS
 * @extends CF.widget.SimpleWidget
 * @description 
 * @behavior .cf_upload_form_template Elements to place inside the iframe's upload form.
 * @behavior {click} .cf_change_image Once an image has already been uploaded, this allows you to upload a different image with the same form.
 * @behavior .cf_upload_error_msg The element that will be displayed with the error message when an uploading error has occurred.
 * @behavior .cf_upload_iframe The actual iframe element that will be used for the iframe upload.
 * @behavior .cf_iframe_holder The container that wraps the iframe.  It will be hidden when the upload has began.
 * @behavior .cf_iframe_upload_progress The elements to show when the upload is in progress (after submit but before the finish).
 */
CF.widget.ImageUpload = function (targetElem, template, templateEngine, data, opts)
{
	var defaultOpts = {
		iframeUrl:null,
		params:	{},
		errorFadeTimeMs:10000
	};	
	opts = CF.extend(defaultOpts, opts);
	CF.extend(opts.params, CF.context.api_v1.opts); //Copy in all API opts as params
	
	if (!opts.iframeUrl)
	{
		CF.error("iframeUrl option is not set.  It is required and must point to the iframe that will perform the upload.");
		return null;
	}
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	that.imageUrl = null;
	that.imageId = null;
	
	that.onStart = function ()
	{
		that.onReload();
	};	
	that.onReload = function ()
	{
		that.iframeId = "iframe_" + CF.iframe.frameCount++;
		that.draw();
	};
	that.getData = function()
	{
		var src = CF.url.addParam("iframeId", that.iframeId, opts.iframeUrl);
		return {iframeSrc:src, iframeId:that.iframeId, 
			image:that.image, authUser:CF.context.auth_user, errorMsg:that.errorMsg};
	};
	that.getDefaultTemplateBody = function ()
	{
		var templ = 
	"<div class='cf_if' binding='authUser'>\
		<div class='cf_if' binding='errorMsg'>\
			<div class='cf_upload_error_msg'>[%errorMsg%]</div> \
		</div>\
		<div class='cf_if' binding='image'> \
			<div class='cf_uploaded_image'>\
				<img cf_src='[%image.thumb_nail_url%]' id='[%image.external_id%]'></img>\
				<button class='cf_change_image'>Change</button>\
			</div>\
			<div class='cf_else'>\
				<div class='cf_iframe_holder'>\
					<iframe class='cf_upload_iframe' cf_src='[%iframeSrc%]' frameborder=0 ></iframe>\
				</div>\
				<div class='cf_iframe_upload_progress' style='display:none'>Uploading...</div>\
			</div>\
		</div>\
		<div class='cf_noprocess cf_upload_form_template' style='display:none'>\
			<input type='file' class='cf_upload_input' name='uploadFile'>\
			<button type='submit' class='cf_upload_submit'>Upload</button>\
		</div>\
		<div class='cf_else'>\
			You must be logged in to upload an image.\
		</div>\
	</div>\
	";
		return templ;
	};
	that.iframeReady = function (evt, iframeComm)
	{
		that.iframeComm = iframeComm;
		opts.params.image_file = 'uploadFile';
		var api = CF.context.api_v1;
		iframeComm.run(opts.params, that.uploadFormTemplate.clone().show(), api.hostname + api.path + "v1/image/create");
	};
	that.iframeSuccess = function(evt, iframeComm, id, url)
	{
		that.iframeComm = iframeComm;
		CF.context.api_v1.entity_get(that.fetchedImage, id);
	};
	that.fetchedImage = function (image, error)
	{
		if(error)
		{
			that.errorMsg = error.error_str;
			that.events.fire("imageupload_fail", that, error.error_str, error.error_code);
		}
		else{
			that.imageUrl = image.url;
			that.imageId = image.external_id;	
			that.image = image;
			that.events.fire("imageupload_success", that, that.imageId, that.imageUrl);
		}
		/**
		 * @name CF.widget.ImageUpload#imageupload_success
		 * @event
		 * @description
		 * Fired when an image upload has failed. 
		 * @param {CF.widget.ImageUpload} The current widget object.
		 * @param {String} id The entity id of the uploaded image.
		 * @param {Number} url The url of the thumbnail size of the uploaded image.
		 */
		that.reload();
	};
	that.iframeFail = function (evt, iframeComm, errStr, errCode)
	{
		that.iframeComm = iframeComm;
		that.errorMsg = errStr;
		/**
		 * @name CF.widget.ImageUpload#imageupload_fail
		 * @event
		 * @description
		 * Fired when an image upload has failed. 
		 * @param {CF.widget.ImageUpload} The current widget object.
		 * @param {String} errStr The error message.
		 * @param {Number} errStr The error code.
		 */
		that.events.fire("imageupload_fail", that, errStr, errCode);
		that.reload();		
	};
	/**
	 * Override this and return false to prevent the submit from firing.  Useful for validation.
	 */
	that.iframeUploadStart = function (evt)
	{
		/**
		 * @name CF.widget.ImageUpload#imageupload_started
		 * @event
		 * @description
		 * Fired when the user has started the upload by submitting the upload form in the iframe.
		 * @param {CF.widget.ImageUpload} The current widget object.
		 */
		var results = that.events.fire("imageupload_started", that);
		var ok = true;
		jQuery.each(results, function (i, r)
		{
			if (r === false)
				ok = false;
		});
		if (!ok)
			return false;
		that.errorMsg = null;
		that.uploadErrorMsg.hide();
		//The set timeout is here for Safari, which will not allow a file upload to start in a hidden element, this gives it time to start before hiding.
		setTimeout(function (){that.iframeHolder.hide(), that.iframeUploadProgress.fadeIn();}, 100);
		return true;
	};
	that.updateParams = function (params)
	{
		CF.extend(opts.params, params);
		if(that.iframeComm)
		{
			that.iframeComm.updateParams(opts.params);
		}
	};	
	that.newImage = function ()
	{
		that.imageUrl = null;
		that.imageId = null;
		that.image = null;
		that.reload();
		that.errorMsg = null;
	};
	that.bindEvents = function (elem, subWidgets)
	{
		that.uploadFormTemplate = elem.find(".cf_upload_form_template");
		that.uploadErrorMsg = elem.find(".cf_upload_error_msg");
		that.iframeElem = elem.find(".cf_upload_iframe");
		that.iframeHolder = elem.find(".cf_iframe_holder");
		that.iframeUploadProgress = elem.find(".cf_iframe_upload_progress");
		that.changeImage = elem.find(".cf_change_image").click(that.newImage);
		var evts = CF.iframe.events;
		evts.listen(that.iframeId + "_ready", that.iframeReady);
		evts.listen(that.iframeId + "_success", that.iframeSuccess);
		evts.listen(that.iframeId + "_fail", that.iframeFail);
		evts.listen(that.iframeId + "_uploadStart", that.iframeUploadStart);
		var ifId = that.iframeId;
		if (that.uploadErrorMsg.length > 0)
		{
			setTimeout(function (){if (ifId == that.iframeId) that.uploadErrorMsg.fadeOut();}, opts.errorFadeTimeMs);
		}
	};
	return that;
};

//Including: CF.widget.SyndicationIcons.js

//Including: CF.widget.EntityRating.js
//= require <CF.widget.SimpleWidget.js>
//= require <CF.context.js>

/**
 * @class
 * A widget for displaying and editing ratings on entities.  The default template is a thumbs up/thumbs down
 * widget.
 * @extends CF.widget.SimpleWidget
 *
 * @behavior {click} .cf_vote When clicked, a vote is registered for the entity with a value equal to the 'voteval' attribute of the
 * clicked element. The new rating's value will be rounded as per the roundTo option and will be within the options minVal and maxVal.
 * If the new rating is successfully created or updated, the rating_created event will be fired.
 * 
 * @description
 * The opts parameter has 5 properties that can be set.<br/>
 * minVal: the minimum value for the rating (default:0)<br/>
 * maxVal: the maximum value for a rating (default:0)<br/>
 * roundTo: the amount to round the results, eg .25 to round to nearest quarter or 10 to round to the nearest product of 10 (default:1)<br/>
 * canRate: If set false, the rating widget will display only and not allow more votes.<br/>
 * category: The ratingType category to rate with.  
 * @see <a href='http://docs.crowdfactory.com/version/current/rest/v1_rating_entity_create.html'>The rating/entity/create/ documentation</a>
 */

CF.widget.EntityRating = function (targetElem, template, templateEngine, data, opts)
{
	var defaultOpts = {
		minVal: 0,
		maxVal: 1,
		roundTo: 1,
		canRate:true,
		category:"ThumbsUp",
		syndicate:false,
		syndiationUrl:location.href,
		syndicationCategory:"rating_entity",
		hovertime:5000
	};
	opts = CF.extend(defaultOpts, opts);		
	var that = CF.widget.BaseRating(targetElem, template, templateEngine, data, opts);
	
	that.sendRating = function (val)
	{
		CF.context.api_v1.rating_entity_create(that.ratingCreated, that.entity.uid, opts.category, val);
	};	
	that.fetchEntity = function ()
	{
		CF.context.api_v1.entity_get(that.handleFetchEntity, that.entityId, {rating:opts.category});
	};
	that.getData = function ()
	{
		return {entity:that.entity, rating:that.rating, opts:opts, authUser:CF.context.auth_user}; 
	};
	that.populateEntity = function (entity)
	{
		that.rating = CF.arrayFind(entity.entity_ratings, function (i, r){
			if(r.category == opts.category)
				return r;
		});
		if (that.rating)
		{
			that.entity = entity;
			that.entityId = entity.uid;
		}
	};
	
	that.handleFetchEntity = function (entity, error)
	{
		if(!error)
		{
			that.populateEntity(entity);
			that.draw();
		}
		else
		{
			//TODO: Handle error;
		}
	};
	
	that.publish = function (entity, error)
	{
		var providers = that.getActiveSyndProviders();
		var value = "" + that.newRating;
		that.syndicate(providers, opts.syndicationCategory, that.entityId, opts.syndicationUrl,value, that.syndComplete);
		that.hideHoverBox();
	};
	
	/**
	 * @description
	 * On reload, the entity will always be refetched.
	 */
    that.onReload = function ()
    {
    	that.fetchEntity();
	};
	/**
	 * @description
	 * On start, an entity will be fetched if only the id was passed in
	 */
	that.onStart = function ()
	{
		if (!that.entity)
		{
			that.fetchEntity();
		}
		else
		{
			that.draw();
		}
	};
	
	//On object constructed to set up data.
	if (typeof data == 'object')
	{
		if(data.entity_ratings)
		{
			that.populateEntity(data);
		}
		that.entityId = data.uid;
	}
	else
	{
		that.entityId = data;
	}
	if(!that.entityId)
	{
		CF.error("Cannot create EntityRating, invalid data parameter set.");
		return null;
	}
	return that;
};
//= require <CF.widget.EntityRating.js>
//= require <CF.widget.SyndicationMixin.js>

/**
 * @class
 * A Widget for rendering a set of syndication icons that are applicable to the current user.
 * @extends CF.widget.SimpleWidget
 * @extends CF.widget.SyndicationMixin
 */

CF.widget.SyndicationIcons = function (targetElem, template, templateEngine, data, opts)
{
	opts = CF.extend({checkbox:false}, opts);
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	CF.widget.SyndicationMixin(that);
	that.checkedToggles = {};
	that.onStart = function ()
	{
		that.draw();
	};
	that.getDefaultTemplateBody = function ()
	{
		return "\
				<div class='cf_if' binding='user'>\
					<div class='cf_for' rendertag='false' binding='providers'>\
						<div class='cf_item cf_syndication_icon'>\
							<div class='cf_if' binding='parent.opts.checkbox'>\
								<input type='checkbox' id='[%item.provider%]_icon' class='cf_synd_icon_cbx' value='[% item.provider %]'>\
								<label class='[% item.className %]' for='[%item.provider%]_icon_[%idOffset+1%]' ></label>\
								<div class='cf_else'>\
									<div cf_class='[%item.className%]'></div>\
								</div>\
							</div>\
						</div>\
					</div>\
				</div>";
	};
	that.getData = function ()
	{
		return {opts:opts, user:CF.context.auth_user, providers:that.getActiveSyndProviders()};
	};
	that.toggleIcon = function ()
	{
		var v = jQuery(this).val();
		if (this.checked)
			that.checkedToggles[v] = true;
		else
			delete that.checkedToggles[v];	
	};
	that.bindEvents = function (elem, subWidgets)
	{
		if(opts.checkbox)
		{
			that.toggles = elem.find(".cf_synd_icon_cbx").click(that.toggleIcon);	
		}
	};
	return that;	
};

//Including: CF.widget.Entity.js
//=  require <CF.widget.SimpleWidget.js>
//=  require <CF.context.js>
/**
 * @class
 * A Widget for rendering a single entity by either id or by passing the full entity object.  If the full entity object is passed
 * as data, no request will be made to fetch the entity.
 * The options are past through as a parameters to the underlying entity/get call.

 * @extends CF.widget.SimpleWidget
 * 
 * @behavior {click} .cf_entity_activate Fires the entity_activated event and fades in any .cf_entity_activate_target elements in the template.
 * @behavior {click} .cf_entity_deactivate Fires the entity_deactivated event and hides any .cf_entity_activate_target elements in the template.
 * @behavior {hover} .cf_entity_hover Fires the entity_hover_in event when an element is hovered and the entity_hover_out event when an element 
 * is unhovered.  Also fades in any .cf_entity_hover_target elements when hover_in happens and hides them on hover_out.
 */

CF.widget.Entity = function (targetElem, template, templateEngine, data, opts)
{
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	/**
	 * @name CF.widget.Entity#entity_activated
	 * @event
	 * @description
	 * Fired when the entity is activated. 
	 * @param {Object} entity The entity that is displayed for this widget.
	 * @param {CF.widget.Entity} The current widget object.
	 */
	
	/**
	 * @name CF.widget.Entity#entity_deactivated
	 * @event
	 * @description
	 * Fired when the entity is deactivated. 
	 * @param {Object} entity The entity that is displayed for this widget.
	 * @param {CF.widget.Entity} The current widget object.
	 */
	
	/**
	 * @name CF.widget.Entity#entity_hover_in
	 * @event
	 * @description
	 * Fired when the entity has began to be hovered. 
	 * @param {Object} entity The entity that is displayed for this widget.
	 * @param {CF.widget.Entity} The current widget object.
	 */
	
	/**
	 * @name CF.widget.Entity#entity_hover_out
	 * @event
	 * @description
	 * Fired when the entity has finished being hovered. 
	 * @param {Object} entity The entity that is displayed for this widget.
	 * @param {CF.widget.Entity} The current widget object.
	 */
	
	if(!data)
	{
		CF.error("Entity: data parameter of either an entity object or an entity id required");
		return null;
	}
	if (typeof data == 'object')
	{
		that.entity = data;
		that.entityId = that.entity.uid;
	}else
	{
		that.entityId = data;
	}
		
	/**
	 * @description
	 * On reload, the entity will always be refetched.
	 */
    that.onReload = function ()
    {
    	that.fetchEntity();
	};
	/**
	 * @description
	 * On start, an entity will be fetched if only the id was passed in.
	 */
	that.onStart = function ()
	{
		if (!that.entity)
		{
			that.fetchEntity();
		}
		else
		{
			that.draw();
		}
	};
	
	/**
	 * The default template body for this is unlikely to be used, as the display case for entities is very different for each
	 * customer.
	 */
	that.getDefaultTemplateBody = function ()
	{
		return " \
			<div class='cf_entity'> \
				<div class='cf_if' binding='entity.url'>\
					<a cf_href='[%entity.url%]'>\
					 <div class='cf_title'>[%entity.title%]</div> \
					</a> \
					<div class='cf_else'> \
					<div class='cf_title'>[%entity.title%]</div> \
					</div> \
				</div>\
				<div class='cf_created'> Added: [% CF.friendlyDate(entity.created) %] </div>\
				<div class='cf_if' binding='entity.description'>\
					<div class='cf_description'>\
						[%entity.description%] \
					</div>\
				</div> \
		 	</div>";
	};
	that.getData = function ()
	{
		return {entity:that.entity, opts:opts, authUser:CF.context.auth_user};
	};
	that.fetchEntity = function()
	{
		CF.context.api_v1.entity_get(that.entityFetched, that.entityId, opts);
	};
	that.entityFetched = function (entity, error)
	{
		if (error)
			that.entity = null;
		else
			that.entity = entity;
		that.draw();
	};
	that.hoverIn = function ()
	{
		that.events.fire("entity_hover_in", that.entity, that);
		that.hoverTargetElem.fadeIn();
	};
	that.hoverOut = function()
	{
		that.events.fire("entity_hover_out", that.entity, that);
		that.hoverTargetElem.hide();
	};
	that.entityActivated = function ()
	{
		that.events.fire("entity_activated", that.entity, that);
		that.activateTargetElem.fadeIn();		
	};
	that.entityDeactivated = function ()
	{
		that.events.fire("entity_deactiviated", that.entity, that);
		that.activateTargetElem.hide();
	};
	that.bindEvents = function (elem, subWidgets)
	{
		elem.find(".cf_entity_hover").hover(that.hoverIn, that.hoverOut);
		elem.find(".cf_entity_activate").click(that.entityActivated);
		elem.find(".cf_entity_deactivate").click(that.entityDeactivated);
		that.activateTargetElem = elem.find(".cf_entity_activate_target").hide();
		that.hoverTargetElem = elem.find(".cf_entity_hover_target").hide();
	};
	return that;	
};

//Including: CF.widget.BoardComments.js
//= require <CF.widget.BaseComments.js>
/**
 * @class
 * A widget for creating lists of comments on boards.
 * @extends CF.widget.BaseComments
 * 
 */
CF.widget.BoardComments = function (targetElem, template, templateEngine, data, opts)
{
	var that = CF.widget.BaseComments(targetElem, template, templateEngine, data, opts);
	if(data && typeof data === 'object')
		data = data.id;
	if(!data)
	{
		CF.error("BoardComments: the data parameter must be set to a board or a board's id", data);
		return null;
	}
	that.fetchComments = function()
	{
		CF.context.api_v1.comment_board_get(that.commentListLoaded, data, that.updateParams(that.params));
	};
	that.postComment = function ()
	{
		var commentBody = that.commentTxt.val();
		if (commentBody)
		{
			var params = {};
			if (that.responseToComment)
			{
				params.parent= that.responseToComment.id;	
			}
			CF.context.api_v1.comment_board_create(that.commentPosted, data, commentBody, params);
		}
	};
	return that;
};

//Including: CF.widget.ActivityEventView.js
//= require <CF.widget.SimpleWidget.js>
//= require <CF.widget.Pageable.js>
//= require <CF.context.js>
/**
 * @class
 * An incomplete in-progress version of a widget used to display activity events.
 * @extends CF.widget.SimpleWidget
 * @extends CF.widget.Pageable
 * @description 
 */
CF.widget.ActivityEventView = function (targetElem, template, templateEngine, data, opts)
{
	var defaultOpts = {
		offset:0,
		max_return:20
	};	
	
	opts = CF.extend(defaultOpts, opts);
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	CF.widget.Pageable(opts,that);
	
	that.onStart = function ()
	{
		that.getEntityList();
	};	
	that.onReload = function ()
	{
		that.getEntityList();
	};
	that.getEntityList = function()
	{
		that.updateParams(opts);
		CF.context.api_v1.activityevent_get(that.handleListLoad, opts);
	};	
	that.pageChanged = function (){
		that.reload();
	};
	that.handleListLoad = function (activityEvents, error)
	{
		if(error)
		{
			activityEvents = [];
		}
		that.activityEvents = activityEvents;	
		that.updatePager(that.activityEvents);
		that.draw();	
	};
	that.getData = function()
	{
		return {activityEvents:that.activityEvents, pager:{ offset:that.getOffset(), num_page:that.getPageNum()}};
	};
	that.getDefaultTemplateBody = function ()
	{
		var templ = 
			"<div class='cf_pager_row'> \
			Current page (<span class='cf_num_page'></span>) \
			Items on page (<span class='cf_num_items'></span>)\
			<a class='cf_first_page'>First</a> <a class='cf_prev_page'>Prev</a> <a class='cf_next_page'>Next</a>\
			</div>\
				<ul class='cf_for' binding = 'activityEvents'> \
				<li class='cf_item'>\
					<div class='cf_if' binding='item.performer.user' assign='true'>\
						performer: [% display_name %] \
					</div> \
					<div class='cf_if' binding='item.participant.user' assign='true'>\
						participant: [% display_name %] \
					</div> \
					<div class='cf_if' binding='item.container.user' assign='true'>\
						container: [% display_name %] \
					</div> \
					<div class='cf_if' binding='item.performer.ExternalEntity' assign='true'>\
						performer: [% title %] \
					</div> \
					<div class='cf_if' binding='item.participant.ExternalEntity' assign='true'>\
						participant: [% title %] \
					</div> \
					<div class='cf_if' binding='item.container.ExternalEntity' assign='true'>\
						container: [% title %] \
					</div> \
					<div class='cf_message cf_if' rendertag='true' binding='item.message'>\
						[% item.message %]\
					</div>\
					<div class='cf_widgetLoader' widgettype='CF.widget.Entity' data='item'/> \
				</li> \
				<li class='cf_item_empty'>No items.</li> \
			</ul>";
		return templ;
	};
	that.bindEvents = function (elem, subwidgets)
	{
		CF.widget.registry.listenType("CF.widget.MyStatusActivity", "activityevent_created", that.reload);
		that.bindPagerEvents(elem);
	};
	return that;
};

//Including: CF.widget.UserLoginStatus.js
//= require <CF.widget.SimpleWidget.js>
//= require <CF.login.js>
//= require <CF.modal.js>

/**
 * @class
 * A widget that displays the current user's display name or a link to login that opens a login form in a 
 * modal.  Supports multiple types of login, currently: 'cf_basic', 'rpx', 'cf_captcha'.
 * 
 * @extends CF.widget.SimpleWidget
 * 
 * @behavior {click} .cf_logout_hover Displays the .cf_logout_popup when shown. 
 * @behavior {click} .cf_logoutBtn  Attempts to log the currently logged in user out.
 * @behavior {click} .cf_loginBtn Opens the .cf_login_modal which will display the login forms for the configured options.loginTypes
 * 
 */

CF.widget.UserLoginStatus = function (targetElem, template, templateEngine, data, opts)
{
	
	var defaultOpts = {
				loginTypes:["cf_basic"] // currently supported ['cf_basic', 'rpx', 'cf_captcha']
	};
	opts = CF.extend(defaultOpts, opts);
	
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
		
	that.onReload = function ()
    {
    	that.draw();
	};
	that.onStart = function ()
	{
		that.draw();
	};
	that.getDefaultTemplateBody = function ()
	{
		return " \
<div class='cf_if' binding='authUser'>\
	<div class='cf_current_user cf_logout_hover'><a>[% authUser.display_name || 'unknown user' %]</a>\
		<div class='cf_logout_popup' style='display:none;'>\
			<a class='cf_logoutBtn'>Logout</a>\
		</div>\
	</div>\
	<div class='cf_else'> \
		<a class='cf_loginBtn'>Login</a>\
		<div class='cf_noprocess cf_login_modal' style='display:none;'>\
			<div class='cf_for' rendertag='false' binding='opts.loginTypes'>\
				<div class='cf_item' rendertag='false'>\
					<div class='cf_choice cf_logintype' binding='item'>\
						<div class='cf_condition' eq_s='cf_basic'>\
							<div class='cf_widgetLoader' widgettype='CF.widget.BasicLogin' options='parent.opts'></div>\
						</div>\
						<div class='cf_condition' eq_s='cf_captcha'>\
							<div class='cf_widgetLoader' widgettype='CF.widget.CaptchaShowLink' options='parent.opts'></div>\
						</div>\
						<div class='cf_condition' eq_s='rpx'>\
							<div class='cf_widgetLoader' widgettype='CF.widget.RPXLogin' options='parent.opts'></div>\
						</div>\
					</div>\
				</div>\
			</div>\
		</div>\
	</div>\
</div>";
	};
	that.hoverIn = function ()
	{
		that.logoutPopup.fadeIn();
	};
	that.hoverOut = function()
	{
		that.logoutPopup.fadeOut();
	};
	that.bindEvents = function (elem, subWidgets)
	{
		that.logoutPopup = elem.find(".cf_logout_popup");
		elem.find(".cf_logoutBtn").click(that.processLogout);
		elem.find(".cf_logout_hover").hover(that.hoverIn, that.hoverOut);
		that.loginModal = elem.find(".cf_login_modal");
		elem.find(".cf_loginBtn").click(that.showModal);
	};

	that.showModal = function ()
	{
		CF.login.events.listen("login_success", CF.modal.hide);
		CF.modal.show(that.loginModal.clone().removeClass("cf_noprocess"), that.getData(), {width:450});
	};
	that.processLogout = function ()
	{
		CF.login.logout();
	};
	that.getData = function ()
	{
		return {authUser:CF.context.auth_user, opts:opts};
	};
	return that;	
};

//Including: CF.widget.Comment.js
//= require <CF.widget.SimpleWidget.js>
/**
 * @class
 * A widget for rendering a single comment.  This is best used inside a comment list widget like CF.widget.EntityComments
 *
 * @extends CF.widget.SimpleWidget
 * @param {element} targetElem   The element or jQuery object that the widget will be rendered into.
 * 
 * @behavior {click} .cf_activate_reply Fires the comment_reply_activated event.
 * 
 * 
 */
CF.widget.Comment = 
function (targetElem, template, templateEngine, data, opts)
{
	if (!data)
	{
		CF.error("The data parameter is required for a CF.widget.Comment", "The data parameter should be a comment object.");
	}
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	that.getDefaultTemplateBody = function ()
	{
	 return "<div class='cf_widgetLoader' widgettype='CF.widget.UserProfile' data='comment.user' options='{showPopup:false}'></div> \
     <p>[% comment.body %]</p> \
     <div class='cf_date'>[% CF.friendlyDate(comment.created) %]</div> \
     <div class='cf_comment_actions'>\
     	 <div class='cf_if' binding='authUser && opts.flaggable'>\
    	 <span class='cf_widgetLoader' widgettype='CF.widget.AddFlag' data='comment.id' options='{type:\"comment\"}'>\
	     	</span>\
	     </div>\
	     <div class='cf_if' binding='authUser && opts.nested'> \
	   		<a class='cf_activate_reply' binding='item'> \
	   			Reply to [% comment.user.display_name %] \
	   		</a>\
	     </div>\
     </div>\
     <div class='cf_clear'> </div>";
	};
	that.activated = function ()
	{
		/**
		 * @name CF.widget.Comment#comment_reply_activated
		 * @event
		 * @description
		 * Fired when activated() is called. 
		 * @param {Object} comment The comment that is displayed for this widget.
		 * @param {CF.widget.Comment} The current widget object.
		 */
		that.events.fire("comment_reply_activated", that.getData().comment, that);
	};
	that.bindEvents = function (elem)
	{
		elem.find(".cf_activate_reply").click(that.activated);
	};
	that.getData = function ()
	{
		return {comment:data, opts:opts, authUser:CF.context.auth_user};
	};
	return that;
};

//Including: CF.widget.StarUserRating.js

//Including: CF.widget.UserRating.js
//= require <CF.widget.SimpleWidget.js>
//= require <CF.context.js>

/**
 * @class
 * A widget for displaying and editing ratings on users.  The default template is a thumbs up/thumbs down
 * widget.
 * @extends CF.widget.SimpleWidget
 * 
 * @behavior {click} .cf_vote When clicked, a vote is registered for the user with a value equal to the 'voteval' attribute of the
 * clicked element. The new rating's value will be rounded as per the roundTo option and will be within the options minVal and maxVal.
 * If the new rating is successfully created or updated, the rating_created event will be fired.
 *
 * @description
 * The opts parameter has 5 properties that can be set.<br/>
 * minVal: the minimum value for the rating (default:0)<br/>
 * maxVal: the maximum value for a rating (default:0)<br/>
 * roundTo: the amount to round the results, eg .25 to round to nearest quarter or 10 to round to the nearest product of 10 (default:1)<br/>
 * canRate: If set false, the rating widget will display only and not allow more votes.<br/>
 * category: The ratingType category to rate with.  
 * @see <a href='http://docs.crowdfactory.com/version/current/rest/v1_rating_user_create.html'>The rating/user/create/ documentation</a>
 */

CF.widget.UserRating = function (targetElem, template, templateEngine, data, opts)
{
	var defaultOpts = {
		minVal: 0,
		maxVal: 1,
		roundTo: 1,
		canRate:true,
		category:"ThumbsUp",
		syndicate:false,
		syndiationUrl:location.href,
		syndicationCategory:"rating_user",
		hovertime:5000
	};
	opts = CF.extend(defaultOpts, opts);	
	
	var that = CF.widget.BaseRating(targetElem, template, templateEngine,data, opts);

	that.sendRating = function (val)
	{
		CF.context.api_v1.rating_user_create(that.ratingCreated, that.userId, opts.category, val);
	};	
	that.fetchUser = function ()
	{
		CF.context.api_v1.user_get(that.handleFetchUser, that.userId, {rating:opts.category});
	};
	that.getData = function ()
	{
		return {user:that.user, rating:that.rating, opts:opts, authUser:CF.context.auth_user}; 
	};
	that.populateUser = function (user)
	{
		that.rating = CF.arrayFind(user.user_ratings, function (i, r){
			if(r.category == opts.category)
				return r;
		});
		if (that.rating)
		{
			that.user = user;
			that.userId = user.external_id;
		}
	};
	
	that.handleFetchUser = function (user, error)
	{
		if(!error)
		{
			that.populateUser(user);
			that.draw();
		}
		else
		{
			//TODO: Handle error;
		}
	};
	
	that.publish = function (user, error)
	{
		var providers = that.getActiveSyndProviders();
		var value = "" + that.newRating;
		that.syndicate(providers, opts.syndicationCategory, that.userId, opts.syndicationUrl,value,that.syndComplete);
		that.hideHoverBox();
	};
	
	/**
	 * @description
	 * On reload, the user will always be refetched.
	 */
    that.onReload = function ()
    {
    	that.fetchUser();
	};
	/**
	 * @description
	 * On start, an user will be fetched if only the id was passed in
	 */
	that.onStart = function ()
	{
		if (!that.user)
		{
			that.fetchUser();
		}
		else
		{
			that.draw();
		}
	};
	
	//On object constructed to set up data.
	if (typeof data == 'object')
	{
		if(data.user_ratings)
		{
			that.populateUser(data);
		}
		that.userId = data.external_id;
	}
	else
	{
		that.userId = data;
	}
	if(!that.userId)
	{
		CF.error("Cannot create UserRating, invalid data parameter set.");
		return null;
	}
	
	return that;

};
//= require <CF.widget.UserRating.js>
/***
 * @class
 * A "Stars" rating widget for user.  
 * It uses a kind of multi-layered bar graph approach with an image mask above it to make the stars, and 
 * could easily be adapted to use a different image mask for a different appearance (eg, check marks instead of stars).
 * The stars display implemented as CSS.  Make sure you have included the CSS and its images from the examples.
 *  
 * @extends CF.widget.UserRating
 * @description
 * The opts parameter behaves as {@link CF.widget.UserRating} with the following additional properties:<br/>
 * width: the width in pixels of the stars rating box.<br/>
 * opacity: the opacity to set with the .cf_opacity behavior.
 * 
 * @behavior {mousemove} .cf_hover_select_rating Moves the user's rating bar when they have their mouse over the rating.
 * @behavior {click} .cf_hover_select_rating Sets a rating when the rating is clicked.
 * @behavior {mouseout} .cf_hover_select_rating Returns the user's rating bar to the stored value when they no longer have their mouse over the ratings bar;
 * @behavior .cf_opacity sets the opacity of any elements to the value of opacity from the opts parameter.
 */
CF.widget.StarUserRating = 
function (targetElem, template, templateEngine, data, opts)
	{		
		var defaultOpts = {
			width : 83,
			minVal: 0,
			maxVal: 5,
			roundTo: .5,
			opacity: .7,
			canRate:true,
			category:"Stars"
		};
		opts = CF.extend(defaultOpts, opts);
		
		var that = CF.widget.UserRating(targetElem, template, templateEngine, data, opts);
		that = CF.widget.StarRatingMixin(that, targetElem, template, templateEngine, data, opts);

		that.superPopulateUser = that.populateUser;
		that.populateUser = function (user)
		{
			that.superPopulateUser(user);
			if (that.rating)
			{
				that.rating.average_rating = that.round(that.rating.average_rating, opts.roundTo);
				that.rating.user_rating = that.round(that.rating.user_rating, opts.roundTo);
				that.setRatings(that.rating.average_rating, that.rating.user_rating);
			}
		};
			//Set up the values
		if (that.user)
		{
			that.populateEntity(that.user);
		}
		
		return that;
	};

//Including: CF.widget.UserProfile.js
//= require <CF.widget.SimpleWidget.js>
/**
 * @class
 * A widget for rendering a user's profile. 
 * @extends CF.widget.SimpleWidget
 * 
 * @behavior {click} .cf_activate_user Activates a user.  If the option showPopup is true, the user's detail
 * information is shown in a .cf_profile_popup class element.  The event userprofile_activated is also fired.
 * @behavior {click} .cf_close_popup_btn Hides the popup element.
 * 
 */
CF.widget.UserProfile = function (targetElem, template, templateEngine, data, opts)
{
	var defaultOpts = {
			showPopup:true
		};
	opts = CF.extend(defaultOpts, (opts || {}));
	
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	if(!data)
	{
		CF.error("UserProfile: the data parameter must be set to an user or userId", data);
		return null;
	}
	that.onStart = function ()
	{
		if (typeof data == 'string')
		{
			that.userId = data;
			that.fetchUser();
		}
		else
		{
			that.user = data;
			that.draw();
		}
	};
	that.onReload = function()
	{
		that.fetchUser();
	};	
	that.fetchUser = function ()
	{
		CF.context.api_v1.user_get(that.userLoaded, that.userId);
	};
	that.isSelf = function()
	{
		var authUser = CF.context.auth_user;
		return authUser && that.userId == authUser.external_id;
	};
	that.userLoaded = function (user, error)
	{
		if(error)
		{
			CF.error("Error fetching user", error);
			return;
		}
		that.user = user;
		that.userId = user.external_id;
		that.draw();
	};
	that.getData = function ()
	{
		return {user:that.user, opts:opts, isSelf:that.isSelf()};
	};	
	that.getDefaultTemplateBody = function ()
	{
		return " \
		<div class='cf_avatar'> \
				<div class='cf_if' binding='user.profile_photo_url'> \
				<a class='cf_activate_user'> \
					<img cf_src='[% user.profile_photo_url %]'/> \
				</a> \
		    </div> \
		</div> \
		<cite class='cf_activate_user'> \
		 	<a>[% user.display_name %]</a> \
		</cite> \
		<div class='cf_profile_popup' style='display:none;'> \
		 	<h4>[%user.display_name%] details <span class='cf_close_popup_btn'>x</span></h4> \
		 	<div class='cf_profile_popup_details'> \
			 	<div class='cf_if' binding='!user.badges'> \
			 		<div>This user has no badges</div> \
			 		<div class='cf_else'> \
				 		<div class='cf_for' binding='user.badges' rendertag=false> \
					 		<div class='cf_item cf_badge'> \
					 			<img cf_src='[%item.img_url%]'></img><a cf_href='[item.url]'>[%item.name%]</a> \
					 		</div> \
				 		</div> \
			 		</div> \
			 	</div> \
			 	<span class='cf_if' binding='user.profile_url'> \
			 		<div><a cf_href='[%user.profile_url%]'>Visit [%user.display_name%]'s page</a></div> \
			 	</span> \
		 	</div> \
		 </div>"; 		 
	};
	that.activate = function()
	{
		/**
		 * @name CF.widget.UserProfile#userprofile_activated
		 * @event
		 * @description
		 * Fired when a user is activated. 
		 * @param {Object} The user whose profile is being viewed.
		 * @param {CF.widget.UserProfile} The current widget object.
		 */
		that.events.fire("userprofile_activated", that.user, that);
		if(opts.showPopup)
		{
			that.popupElem.fadeIn();
		}
	};
	that.hidePopup = function ()
	{
		that.popupElem.hide();
	};
	that.bindEvents = function (elem, subWidgets)
	{
		elem.find(".cf_activate_user").click(that.activate);
		that.popupElem = elem.find(".cf_profile_popup");
		elem.find(".cf_close_popup_btn").click(that.hidePopup);
	};
	return that;
};

//Including: CF.widget.RegForm.js
//= require <CF.widget.SimpleWidget.js>
//= require <CF.widget.SyndicationMixin.js>
//= require <CF.login.js>
//= require <CF.context.js>

/**
 * @class
 * The widget for updating a user's profile over B2C.
 * This form works by convention that textual form fields (input, textarea) with an id that is prefixed with cf_ will have their
 * value sent as parameters to the rest/v1/loginreg/register function as the suffix of their id field.
 * 
 * The data field MUST be passed set to an object with a "provider" property that is set to the loginreg provider that is 
 * will be used to process the registration request.
 * 
 * @extends CF.widget.SimpleWidget
 * @extends CF.widget.SyndicationMixin
 */

CF.widget.RegForm = function(targetElem, template, templateEngine, data, opts){
	
	opts = CF.extend({syndicate:false, syndicationUrl:location.href, syndicationCategory:"registration"}, opts);
	
	if (!data.provider)
	{
		return null;
	}
	
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	CF.widget.SyndicationMixin(that);
	that.onReload = function(){
		that.draw();
	};
	that.onStart = function(){
		that.draw();
	};
	that.getDefaultTemplateBody = function(){
		return "<div class='cf_regform cf_if' binding='user' rendertag='true'> \
					<h2>Create your Profile</h2>\
					<div class='cf_widgetLoader cf_syndication_icons' widgettype='CF.widget.SyndicationIcons'></div>\
					<div class='cf_if' binding='requirePass'>\
						<div class='cf_regform_row'>\
							<label for='cf_username'>Username:</label>\
							<input type='text' id='cf_external_id' class='cf_validate' validator='required' validator_msg='Please enter a username'></input>\
							<div class='cf_clear'> </div>\
						</div>\
						<div class='cf_regform_row'>\
							<label for='cf_password'>Password:</label>\
							<input type='password' id='cf_password' class='cf_validate' validator='required' validator_msg='Please enter a password' value=''></input>\
							<div class='cf_clear'> </div>\
						</div>\
					</div>\
					<div class='cf_regform_row'>\
						<label for='cf_display_name'>Display name:</label>\
						<input type='text' id='cf_display_name' class='cf_validate' validator='required' validator_msg='Please enter a display name' value='[%user.display_name%]'></input>\
						<div class='cf_clear'> </div>\
					</div>\
					<div class='cf_regform_row'>\
						<label for='cf_email'>Email address:</label>\
						<input type='text' id='cf_email' class='cf_validate cf_required' validator='email' validator_msg='Please enter a valid email address' value='[%user.email%]'></input>\
						<div class='cf_clear'> </div>\
					</div>\
					<div class='cf_regform_row'>\
						<label for='cf_email'>First name:</label>\
						<input type='text' id='cf_first_name' class='cf_validate' validator='required' validator_msg='Please enter your first name' value='[%user.firstName%]'></input>\
						<div class='cf_clear'> </div>\
					</div>\
					<div class='cf_regform_row'>\
						<label for='cf_email'>Last name:</label>\
						<input type='text' id='cf_last_name' class='cf_validate' validator='required' validator_msg='Please enter your last name' value='[%user.lastName%]'></input>\
						<div class='cf_clear'> </div>\
					</div>\
					<div class='cf_regform_row'>\
						<label for='cf_profile_photo_url'>Avatar:</label>\
						<input type='text' id='cf_profile_photo_url' class='cf_validate' validator='imageUrl' validator_msg='Please enter a valid avatar url' value='[%user.profile_photo_url%]'></input>\
						<div class='cf_if' binding='user.profile_photo_url'>\
							<img class='cf_avatar_preview' src='[%user.profile_photo_url%]'></img>\
						</div>\
						<div class='cf_clear'> </div>\
					</div>\
					<div class='cf_regform_error_msg'></div>\
					<div class='cf_regform_row cf_regform_syndicate' style='display:none;'>\
							<label> </label>\
							<input type='checkbox' class='cf_regform_syndicate_cbx' checked='checked'>Let my friends know about this site\
					</div>\
					<div class='cf_regform_button_row'>\
						<button type='button' class='cf_regform_submit'>Save</button>\
					</div>\
					<div class='cf_else'>\
						You must be logged in with an account in the 'Unverified' state to use this widget.\
					</div>\
				</div>\
		";
	};
	that.getData = function(){
	    var d = {};
	    CF.extend(d, data);
	    d.opts = opts;
	    d.user = CF.context.auth_user;
		return d;
	};
	that.updateAvatar = function ()
	{
		that.avatarPreview.attr("src", that.profilePhotoUrl.val());
	};
	that.processForm = function ()
	{
		that.errorMsg.html("");
		if (CF.validate.run(targetElem)) {
			var params = {};
			targetElem.find("input, textarea").each(function(i, elem){
				elem = jQuery(elem);
				var val = elem.val();
				var id = elem.attr("id");
				if (val && id) 
					params[id.replace("cf_", "")] = val;
			});
			params.provider = data.provider;
			CF.context.api_v1.loginreg_register(that.formComplete, params);
		}
		else{
			/**
			 * @name CF.widget.RegForm#regform_validate_failed
			 * @event
			 * @description
			 * Fired when the user has entered something incorrectly on the registration form.
			 * @param {CF.widget.RegForm} The current widget object.
			 */
			that.events.fire("regform_validate_failed", that);
		}
	};
	that.formComplete = function (user, error)
	{
		if (error)
		{
			var errElem;
			if(error.error_code == 164)
			{
				errElem = CF.build("div", ["The username you've selected is already taken.  Please select a different username."]);			
			}
			else{
				errElem = CF.build("div", "Error: "+ error.error_str + " - "+ error.error_detail);
			}
			errElem.hide();
			that.errorMsg.append(errElem);
			errElem.fadeIn();			
			/**
			 * @name CF.widget.RegForm#regform_error
			 * @event
			 * @description
			 * Fired when the server had an error processing the registration form.  The most common reason for this is that the external id that the user passed in is already taken.
			 * @param {Error} The error object for the error.
			 * @param {CF.widget.RegForm} The current widget object.
			 */
			that.events.fire("regform_error", error, that);
		}
		else
		{
			if(opts.syndicate && that.isSyndicated)
			{
				var provs = that.getActiveSyndProviderNames();
				that.syndicate(provs, opts.syndicationCategory, data.user.external_id, opts.syndicationUrl, null, that.syndicationComplete);
			}
			
			/**
			 * @name CF.widget.RegForm#regform_complete
			 * @event
			 * @description
			 * Fired when the user has successfully completed the registration form
			 * @param {Object} user The new user object with its fields updated to reflect the user's input.
			 * @param {CF.widget.RegForm} The current widget object.
			 */
			that.events.fire("regform_complete", user, that);	
		}		
	};
	that.syndicationComplete = function (synd, error)
	{
		if (error) 
			CF.error("Error performing syndication event", error);
		else {
			CF.log("Syndication completed", synd);
			that.events.fire("regform_syndication_complete", synd, that);
		}
	};
	that.toggleSyndicate = function(){
		that.isSyndicated = this.checked;	
	};
	that.bindEvents = function(elem, subWidgets)
	{
		that.avatarPreview = elem.find(".cf_avatar_preview");
		that.profilePhotoUrl = elem.find("#cf_profile_photo_url").blur(that.updateAvatar);
		elem.find(".cf_regform_submit").click(that.processForm);
		that.errorMsg = elem.find(".cf_regform_error_msg");
		if (that.canSyndicate())
		{
			that.isSyndicated = true;
			that.syndElems = elem.find(".cf_regform_syndicate").show();
			that.syndIcon = elem.find(".cf_regform_syndicate_cbx").click(that.toggleSyndicate);		
		}
	};
	return that;	
};

//Including: CF.widget.UserComments.js
//= require <CF.widget.BaseComments.js>
/**
 * @class
 * @extends CF.widget.BaseComments
 * @description A widget for creating lists of comments on users.
 */
CF.widget.UserComments = function (targetElem, template, templateEngine, data, opts)
{
	var that = CF.widget.BaseComments(targetElem, template, templateEngine, data, opts);
	if(data && typeof data === 'object')
		data = data.external_id;
	if(!data)
	{
		CF.error("UserComments: the data parameter must be set to a user or a user's id", data);
		return null;
	}
	that.fetchComments = function()
	{
		CF.context.api_v1.comment_user_get(that.commentListLoaded, data, that.updateParams(that.params));
	};
	that.postComment = function ()
	{
		var commentBody = that.commentTxt.val();
		if (commentBody)
		{
			var params = {};
			if (that.responseToComment)
			{
				params.parent= that.responseToComment.id;	
			}
			CF.context.api_v1.comment_user_create(that.commentPosted, data, commentBody, params);
		}
	};
	return that;
};

//Including: CF.widget.StarRatingMixin.js

/**
 * @Class
 * A mixin class that adds star rating capabilities to another class instance.
 */
CF.widget.StarRatingMixin = function (that, targetElem, template, templateEngine, data, opts)
{
	that.scaleFactor = (opts.width / (opts.maxVal - opts.minVal));
	that.superBindEvents = that.bindEvents;
	that.bindEvents = function (elem, subWidgets)
	{
		that.superBindEvents(elem, subWidgets);
		that.avgElem = elem.find(".cf_rating_avg");
		that.selectedElem = elem.find(".cf_rating_selected");
		that.maskElem = elem.find(".cf_rating_mask");
		elem.find(".cf_opacity").css("opacity", opts.opacity);
		that.computeWidths();
		if (CF.context.auth_user && opts.canRate)
		{
			that.containerElem = elem.find(".cf_hover_select_rating").mousemove(that.selectionMoved).click(that.selectionClicked).mouseout(that.mouseout);	
		}
	};
	that.getDefaultTemplateBody = function()
	{
		return " \
		<span class='cf_rating'> \
			<div class='cf_hover_select_rating'>\
				<div class='cf_rating_avg'></div>\
				<div class='cf_rating_selected cf_opacity'></div>\
				<div class='cf_rating_mask'></div>\
			</div>\
			<span class='cf_average'>\
				Average:[% rating.average_rating %]/[% opts.maxVal %] Votes: [% rating.count %]\
			</span>\
			<div class='cf_rating_hoverbox' style='display:none'>\
			<h4>Your rating has been counted</h4>\
				<a class='cf_rating_synd_btn'>Publish to: </a>\
				<span class='cf_widgetLoader cf_syndication_icons' widgetType='CF.widget.SyndicationIcons'></span>\
			</div>\
		</span>\
		";	
	};		
	that.mouseout = function (e)
	{
		that.setRatings(that.avgVal, that.selectedVal);
	};
	that.selectionMoved = function (e)
	{
		if (opts.canRate)
		{
			var offset = targetElem.offset();
			var mouseX = e.pageX - offset.left;
			that.selectedElem.width(mouseX);
		}
	};
	that.selectionClicked = function (e)
	{
		if (opts.canRate)
		{
			var offset = targetElem.offset();
			var clickX = e.pageX - offset.left;
			var newX = Math.round(clickX);
			that.newRating = that.round((newX/that.scaleFactor), opts.roundTo);
			that.sendRating(that.newRating);
		}
	};
	that.computeWidths = function ()
	{
		that.avgElem.width(Math.round(that.scaleFactor * that.avgVal));
		that.selectedElem.width(Math.round(that.scaleFactor * that.selectedVal));
	};
	that.clearRatings = function ()
	{
		return that.setRatings(null, null);
	};
	that.setRatings = function (avgVal, selectedVal)
	{
		that.avgVal = (avgVal || opts.minVal);
		that.selectedVal = (selectedVal || opts.minVal);
		if(that.containerElem)
		{
			that.computeWidths();
		}
	};
	return that;
};

//Including: CF.widget.StarEntityRating.js
//= require <CF.widget.EntityRating.js>
/***
 * @class
 * A "Stars" rating widget for entities.  
 * It uses a kind of multi-layered bar graph approach with an image mask above it to make the stars, and 
 * could easily be adapted to use a different image mask for a different appearance (eg, check marks instead of stars).
 * The stars display implemented as CSS.  Make sure you have included the CSS and its images from the examples.
 *  
 * @extends CF.widget.EntityRating
 * @description
 * The opts parameter behaves as {@link CF.widget.EntityRating} with the following additional properties:<br/>
 * width: the width in pixels of the stars rating box.<br/>
 * opacity: the opacity to set with the .cf_opacity behavior.
 * 
 * @behavior {mousemove} .cf_hover_select_rating Moves the user's rating bar when they have their mouse over the rating.
 * @behavior {click} .cf_hover_select_rating Sets a rating when the rating is clicked.
 * @behavior {mouseout} .cf_hover_select_rating Returns the user's rating bar to the stored value when they no longer have their mouse over the ratings bar;
 * @behavior .cf_opacity sets the opacity of any elements to the value of opacity from the opts parameter.
 */
CF.widget.StarEntityRating = 
function (targetElem, template, templateEngine, data, opts)
	{		
		var defaultOpts = {
			width : 83,
			minVal: 0,
			maxVal: 5,
			roundTo: .5,
			opacity: .7,
			canRate:true,
			category:"Stars"
		};
		opts = CF.extend(defaultOpts, opts);
		
		var that = CF.widget.EntityRating(targetElem, template, templateEngine, data, opts);
		that = CF.widget.StarRatingMixin(that, targetElem, template, templateEngine, data, opts);
		
		that.superPopulateEntity = that.populateEntity;
		that.populateEntity = function (entity)
		{
			that.superPopulateEntity(entity);
			
			if (that.rating)
			{
				that.rating.average_rating = that.round(that.rating.average_rating, opts.roundTo);
				that.rating.user_rating = that.round(that.rating.user_rating, opts.roundTo);
				that.setRatings(that.rating.average_rating, that.rating.user_rating);
			}
		};
			//Set up the values
		if (that.entity)
		{
			that.populateEntity(that.entity);
		}
		return that;
	};

//Including: CF.widget.EntityBrowse.js
//= require <CF.widget.SimpleWidget.js>
//= require <CF.widget.Pageable.js>
//= require <CF.context.js>
/**
 * @class
 * A widget used to fetch multiple entities.
 * @extends CF.widget.SimpleWidget
 * @extends CF.widget.Pageable
 * @description 
 * The constructor for EntityBrowse
 * @see <a href='http://docs.crowdfactory.com/version/current/rest/v1_entity_browse.html'>The entity/browse documentation</a> 
 * For the values allowed in the opts object.
 */
CF.widget.EntityBrowse = function (targetElem, template, templateEngine, data, opts)
{
	var defaultOpts = {
		order:"MostRecentFirst", //LeastRecentFirst, MostCommented, MostRecentlyCommented, LeastRecentlyCommented, HighestRated
		offset:0,
		max_return:20
	};	
	
	opts = CF.extend(defaultOpts, opts);
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	CF.widget.Pageable(opts,that);
	
	that.onStart = function ()
	{
		that.getEntityList();
	};	
	that.onReload = function ()
	{
		that.getEntityList();
	};
	that.getEntityList = function()
	{
		that.updateParams(opts);
		CF.context.api_v1.entity_browse(that.handleListLoad, opts);
	};	
	that.pageChanged = function (){
		that.reload();
	};
	that.handleListLoad = function (entities, error)
	{
		if(error)
		{
			entities = [];
		}
		that.entities = entities;	
		that.updatePager(that.entities);
		that.draw();	
	};
	that.getData = function()
	{
		return {entities:that.entities, pager:{ offset:that.getOffset(), num_page:that.getPageNum()}};
	};
	that.getDefaultTemplateBody = function ()
	{
		var templ = 
			"<div class='cf_pager_row'> \
			Current page (<span class='cf_num_page'></span>) \
			Items on page (<span class='cf_num_items'></span>)\
			<a class='cf_first_page'>First</a> <a class='cf_prev_page'>Prev</a> <a class='cf_next_page'>Next</a>\
			</div>\
				<ul class='cf_for' binding = 'entities'> \
				<li class='cf_item'>\
					<div class='cf_widgetLoader' widgettype='CF.widget.Entity' data='item'/> \
				</li> \
				<li class='cf_item_empty'>No items.</li> \
			</ul>";
		return templ;
	};
	that.bindEvents = function (elem)
	{
		that.bindPagerEvents(elem);
	};
	return that;
};

//Including: CF.widget.CrowdList.js
//= require <CF.widget.SimpleWidget.js>
//= require <CF.widget.Pageable.js>
//= require <CF.context.js>
/**
 * @class
 * A CrowdList is a list of entities that is identified by its name, category, and either a user or a group who owns it.
 * To use this widget, it must be instantiated with a its options parameter set with the fields name, category, and either user or group parameter.
 * @extends CF.widget.SimpleWidget
 * @extends CF.widget.Pageable
 * 
 */
CF.widget.CrowdList = function (targetElem, template, templateEngine, data, opts)
{
	opts = CF.extend({offset:0, max_return:20, isUserList:true}, opts);
	
	if(opts.isUserList)
		opts.user = (typeof data ==='object') ? data.external_id : data;
	else
		opts.group = (typeof data === 'object') ? data.id : data;
	
	if(!opts.user && !opts.group)
	{
		CF.error("CrowdList: The data parameter must be set to either a user or a group", data);
		return null;
	}	
	if(!opts.category || !opts.name)
	{
		CF.error("Category, and name are required options for the CrowdList widget");
	}
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	CF.widget.Pageable(opts,that);
	
	that.onStart = function ()
	{
		that.onReload();
	};	
	
	that.onReload = function ()
	{
		var params = that.updateParams(opts);
		CF.context.api_v1.list_get(that.handleListLoad, opts.category, opts.name, opts);
	};	
	that.pageChanged = function (){
		that.reload();
	};
	that.handleListLoad = function (list, error)
	{
		if(error)
		{
			list = {};
			list.items = [];
		}
		that.list = list;	
		that.updatePager(list.items);
		that.draw();	
	};
	that.getData = function()
	{
		return {list:that.list, pager:{ offset:that.getOffset(), num_page:that.getPageNum()}, items:that.list.items};
	};
	that.getDefaultTemplateBody = function ()
	{
		var templ = 
		"<div>Total Items ([%list.item_count%]) Current page (<span class='cf_num_page'></span>) \
		Items on page (<span class='cf_num_items'></span>)\
		<a class='cf_first_page'>First</a> <a class='cf_prev_page'>Prev</a> <a class='cf_next_page'>Next</a>\
		</div>\
			<ul class='cf_for' binding = 'items'> \
			<li class='cf_item'>\
				<div class='cf_widgetLoader' widgettype='CF.widget.Entity' data='item.ExternalEntity'></div>\
			</li> \
			<li class='cf_item_empty'>No items.</li> \
		</ul>";
		return templ;
	};
	that.removeFromList = function (evt)
	{
		var jq = jQuery(this);
		CF.context.api_v1.list_entity_remove( that.reload, jq.attr("entity_id"), opts.category, opts.name);
	};
	that.bindEvents = function (elem)
	{
		elem.find(".cf_list_remove").click(that.removeFromList);
		that.bindPagerEvents(elem);
	};
	return that;
};

//Including: CF.widget.CaptchaLogin.js
//=  require <CF.widget.SimpleWidget.js>
//=  require <CF.context.js>
/**
 * @class
 * A widget for displaying a captcha that gates access to the RegForm 
 * 
 * 
 * @extends CF.widget.SimpleWidget
 */

CF.widget.CaptchaLogin = function (targetElem, template, templateEngine, data, opts)
{
	opts = opts || {};
	var defaultOpts = {
		scriptSrc:"http://api.recaptcha.net/js/recaptcha_ajax.js",
		key:"6Lc-fAcAAAAAAJhL3j6CpAswfjvdgfaNood9j5n7",
		theme:"red"	
	};
	opts = CF.extend(defaultOpts, opts);
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	that.onReload = function ()
    {
		Recaptcha.destroy();
		that.draw();
	};
	that.onStart = function ()
	{
		if(!window.Recaptcha)
		{
			jQuery.getScript(opts.scriptSrc, that.draw);
		}
		else
		{that.draw();}
	};
	that.onRemove = function ()
	{
		Recaptcha.destroy();
	};	
	that.getDefaultTemplateBody = function ()
	{
		return "\
				<div class='cf_captcha_body'>\
					<h2>Prove your humanity</h2>\
					<p>In order to create an account, you must first prove that you are not a robot.</p>\
					<p>Please solve this captcha.</p>\
					<div id='recaptcha_div'></div>\
					<div class='cf_captcha_msg'></div>\
					<button class='cf_captcha_cancel' type='button'>Cancel</button>\
					<button class='cf_captcha_submit' type='button'>Submit Answer</button>\
				</div>\
				";
	};
	that.bindEvents = function (elem, subWidgets)
	{
		that.captchaBody = elem.find(".cf_captcha_body");
		that.captchaMsg = elem.find(".cf_captcha_msg");
		that.captchaSubmit = elem.find(".cf_captcha_submit").click(that.submitCaptcha);
		that.captchaCancel = elem.find(".cf_captcha_cancel").click(that.cancelCaptcha);
		Recaptcha.create(opts.key,
			"recaptcha_div", {
			   theme: opts.theme,
			   callback: Recaptcha.focus_response_field
			});
	};
	that.cancelCaptcha = function ()
	{
		/**
		 * @name CF.widget.CaptchaLogin#captcha_cancel
		 * @event
		 * @description
		 * Fired when the user has canceled their captcha attempt.
		 * @param {CF.widget.CaptchaLogin} The current widget object.
		*/
		that.events.fire("captcha_cancel", that);
		that.captchaBody.fadeOut();
		Recaptcha.destroy();
	};
	that.submitCaptcha = function ()
	{
		that.captchaMsg.html("");
		var token = Recaptcha.get_challenge() + "|" + Recaptcha.get_response();
		CF.context.api_v1.loginreg_auth(that.tokenOk, token, "cf-captcha");
	};
	
	that.tokenOk = function (result, error)
	{
		if (error)
		{
			/**
			 * @name CF.widget.CaptchaLogin#captcha_incorrect
			 * @event
			 * @description
			 * Fired when the user has failed a captcha attempt.
			 * @param {Error} The error passed back from the captcha.
			 * @param {CF.widget.CaptchaLogin} The current widget object.
			*/
			that.events.fire("captcha_incorrect", error, that);
			var error = CF.build("div", "Incorrect answer.  Please try again.").hide();
			that.captchaMsg.append(error);
			Recaptcha.reload();
			error.fadeIn();
		}
		else
		{
			/**
			 * @name CF.widget.CaptchaLogin#captcha_success
			 * @event
			 * @description
			 * Fired when the user has passed a captcha attempt.
			 * @param {User} user The user that was created as a result of the captcha passing.
			 * @param {String} jsessionid The jsessionid of the newly logged-in user.
			 * @param {CF.widget.CaptchaLogin} The current widget object.
			*/
			that.events.fire("captcha_success", result.user, result.jsessionid, that);
		}
	};

	return that;	
};

//Including: CF.widget.BaseRating.js
//= require <CF.widget.SimpleWidget.js>
//= require <CF.context.js>

/**
 * @class
 * The base rating class for all ratings widgets.  
 * @extends CF.widget.SimpleWidget
 *
 * @behavior {click} .cf_vote When clicked, a vote is registered for the entity with a value equal to the 'voteval' attribute of the
 * clicked element. The new rating's value will be rounded as per the roundTo option and will be within the options minVal and maxVal.
 * If the new rating is successfully created or updated, the rating_created event will be fired.
 * 
 * @description
 * The opts parameter has 5 properties that can be set.<br/>
 * minVal: the minimum value for the rating (default:0)<br/>
 * maxVal: the maximum value for a rating (default:0)<br/>
 * roundTo: the amount to round the results, eg .25 to round to nearest quarter or 10 to round to the nearest product of 10 (default:1)<br/>
 * canRate: If set false, the rating widget will display only and not allow more votes.<br/>
 * category: The ratingType category to rate with.  
 * syndicate: If set true, users will be prompted to syndicate their ratings out to external social networks.
 * syndicationUrl: the URL to set to the syndication event to.  Default is current page url.
 * syndicationCategory: the category name of the syndication event.  Default of "rating"
 *  
 */

CF.widget.BaseRating = function (targetElem, template, templateEngine, data, opts)
{
	var defaultOpts = {
		minVal: 0,
		maxVal: 1,
		roundTo: 1,
		canRate:true,
		category:"ThumbsUp",
		syndicate:false,
		syndiationUrl:location.href,
		syndicationCategory:null,
		hovertime:5000
	};
	opts = CF.extend(defaultOpts, opts);	
	
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine);
	CF.widget.SyndicationMixin(that);
	
	that.avgVal = opts.minVal;
	that.selectedVal = opts.minVal;
	
	that.getDefaultTemplateBody = function ()
	{
		return "\
<span class='cf_rating'> \
	<span class='cf_choice' binding='rating.user_rating'>\
		<span class='cf_condition' lt='0' eq='0'>\
			<span class='cf_thumbs_up cf_vote cf_deselected' voteval='1'></span>\
			<span class='cf_thumbs_down cf_vote cf_selected' voteval='0'></span>\
		</span>\
		<span class='cf_condition' gt='1' eq='1'>\
			<span class='cf_thumbs_up cf_vote cf_selected' voteval='1'></span>\
			<span class='cf_thumbs_down cf_vote cf_deselected' voteval='0'></span>\
		</span>\
		<span class='cf_otherwise'>\
			<span class='cf_thumbs_up cf_vote' voteval='1'></span>\
			<span class='cf_thumbs_down cf_vote' voteval='0'></span>\
		</span>\
	</span>\
	<span class='cf_average'>\
		Average: [% rating.average_rating * 100 %]% Votes: [% rating.count %]\
	</span>\
	<div class='cf_rating_hoverbox' style='display:none'>\
		<h4>Your rating has been counted</h4>\
		<a class='cf_rating_synd_btn'>Publish to: </a>\
		<span class='cf_widgetLoader cf_syndication_icons' widgetType='CF.widget.SyndicationIcons'>  </span>\
	</div>\
</span>\
		";
	};
	
	that.bindEvents = function (elem, subWidgets)
	{
		if(CF.context.auth_user && opts.canRate)
		{
			elem.find(".cf_vote").click(that.vote);
			that.hoverbox = elem.find(".cf_rating_hoverbox").hover(that.stopHoverTimer, that.startHoverTimer);
			that.syndBtn = elem.find(".cf_rating_synd_btn").click(that.publish);
		}
	};
	
	that.round = function (num, roundTo)
	{
		var rem, ramt, ret;
		rem = num % roundTo;
		ramt = roundTo / 2;
		if (rem >= ramt)
			ret = (num - rem) + roundTo;
		else 
			ret = num - rem;
		
		return Math.max(opts.minVal, Math.min(opts.maxVal, ret));
	};
	that.vote = function ()
	{
		var voteVal = jQuery(this).attr("voteval");
		if (voteVal)
		{
			that.newRating = that.round(new Number(voteVal), opts.roundTo);
			that.sendRating(that.newRating);	
		}		
	};
	
	that.sendRating = function (val)
	{
		//Call that.ratingCreated when complete.
		//EG: CF.context.api_v1.rating_entity_create(that.ratingCreated, that.entity.uid, opts.category, val);
		CF.error("sendRating must be overridden in child classes of CF.widget.BaseRating");
	};
	
	that.startHoverTimer = function ()
	{
		if(!that.timer)
			that.timer = setTimeout(that.hideHoverBox,opts.hovertime);
	};
	that.stopHoverTimer = function ()
	{
		if (that.timer)
			clearTimeout(that.timer);
		that.timer = null;
	};
	that.hideHoverBox = function(){
		that.timer = null;
		that.hoverbox.fadeOut(that.reload);
	};
	that.publish = function ()
	{
		CF.error("publish must be overridden in child classes of CF.widget.BaseRating");
		//var providers = that.getActiveSyndProviders();
		//that.syndicate(providers, opts.syndicationCategory, entityId, opts.syndicationUrl,that.syndComplete)
	};
	that.syndComplete = function (result, error)
	{
		
	};
	
	/**
	 * @name CF.widget.BaseRating#rating_created
	 * @event
	 * @description
	 * The rating_created event is fired when a rating has been succesfully created.
	 * @parameter {Object} The entity object that was rated. 
	 * @parameter {CF.widget.BaseRating} The current widget object.
	 */
	that.ratingCreated = function (result, error)
	{
		if (!error)
		{
			that.events.fire("rating_created", that.entity, that);
			if (opts.syndicate && that.canSyndicate())
			{
				that.startHoverTimer();
				that.hoverbox.fadeIn();
			}
			else
				that.reload();
		}
	};
	return that;
};

//Including: CF.widget.GalleryCrowdList.js
//= require <CF.widget.SimpleWidget.js>
//= require <CF.context.js>
//= require <CF.modal.js>

/**
 * @class
 * Extends the normal CrowdList to add some graphical flourishes for a "gallery" view.
 * Listens for nested CF.widget.Entity widget entity_activated events to trigger a modal
 * "lightbox" style effect to display the nested entity.
 * 
 * @extends CF.widget.CrowdList
 * @description 
 * You can pass options to the CF.modal object by setting a modalOpts Object property of the opts parameter.
 * Otherwise, the opts parameter behaves as in the {@link CF.widget.CrowdList}.
 * 
 * @behavior {hover} .cf_prev_page Fades in and fades out any .cf_prev_page_hover elements when hovered.
 * @behavior {hover} .cf_next_page Fades in and fades out any .cf_next_page_hover elements when hovered.
 * 
 * @usage
 * This class is heavily stylesheet reliant.  Here is an example stylesheet.
 * 
 * .cf_widgetLoader {display:none;}
	.cf_modal_bg {background-color:#FFF}
	.cf_modal_closer {float:right; font-size:25px; color:#666; cursor:pointer; margin-top:10px;}
	.cf_modal_header {text-align:center; height:40px;}
	.cf_modal_body {padding:0 10px 10px; background-color:#000; color:#666;}
	.cf_modal_load {margin:auto; width:31px; height:31px; background-image:url(../../images/greyspinner.gif);}
		
	.cf_gallerypane {background-color:#000; color:#888; height:420px; width:502px;}
	.cf_gallerypane .cf_gallerypage { text-align:right; line-height:30px; height:30px; border-bottom:1px solid #444;}
	.cf_gallerypane .cf_gallerybody{ width:390px; margin-left:56px;}
	.cf_gallerypane .cf_gallerybody .cf_galleryitem { margin:10px;float:left;}
	
	.cf_gallerypane .cf_imageborder {border:1px solid #444; width:100px; height:100px; padding:4px;}
	.cf_gallerypane .cf_entity_hover {width:100px; height:100px;}
	.cf_gallerypane .cf_entity_hover_target {color:#666; font-weight:bold;}
	.cf_gallerypane .cf_prev_page{float:left; height:420px; width:50px;}
	.cf_gallerypane .cf_prev_page .cf_prev_page_btn   {position:absolute; height:420px; width:50px; background:url(../../images/arrows-dk-left.gif) center center no-repeat;}
	.cf_gallerypane .cf_prev_page .cf_prev_page_hover {cursor:pointer; position:absolute; height:420px; width:50px; background:url(../../images/arrows-lt-left.gif) center center no-repeat;}
	.cf_gallerypane .cf_disabled_page .cf_prev_page_hover {height:0; visibility:hidden;}
	.cf_gallerypane .cf_next_page{float:right; height:420px; width:50px;}
	.cf_gallerypane .cf_next_page .cf_next_page_btn   {position:absolute; height:420px; width:50px; background:url(../../images/arrows-dk-right.gif) center center no-repeat;}
	.cf_gallerypane .cf_next_page .cf_next_page_hover {cursor:pointer; position:absolute; height:420px; width:50px; background:url(../../images/arrows-lt-right.gif) center center no-repeat;}
	.cf_gallerypane .cf_disabled_page .cf_next_page_hover {height:0; visibility:hidden;}
	.cf_gallerypane .cf_entity_hover_target { background:url(../../images/80transblack.png); text-align:center; min-height:50px; padding:2px; width:96px;border-bottom: 1px solid #444; position:absolute;}
	.cf_gallerypane .cf_entity_hover_target p{font-weight:bold; font-size:90%; color:#DDD;}
	.cf_gallerypane .cf_entity_activate{cursor:pointer;}
	
	.cf_loading_spinner {margin:0 auto; width:31px; height:31px; padding-top:195px; background:url(../../images/greyspinner.gif) no-repeat bottom;}

	//IE 6 Double margin on floats bug 
	*html .cf_gallerypane .cf_gallerybody .cf_galleryitem {display:inline;}			 

 */

CF.widget.GalleryCrowdList = function (targetElem, template, templateEngine, data, opts)
{
	opts = opts || {};
	opts.modalOpts =  opts.modalOpts || {};
	var that = CF.widget.CrowdList(targetElem, template, templateEngine, data, opts);
	
	that._hoverInBtn = function (elem)
	{
		elem.fadeIn();
	};
	that._hoverOutBtn = function (elem)
	{
		elem.fadeOut();
	};
	that.hoverInPrevPage = function ()
	{
		that._hoverInBtn(that.hoverPrevPage);
	};
	that.hoverOutPrevPage = function ()
	{
		that._hoverOutBtn(that.hoverPrevPage);
	};
	that.hoverInNextPage = function ()
	{
		that._hoverInBtn(that.hoverNextPage);
	};
	that.hoverOutNextPage = function ()
	{
		that._hoverOutBtn(that.hoverNextPage);
	};
	that.superBindEvents = that.bindEvents;
	that.bindEvents = function (elem, subWidgets)
	{
		that.superBindEvents(elem, subWidgets);
		that.hoverPrevPage = elem.find(".cf_prev_page_hover");
		that.hoverNextPage = elem.find(".cf_next_page_hover");
		that.modalTemplateElem = elem.find(".cf_modal_template");
		elem.find(".cf_prev_page").hover(that.hoverInPrevPage, that.hoverOutPrevPage);
		elem.find(".cf_next_page").hover(that.hoverInNextPage, that.hoverOutNextPage);
		jQuery.each(subWidgets, function (i, w){ 
    		if (w.type === "CF.widget.Entity")
    		{
    			w.widget.events.listen("entity_activated", that.activateFullView);
    		}
    	});
	};
	that.getDefaultTemplateBody = function ()
	{
	return "\
	<div class='cf_loading'>\
		<div class='cf_loading_spinner'></div>\
	</div>\
	<div class='cf_prev_page'>\
		<div class='cf_prev_page_btn'></div>\
		<div class='cf_prev_page_hover' style='display:none;'></div>\
	</div>\
	<div class='cf_next_page'>\
		<div class='cf_next_page_btn'></div>\
		<div class='cf_next_page_hover' style='display:none;'></div>\
	</div>\
	<div class='cf_gallerybody'>\
		<div class='cf_gallerypage'>\
			Showing [% pager.offset + 1 %] to [% items.length + pager.offset %] of [% list.item_count %]\
		</div>\
		<div class='cf_for' binding = 'items'>\
			<div class='cf_item cf_galleryitem'>\
				<div class='cf_widgetLoader cf_imageborder' widgettype='CF.widget.Entity' data='item.ExternalEntity'>\
					<div class='cf_entity_hover cf_entity_activate'>\
						<div class='cf_entity_hover_target' style='display:none;'>\
							<p>[% entity.title %]</p>\
						</div> \
						<img cf_src='[% entity.image_urls.smll_s %]' />\
					</div>\
				</div>\
			</div>\
			<div class='cf_item_empty'>\
				No items on this page.\
			</div>\
		</div>\
	</div>\
	<div class='cf_widgetLoader cf_modal_template cf_noprocess' widgettype='CF.widget.Entity'>\
			<img cf_src='[% entity.image_urls.larg_r %]' />\
			<h4>[% entity.title %]</h4>\
			<h5>[% entity.description %]</h5> \
	</div>";
	};
	that.activateFullView = function (evt, entity, widget)
	{
		var elem = that.modalTemplateElem.clone().removeClass("cf_noprocess");
		that.events.fire("gallery_modal_activated", entity, elem, that);
		CF.modal.show(elem, entity, opts.modalOpts);
	};
	return that;
};

//Including: CF.widget.EntityQuery.js
//= require <CF.widget.SimpleWidget.js>
//= require <CF.widget.Pageable.js>
//= require <CF.context.js>
/**
 * @class
 * A widget used to fetch entities that have been ranked by a specific order.
 * It supports the following queryTypes: 
 * highest_rated, most_commented, most_rated, number_lists, recently_commented, and top_rated
 * @extends CF.widget.SimpleWidget
 * @extends CF.widget.Pageable
 * @description 
 * Each of the EntityQuery queryTypes may have different allowed parameters.  The appropriate 
 * parameters can be found by looking at the documentation for the rest/v1/query/entity/* apis.
 * @see <a href='http://docs.crowdfactory.com/version/current/rest/rest_api_overview.html#activityqueries'>The query/entity/* documentation</a> 
 */
CF.widget.EntityQuery = function (targetElem, template, templateEngine, data, opts)
{
	var defaultOpts = {
		queryType:"highest_rated", //most_commented, most_rated, number_lists, recently_commented, top_rated
		offset:0,
		max_return:20
	};
	opts = CF.extend(defaultOpts, opts);
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	CF.widget.Pageable(opts,that);
	
	that.onStart = function ()
	{
		that.getEntityList();
	};	
	that.onReload = function ()
	{
		that.getEntityList();
	};
	that.getEntityList = function()
	{
		try{
			var params = jQuery.extend({}, opts);
			delete params.queryType;
			params = that.updateParams(params);
			CF.context.api_v1["query_entity_"+opts.queryType](that.handleListLoad, params);
		}
		catch(e)
		{
			CF.error("Error making entity query (is queryType valid?)", e);
		}
	};	
	that.pageChanged = function (){
		that.reload();
	};
	that.handleListLoad = function (entities, error)
	{
		if(error)
		{
			entities = [];
		}
		that.entities = entities;	
		that.updatePager(that.entities);
		that.draw();	
	};
	that.getData = function()
	{
		return {entities:that.entities, pager:{ offset:that.getOffset(), num_page:that.getPageNum()}};
	};
	that.getDefaultTemplateBody = function ()
	{
		var templ = 
		"<div class='cf_pager_row'> \
		Current page (<span class='cf_num_page'></span>) \
		Items on page (<span class='cf_num_items'></span>)\
		<a class='cf_first_page'>First</a> <a class='cf_prev_page'>Prev</a> <a class='cf_next_page'>Next</a>\
		</div>\
		<ul class='cf_for' binding = 'entities'> \
			<li class='cf_item'>\
				<div class='cf_widgetLoader' widgettype='CF.widget.Entity' data='item'></div> \
			</li> \
			<li class='cf_item_empty'>No items.</li> \
		</ul>";
		return templ;
	};
	that.bindEvents = function (elem)
	{
		that.bindPagerEvents(elem);
	};
	return that;
};

//Including: CF.widget.BasicLogin.js
//=  require <CF.widget.SimpleWidget.js>
//=  require <CF.login.js>
//=  require <CF.context.js>

/**
 * @class
 * A class for simple username / password based login to the CF platform.
 * 
 * @extends CF.widget.SimpleWidget
 * 
 * @behavior {click} .cf_loginBtn Attempts to log the user in with the username and password specified by the values of the #cf_username and #cf_password elements.
 * @behavior {click} .cf_logoutBtn  Attempts to log the currently logged in user out.
 * @behavior {keydown#enterKey} #cf_password Attempts to log the user in when the enter key is pressed in the #cf_password element.
 * 
 */

CF.widget.BasicLogin = function (targetElem, template, templateEngine, data, opts)
{
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
		
	that.onReload = function ()
    {
    	that.draw();
	};
	that.onStart = function ()
	{
		that.draw();
	};
	that.getDefaultTemplateBody = function ()
	{
		return " \
				<div class='cf_if' binding='authUser'>\
					Logged in as [% authUser.display_name %] \
					<br/> \
					<a class='cf_logoutBtn'>Click here to log out</a> \
					<div class='cf_else cf_loginForm'> \
						<div class='cf_formRow'> \
							<label for='cf_username'>Username:</label> \
							<input name='cf_username' id='cf_username' type='text' /> \
							<div class='cf_clear'> </div>\
						</div> \
						<div class='cf_formRow'> \
							<label for='cf_password'>Password:</label> \
							<input type='password' name='cf_password' id='cf_password'/> \
							<div class='cf_clear'> </div>\
						</div>\
						<div class='cf_loginError'> \
							<div class='cf_if' binding='error'> \
								Incorrect username or password. \
							</div>\
						</div>\
						<div class='cf_buttonRow'> \
							<button type='button' class='cf_loginBtn'>Login</button>\
						</div>\
					</div>\
		 	</div>";
	};
	that.bindEvents = function (elem, subWidgets)
	{
		that.usernameElem = elem.find("#cf_username");
		that.passwordElem = elem.find("#cf_password").keypress(CF.enterPressed(that.processLogin));
		elem.find(".cf_loginBtn").click(that.processLogin);
		elem.find(".cf_logoutBtn").click(that.processLogout);
	};
	that.processLogout = function ()
	{
		//Hand off to the CF.login helper methods
		CF.login.logout();
	};
	that.processLogin = function ()
	{
		that.error = null;
		CF.login.events.listen("login_fail", that.setLoginError);
		CF.login.login(that.usernameElem.val(), that.passwordElem.val());
	};
	that.setLoginError = function (evt, error)
	{
		that.error = error;
		that.reload();
	};
	that.getData = function ()
	{
		return {authUser:CF.context.auth_user, error: that.error};
	};
	return that;	
};

//Including: CF.widget.CaptchaShowLink.js
//= require <CF.widget.SimpleWidget.js>
//= require <CF.modal.js>

/**
 * @class
 * Opens the CF CaptchaLogin widget in a modal when activated. * 
 * @behavior {click} .cf_captcha_start Starts the captcha modal (as designated by the cf_captcha_modal class) in a new modal. Listens for modal completion and calls CF.login.showRegForm when done.
 * @extends CF.widget.SimpleWidget
 * 
 */

CF.widget.CaptchaShowLink = function (targetElem, template, templateEngine, data, opts)
{
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);

	that.getDefaultTemplateBody = function ()
	{
		return "<a class='cf_captcha_start'>Register for an account!</a>\
				<div class='cf_captcha_modal cf_noprocess cf_widgetLoader' widgettype='CF.widget.CaptchaLogin' options='opts'></div>\
			";
	};
	that.bindEvents = function (elem, subWidgets)
	{
		elem.find(".cf_captcha_start").click(that.startCaptcha);
		that.captchaModal = elem.find(".cf_captcha_modal");		
	};
	that.fireSuccess = function(evt, user, jsessionid, widg){
		CF.login.showRegForm({
			user: user,
			requirePass: true,
			provider: "cf-captcha"
		}, jsessionid);
	};
	that.startCaptcha = function ()
	{
		var elem = that.captchaModal.clone().removeClass("cf_noprocess");
		var subWidgets = CF.modal.show(elem, {opts:opts});
		jQuery.each(subWidgets, function (i, o){
			if (o.type && o.type == "CF.widget.CaptchaLogin") {
				o.widget.events.listen("captcha_success", that.fireSuccess);
				o.widget.events.listen("captcha_cancel", CF.modal.hide);
			}
		}
		);
	};
	return that;	
};

//Including: CF.widget.AddFlag.js
//= require <CF.widget.SimpleWidget.js>
/**
 * @class
 * A class that provides a simple UI for flagging inappropriate content.
 * It allows the user to enter a reason for flagging the content and works with 
 * activityevent, board, comment, entity, and user objects.
 * 
 * @extends CF.widget.SimpleWidget
 * 
 * @behavior {click} .cf_add_flag Shows the cf_add_flag_popup element.
 * @behavior {click} .cf_add_flag_cancel Hides the cf_add_flag_popup element if it is shown.
 * @behavior {click} .cf_add_flag_send Sends a flag for the current object.  Uses the value of the text field .cf_add_flag_txt as the desc parameter.  On completion, shows the cf_flag_add_msg element and hides the cf_add_flag_popup element and fires the flag_created event.
 * 
 */
CF.widget.AddFlag = function (targetElem, template, templateEngine, data, opts)
{
	 /**
	 * @name CF.widget.AddFlag#flag_created
	 * @event
	 * @description
	 * Fired when the when a flag has been created. 
	 * @param {Object} data The data parameter passed in to the widget, it is the identifier of the object that was flagged.
	 * @param {CF.widget.Entity} The current widget object.
	 */
	var that = CF.widget.SimpleWidget(targetElem, template, templateEngine, data, opts);
	if(!data)
	{
		CF.error("The data parameter is required and must be the identifier for a flaggable object");
	}
	opts = opts || {};
	if (!CF.inList(opts.type, ["activityevent", "board", "comment", "entity", "user"]))
	{
		CF.error("Invalid or missing type option");
	}
	that.onReload = function ()
    {
    	that.draw();
	};
	that.onStart = function ()
	{
		that.draw();
	};
	that.getDefaultTemplateBody = function ()
	{
		return " \
			<div class='cf_if' binding='authUser'>\
				<a class='cf_add_flag'>Flag as Inappropriate</a>\
				<div class='cf_add_flag_popup' style='display:none;'>\
					<div class='cf_flag_create'>\
						<em>Reason for flagging:</em>\
						<textarea class='cf_add_flag_txt'>I find this content objectionable.</textarea>\
						<div class='cf_add_flag_button_row'>\
							<button type='button' class='cf_add_flag_cancel'>Cancel</button>\
							<button type='button' class='cf_add_flag_send'>Raise the Flag</button>\
						</div>\
					</div>\
					<div class='cf_flag_added_msg'>\
						<em>Your flag has been raised.</em><br/>\
						A moderator will examine the content.\
					</div>\
				</div>\
			</div>\
			";
	};
	that.showPopup = function ()
	{
		if (! that.isShown)
		{
			that.isShown =true;
			that.addFlagPopup.slideDown(function () {that.selectTxt();});
		}
		if (that.flagSent)
		{
			that.flagComplete();
		}
	};
	that.hidePopup = function ()
	{
		if (that.isShown)
		{
			that.addFlagPopup.slideUp(function(){that.isShown = false;});
		}
	};
	that.selectTxt = function ()
	{
		that.addFlagTxt.select();
	};
	that.bindEvents = function (elem, subWidgets)
	{
		that.addFlag = elem.find(".cf_add_flag").click(that.showPopup);
		that.addFlagPopup = elem.find(".cf_add_flag_popup");
		that.addFlagTxt = elem.find(".cf_add_flag_txt");
		that.addFlagCancel = elem.find(".cf_add_flag_cancel").click(that.hidePopup);
		that.addFlagSend = elem.find(".cf_add_flag_send").click(that.sendFlag);
		that.addFlagButtonRow = elem.find(".cf_add_flag_button_row");
		that.flagAddedMsg = elem.find(".cf_flag_added_msg").hide();
		that.flagCreate = elem.find(".cf_flag_create");
	};
	that.sendFlag = function ()
	{
		var t = opts.type;
		
		CF.context.api_v1["flag_"+t](that.flagComplete, data, {desc:(that.addFlagTxt.val() || "")});
	};
	that.flagComplete= function (res, error)
	{
		if(!error)
		{
			that.events.fire("flag_created", data, that);
		}
		that.flagSent = true;
		that.flagCreate.hide();
		that.flagAddedMsg.fadeIn(function (){
			setTimeout(that.hidePopup, 5000);
		});
	};
	that.getData = function ()
	{
		return {authUser:CF.context.auth_user, opts:opts};
	};
	return that;	
};
