/*
 * All comments have been removed from these files. To view licenses, attributions and comments see this url: http://nmp.newsgator.com/NGBuzz/3317/load.ashx/buzz/unpacked 
*/

(function() {
	/**
	* This file assumes that BuzzUtils.js and NGDsrLoader.js is loaded as well. Typically the BuzzLoader will take care of this. 
	*/
	if (!window.ng) {
		window.ng = {};
	}

	//Initialize the ng.buzz object
	if (!ng.buzz) {
		ng.buzz = {
			//These objects are used for tracking various bits of state...
			scriptsLoaded: window.ngbuzzScriptsLoaded || {},
			scriptsLoading: {},
			stylesLoaded: {},

			/**
			* A "static" function for late-loading a widget with minimal information. 
			* Intended to allow clients in Ajaxy situations late-load a buzz box with minimal muss and fuss, and without having to new up a buzzlet object.
			* Minimal example: <input type="button" onclick="ng.buzz.LoadWidget(7231, '0C95DC052F0341B78F525C91D376EAE6', 'buzzTarget')" />
			*
			* @param {Number} buzzId the buzzid to load (or buzz name) (required)
			* @param {String} apiToken The API token for the buzz box (required)
			* @param {String} targetId The ID of a DOM node to render the buzz box in. Because this is intended to be used after page load is complete, we can't document.write() one for ourselves. (required)
			* @param {Object} extraArgs A map of extra args to send (optional)
			* @param {String} buzzAppUrl The URL to call for info. Optional, defaults to http://nmp.newsgator.com/ngbuzz/
			*/
			LoadWidget: function(buzzId, apiToken, targetId, extraArgs, buzzAppUrl, templateId, clientPostCreateHandler) {
				this._loadWidget(buzzId, apiToken, targetId, extraArgs, buzzAppUrl, templateId,
					function(args) {
						var buzz = args.buzzObj;

						if (clientPostCreateHandler) {
							buzz.addEvent("postCreate", clientPostCreateHandler);
						}

						buzz.addEvent("postCreate", function(args) {
							args.buzzObj.render();
						});
					}
				);
			},

			/**
			* Function that actually implements the LoadWidget() functionality
			* the difference between this and LoadWidget() is that LoadWidget() runs the render() function for you.
			*/
			_loadWidget: function(buzzId, apiToken, targetId, extraArgs, buzzAppUrl, templateId, clientPostCreateHandler) {
				var args = (extraArgs ? ng_clone(extraArgs) : {});
				args = ng_mixin(args, {
					buzzId: buzzId,
					apiToken: apiToken,
					load: "LoadBuzz"

					//Note we're specifically not sending these arguments, instead we're applying them clientside before creating the widget
					//Not sending them improves Akamai cacheability
					//targetId: targetId,
					//templateId: templateId,
				});

				//callback function, once we get the settings we need to jigger them a bit and then create the actual ng.buzz.Buzzlet
				var settingsReceived = function(status, data, reason) {
					if (!status) {
						ng_debug("Failed to load settings for widget " + buzzId + ".  " + reason);
						return;
					}
					var settingsObj = data;

					//wire up the post create event, if any
					settingsObj.events = settingsObj.events || {};
					settingsObj.events.postCreate = [clientPostCreateHandler];

					//set in the targetId and templateId
					settingsObj.targetId = targetId;
					settingsObj.templateId = templateId;

					new ng.buzz.Buzzlet(settingsObj);
				};

				buzzAppUrl = buzzAppUrl || "http://nmp.newsgator.com/ngbuzz/";

				ng.dsr.bind(buzzAppUrl + "/buzz.ashx", args, settingsReceived, 30000, "ngLoadBuzzSettings_" + buzzId);
			}
		};
	}

	if (!ng.buzz.Buzzlet) {
		//Define the buzzlet constructor
		/**
		* Constructor!
		* @constructor {ng.buzz.Buzzlet}
		* @classDescription A single Buzz item
		* @return {ng.buzz.Buzzlet} 
		* @param {Number} buzzId The ID for this buzz item
		* @param {String} apiToken 
		* @param {String} templateId (Optional) a Javascript variable name or DOM node ID to use for a template
		* @param {String} targetId (Optional) a DOM node ID to render into 
		* @param {String} buzzAppUrl The base URL for the Buzz application. By default "http://nmp.newsgator.com/NGBuzz/"
		* @param {String} attnUrl DEPRECATED and not used -- was: The URL to use for attention data. Default is "http://services.newsgator.com/redirect/attn.ashx?uid={0}&fid={1}&pid={2}&t={4}"
		* @param {Number} userId  DEPRECATED The userID of the current user. Used for logging attention data.
		* @param {Object} extraArgs Extra arguments, such as debug, nocache, custom feed groups, etc
		* @param {String} orgCode The organization code for this buzz.
		* @param {Object} analyticsArgs An object specifying which client analytics to call
		* @param {Object} excessiveArgs And object containing all further arguments, because adding more args is getting ridiculous
		*/
		ng.buzz.Buzzlet = function(buzzId, apiToken, templateId, targetId, buzzAppUrl, attnUrl, notUsed /*was userId*/, extraArgs, orgCode, analyticsArgs, buzzTrackingArgs, excessiveArgs) {
			var argsObj;
			if (!buzzId) {
				return;
			} else if (!isNaN(buzzId) || buzzId.toLowerCase) {
				//Wrap up all the arguments into an args object for the construct() function
				argsObj = {
					buzzId: buzzId,
					apiToken: apiToken,
					templateId: templateId,
					targetId: targetId,
					buzzAppUrl: buzzAppUrl,
					/* attnUrl : attnUrl,*/
					/* userId : userId, */
					extraArgs: extraArgs,
					orgCode: orgCode,
					analytics: analyticsArgs,
					buzzTracking: buzzTrackingArgs,
					fbApiKey: (excessiveArgs ? excessiveArgs.fbApiKey : null)
				};
			} else {
				argsObj = buzzId;
			}

			//do the actual building
			construct.apply(this, [argsObj]);
		};

		//The "real" constructor for the widget
		function construct(args) {
			var _this = this;
			this.name = args.name || "";
			this.buzzId = args.buzzId;
			this.apiToken = args.apiToken;
			this.buzzAppUrl = args.buzzAppUrl || "http://nmp.newsgator.com/NGBuzz/";
			this.ngBaseUrl = this.buzzAppUrl.replace(/NGBuzz\/?/i, "");
			this.directUrl = args.directUrl || "http://hosted.newsgator.com/";
			this.directAppUrl = this.directUrl + "NGBuzz";
			this._extraArgs = args.extraArgs || {};
			this.orgCode = args.orgCode;
			this.buzzTracking = args.buzzTracking || {};
			this.fbApiKey = args.fbApiKey;
			this.WidgetConfiguration = args.WidgetConfiguration || {};
			this.scriptCtx = args.scriptCtx || "window";
			this.events = args.events || {};

			var extraArgs = this._extraArgs || {};

			if (extraArgs.suppressScripts) {
				ng.buzz.scriptsLoaded[this.buzzId] = true;
			}
			if (extraArgs.suppressStyles) {
				ng.buzz.stylesLoaded[this.buzzId] = true;
			}


			var t = Math.floor(Math.random() * 10000000000);


			var analyticsArgs = args.analytics || {};
			analyticsArgs.orgCode = analyticsArgs.orgCode || this.orgCode;
			analyticsArgs.buzzObj = this;
			analyticsArgs.disabled = (extraArgs.disableAnalytics == "true");
			analyticsArgs.googleDisabled = (extraArgs.disableGoogleAnalytics == "true");

			this.analytics = new ng.Analytics(analyticsArgs);

			this._targetId = args.targetId || scrubId("buzzTgt_" + this.buzzId + "_" + t);
			this._templateId = args.templateId || scrubId("buzzTemplate_" + this.buzzId);
			this.uniqueId = "buzzObj_" + t;

			//We don't need to send these back up later, so get rid of them
			delete extraArgs.templateId;
			delete extraArgs.targetId;

			//If we're debugging or blocking cache, append a random number as an extra argument
			//It's insurance since some browsers (*cough*IE7*cough*) have inconsistent caching behavior
			if (extraArgs.nocache || extraArgs.debug) {
				extraArgs.t = t;
			}

			this._dataVarName = scrubId("buzzData_" + this.buzzId + "_" + (new Date()).getTime());

			//Save a reference to this buzz object in ng.buzz.
			ng.buzz[this.uniqueId] = this;

			this.fireEvent("postCreate", { buzzObj: this });
		}

		ng.buzz.Buzzlet.prototype = {
			dataObj: null,
			postRenderCallbacks: null,

			/**
			* Create a slave widget. Note this function is deprecated, use loadSubordinateWidget() instead
			* @deprecated Deprecated because it's very difficult to set all the properites correctly, eg WidgetConfiguration
			*/
			createSlaveWidget: function(buzzId, argsObj) {
				argsObj = argsObj || {};

				function setWithDefault(name, defValue) {
					if (typeof argsObj[name] == "undefined") {
						argsObj[name] = defValue;
					}
				}

				setWithDefault("apiToken", this.apiToken);
				setWithDefault("buzzAppUrl", this.buzzAppUrl);
				setWithDefault("directUrl", this.directUrl);
				setWithDefault("orgCode", this.orgCode);
				setWithDefault("analytics", ng_clone(this.analytics));
				setWithDefault("buzzTracking", ng_clone(this.buzzTracking));
				argsObj.buzzTracking.masterBuzzId = this.buzzId;
				argsObj.buzzId = buzzId;

				//		return new ng.buzz.Buzzlet(buzzId, apiToken, argsObj.templateId, argsObj.targetId, 
				//									buzzAppUrl, null /*attnUrl*/, userId, argsObj.extraArgs, orgCode, 
				//									analyticsArgs, buzzTracking);		
				return new ng.buzz.Buzzlet(argsObj);
			},

			/**
			* Load a slave widget. Basically the same as using ng.buzz.LoadWidget(), but also sets the master buzzId appropriately.
			* Note that this function does not immediately render the widget. Users should call render() themselves.
			* @buzzId The widget to load (this can be a buzz name, if it's in the same org)
			* @targetId ID of DOM element to render the widget inside
			* @argsObj ExtraArgs map. 
			* @postCreateCallback Function to call after the widget object is instantiated, but before all other loading steps
			*/
			loadSubordinateWidget: function(buzzId, targetId, argsObj, postCreateCallback) {
				argsObj = argsObj || {};

				var masterBuzzObj = this;

				//Our own callback to wire up master buzz id
				var postCreate = function(argsObj) {
					var buzzObj = argsObj.buzzObj;

					buzzObj.addEvent("postCreate", function() {
						this.buzzTracking.masterBuzzId = masterBuzzObj.buzzId;
						this.analytics.masterBuzzId = masterBuzzObj.buzzId;
						this.analytics.buzzTrackingId = masterBuzzObj.analytics.buzzTrackingId;
						this.analytics.parentTrackingId = masterBuzzObj.analytics.parentTrackingId;
					});

					//if the user passed in a callback, make sure that gets invoked
					if (postCreateCallback) {
						buzzObj.addEvent("postCreate", postCreateCallback);
					}
				};

				ng.buzz._loadWidget(buzzId, argsObj.apiToken || this.apiToken, targetId, argsObj, this.buzzAppUrl, argsObj.templateId || null, postCreate);
			},

			/**
			* Force a refresh of the data and then a rerender
			* @memberOf {ng.buzz.Buzzlet}
			* @method refresh
			*/
			refresh: function() {
				window[this._dataVarName] = null;
				this.dataObj = null;
				return this.render();
			},

			/**
			* Forces a load of data, either from a window variable or from the server
			* @param {Function} callback An optional callback function 
			*/
			loadData: function(callback) {
				this.debug("Loading buzz data: " + this.toString());

				detectCharset(this);

				var content = { load: "data" };

				ng_mixin(content, this._extraArgs);

				this.fireEvent("preDataLoad", { arguments: content });

				var _this = this;
				this._bind(content, function(status, data, reason) {
					var success = status;
					if (status) {
						data = data || _this._loadFromWindow(_this._dataVarName);
						if (_this._validateBuzzData(data)) {
							_this.debug("data loaded");
							_this.dataObj = postProcessBuzzData(data);
						} else {
							_this.debug("data loaded but response was invalid", data);
							status = false;
						}
					} else {
						_this.debug("Data load failed because: " + reason, data);
					}
					_this.fireEvent("postDataLoad", { response: data });
					callback && callback(status);
				},
		null, /* timeout */
		"data");
			},

			/**
			* Internal function, validates that the buzz data is actually usable
			* @param {Object} data data to validate
			*/
			_validateBuzzData: function(data) {
				return (data != null && data.Data != null);
			},

			/**
			* Check in the window context for some bit of data
			* @param {Object} dataName the name to look for
			*/
			_loadFromWindow: function(dataName) {
				if (window[dataName]) {
					this.debug("found in window: " + dataName);
					return window[dataName];
				}
				return null;
			},

			/**
			* Forces a load of the template, either from a prenamed data variable or from the server
			* @param {Function} callback Optional callback function 
			*/
			loadTemplate: function(callback) {
				this.debug("Loading template");
				var _this = this;

				if (this._templateId && document.getElementById(this._templateId)) {
					this._setTemplate(this._templateId);
				} else {
					var d = this._loadFromWindow(this._templateId);
					if (d) {
						this._setTemplate(d);
					}
				}

				if (this._template) {
					callback(true);
					return;
				} else {
					var content = { load: "template" };

					var myCB = function(status, data, reason) {
						try {
							if (status) {
								_this.debug("Template loaded");
								var templ = data || _this._loadFromWindow(_this._templateId);
								_this._setTemplate(templ);
								window[_this._templateId] = data;
								callback((data != null && data.toString().length > 0));
							} else {
								_this.debug("Template load failed because of: " + reason, data);
								callback(false);
							}
						} catch (e) {
							_this.debug("Error executing callback from LoadTemplate", e);
						}
					};

					this._bind(content, myCB, null, "template");
				}
			},

			/**
			* Set the template for this buzz object
			* @param {String} templateText Either the actual text of the template, or a DOM node ID to read the template from 
			*/
			_setTemplate: function(templateText) {
				try {
					if (document.getElementById(templateText)) {
						this._template = TrimPath.parseDOMTemplate(templateText);
					} else {
						this._template = TrimPath.parseTemplate(templateText);
					}
				} catch (e) {
					this.debug("Error parsing template", e);
					this._templateError = e.message;

					//If the template is broken, we want to still set something into the _template variable
					//Otherwise we could get locked into an infinite loop of badness.
					this._template = TrimPath.parseTemplate("Template parsing error <br/>" + e.toString());
				}
			},

			/**
			* Internal function for performing a callback to the buzz.ashx handler
			* @param {Object} args Arguments to send back. apiToken, buzzId and extraArgs will automatically be mixed into this 
			* @param {Function} callback Function to callback when data is loaded (or failed, or timed out)
			* @param {Number} timeout Number of milliseconds for timeout. Default is 60000
			*/
			_bind: function(args, callback, timeout, requestId) {
				args.apiToken = args.apiToken || this.apiToken;
				args.buzzId = args.buzzId || this.buzzId;
				args = ng_mixin(args, this._extraArgs);

				var dsrRequestId = (requestId ? "ngbuzz_" + this.buzzId + "_" + requestId : null);
				ng.dsr.bind(this.buzzAppUrl + "buzz.ashx", args, callback, (timeout || 60000), dsrRequestId);
			},

			suppressTemplateLoading: function() { /* retained for backwards compatibility */ },
			suppressScriptsLoading: function() { ng.buzz.scriptsLoaded[this.buzzId] = true; },
			suppressStylesLoading: function() { ng.buzz.stylesLoaded[this.buzzId] = true; },

			/**
			* Load an external stylesheet into the doc. Note this is totally asynchronous with no confirmation - "fire and pray". 
			* Note it will not attempt to load the same file twice.
			* @param {String} cssUrl the base url of the stylesheet
			* @param {Object} queryArgs an optional map of querystring args to pass along
			**/
			_loadCSS: function(cssUrl, queryArgs) {
				//Create a dynamic LINK tag to load styles
				var e = document.createElement("link");
				e.rel = "stylesheet";
				e.type = "text/css";

				//build the url, appending all the extra args to the query string
				var url = cssUrl;

				if (queryArgs) {
					var connector = (cssUrl.indexOf("?") > 0 ? "&" : "?");
					for (var key in queryArgs) {
						url += connector + key + "=" + escape(queryArgs[key]);
						connector = "&";
					}
				}

				if (!ng.buzz.stylesLoaded[url]) {
					ng.buzz.stylesLoaded[url] = true;

					this.debug("Loading stylesheet", url);

					//Stick the element into the HEAD
					e.href = url;
					setTimeout(function() {
						document.getElementsByTagName("HEAD")[0].appendChild(e);
					}, 1);
				}
				return true;
			},

			/**
			* Ensures that the CSS and Javascript portions of the template have been loaded
			* @param {Function} callback
			*/
			_loadScriptsAndStyles: function() {
				var loaded = true;

				//Load the style block
				if (!ng.buzz.stylesLoaded[this.buzzId]) {
					this.debug("Loading styles");
					ng.buzz.stylesLoaded[this.buzzId] = true;

					if (typeof window["buzzStyles_" + this.buzzId] != "undefined") {
						try {
							ng_insertCSS(window["buzzStyles_" + this.buzzId]);
							window["buzzStyles_" + this.buzzId] = null;
						} catch (e) {
							this.debug("Error inserting dynamic CSS", e);
						}
					} else {
						//Styles were not preloaded into the document, create a dynamic link tag instead
						//build the url, appending all the extra args to the query string
						var url = this.buzzAppUrl + "buzz.ashx";
						var args = ng_mixin({
							apiToken: this.apiToken,
							buzzId: this.buzzId,
							//uniqueId : this.uniqueId, //This was a really stupid idea on my part. It breaks browser caching.
							load: "styles"
						}, this._extraArgs);

						this._loadCSS(url, args);

						loaded = false;
					}
				}

				//Load the attached scripts
				var _buzzId = this.buzzId;
				var scriptTxt = window["buzzScript_" + _buzzId];
				if (this.scriptLoaded) {
					//script already loaded, do nothing
				} else if (scriptTxt != null) {
					this.scriptLoaded = true;

					try {
						if (this.scriptCtx == "widget") {
							var settingsObj = this.getSettingsObj();

							//Wrap the code in a dynamic Function and then execute it in the context of the widget
							//We're uilding up the body of the function
							//We're going to go through the settingsObj and create local variables for all the tokens
							//This provides a more consistent API, because tokens can be referenced the same way as within the TrimPath template
							//Note that this is similar to doing a "with(settingsObj)", but IE6 and Safari have bugs in "with" -- they don't carry that context into nested functions. Which sucks.
							var funcBody = "";
							var blankObj = {};
							for (var key in settingsObj) {
								if (!blankObj[key]) {
									funcBody += ("var " + key + " = ctx[\"" + key + "\"];\n");
								}
							}
							funcBody += scriptTxt;


							var scriptFunc = new Function(["ctx"], funcBody);
							scriptFunc.apply(this, [settingsObj]);
						} else {

							//Execute the script in the global scope. This take some gymnastics to be cross browser
							if (window.execScript) {
								//use the IE-specific function if it's available
								if (scriptTxt && scriptTxt.length) {
									window.execScript(scriptTxt);
								}
							} else {
								loaded = false;
								//Otherwise use setTimeout to make the window execute the script for us.
								setTimeout(scriptTxt, 0);
							}
						}
					} catch (e) {
						this.debug("Error evaluating widget scripts: ", e);
						this.addEvent("postRender", function(args) {
							var div = document.createElement("DIV");
							div.innerHTML = "Error running widget script: " + e.message;
							args.targetElem.appendChild(div);
						});
						//throw e;
					}
				} else if (!ng.buzz.scriptsLoaded[_buzzId]) {
					loaded = false;
					this.debug("ng.buzz.scriptsLoading[", _buzzId, "] = ", ng.buzz.scriptsLoading[_buzzId]);
					this.debug("ng.buzz.scriptsLoaded[", _buzzId, "] = ", ng.buzz.scriptsLoaded[_buzzId]);

					if (!ng.buzz.scriptsLoading[_buzzId] && ng.dsr.isDocumentReady()) {
						ng.buzz.scriptsLoading[_buzzId] = true; //set flag that says we're loading the scripts so nobody else tries
						var args = ng_mixin({ apiToken: this.apiToken, buzzId: _buzzId, load: "script" }, this._extraArgs);

						var url = this.buzzAppUrl + "buzz.ashx";
						var callId = "ngbuzzDynScriptLoad_" + _buzzId;
						var dataLoadedCallback = function(success, data, reason) {
							ng.buzz.scriptsLoading[_buzzId] = null;
							ng.buzz.scriptsLoaded[_buzzId] = true; //we'll say it's loaded regardless of the actual status, just to avoid an infinite loop
							if (success) {
								window["buzzScript_" + _buzzId] = data;
							} else {
								window["buzzScript_" + _buzzId] = "/* Error loading script: " + data + " */";
							}
						};

						//Create a dynamic script block to load in the scripts.
						//Buzz.ashx has special logic to invoke the DSR callback method at the end of the script block
						//Calling a normal script file this way would not invoke the callback, though it probably would load the file
						ng.dsr.bind(url, args, dataLoadedCallback, null, callId);
					}
				}

				return loaded;
			},

			/**
			* Internal function to call back the render() method
			* @param {Boolean} succeeded
			* @param {Function} callback
			*/
			_rerender: function(succeeded, callback) {
				if (succeeded) {
					this.render(callback);
				} else if (callback) {
					try {
						callback(false);
					} catch (e) { }
				}
			},

			//params: callback, a function to call after rendering is complete
			/**
			* The "main" function of the buzzlet
			* ensures that the scripts, styles, template and data have been loaded
			* then invokes the JST template and shoves the result into the target element
			* note that if something has not been loaded this function will register a callback to itself
			* So a single "client call" of this function may result in it being invoked up to 4 times
			* @param {Function} callback a callback that will be called once the buzz has been rendered (or if rendering fails for some reason)
			*/
			render: function(callback) {
				var _this = this;
				var rerender = function(s) { _this._rerender(s, callback); };
				//this.debug("render()");

				//ensure that the external scripts and styles are loaded in
				if (!this._loadScriptsAndStyles()) {
					//If scripts and/or styles aren't loaded, check back in 50ms
					setTimeout(function() {
						rerender(true);
					}, 50);
					return;
				}

				var tgtElem = document.getElementById(this._targetId);
				if (this._templateError) {
					//this.debug("Template error!");
					tgtElem.innerHTML = this._templateError;
					return;
				}

				//ensure that the template is loaded
				//this is intentionally not in a try/catch block, so that template parsing errors will bubble up
				if (!this._template) {
					//this.debug("loading template");
					this.loadTemplate(rerender);
					return;
				}

				try {
					//Ensure the data is loaded
					if (!this._validateBuzzData(this.dataObj)) {
						//this.debug("loading data");
						this.loadData(rerender);
						return;
					}

					//ensure that the target exists.
					//If it doesn't just create it and append it to the document
					//that's kinda goofy behavior, but really the target should always be set
					if (!document.getElementById(this._targetId)) {
						//this.debug("Target element not found, creating: " + this._targetId);
						var tgt = document.createElement("span");
						tgt.id = this._targetId;
						document.body.appendChild(tgt);
					}

					if (tgtElem) {
						//Construct the arguments to the templater
						var ctx = ng_mixin(this.getSettingsObj(), this.dataObj);

						this.fireEvent("preRender", { renderContext: ctx, targetElem: tgtElem });

						//Render the templater
						var text = this._template.process(ctx, { throwExceptions: true, scope: this });
						if (text && !(/^\s*$/).test(text)) {
							tgtElem.innerHTML = text;

							this.logGenericEvent("view");

							setTimeout(function() {
								_this._invokePostRenderCallbacks(callback, true, tgtElem, text);
							}, 0);

							this.debug("render() completed...");
						} else {
							this.debug("Got through render, but didn't render anything. Probably waiting for scripts to load.");
						}
					}
				} catch (e) {
					setTimeout(function() {
						_this._invokePostRenderCallbacks(callback, false, tgtElem, e.message);
					}, 50);

					this.debug("Error rendering", e);
					this._templateError = e.message;
					if (tgtElem) {
						tgtElem.innerHTML = e.message;
					}
				}
			},

			/**
			* Render an arbitrary template in the context of this widget.
			* The advantage of this over just using TrimPath directly is that you get access to most of the buzz BuiltIns and Modifiers automatically.
			* It also handles detection of DOM-based templates vs in-memory strings
			* Note that unlike the buzz's native template, templates passed into here are not cached, so they will be a bit slower.
			* Also, AddPostRenderEvent(), RenderToolbar() and LoadBuzz() are not supported by this function.
			*
			* @param (String) templateOrIdOrText Either a parsed TrimPath template, the template string itself, or the ID of the DOM node containing the template
			* @param (Object) data Data to be passed into the template. All the properties of this object will be available inside the template.
			*/
			renderTemplate: function(templateOrIdOrText, data) {
				var template;

				try {
					if (templateOrIdOrText.process) {
						template = templateOrIdOrText;
					} else if (document.getElementById(templateOrIdOrText)) {
						template = TrimPath.parseDOMTemplate(templateOrIdOrText);
					} else {
						template = TrimPath.parseTemplate(templateOrIdOrText);
					}
				} catch (e) {
					this.debug("Error parsing template", e);
					return "Custom template parsing error: " + e.toString();
				}

				var ctx = ng_mixin(this.getSettingsObj(), { Data: data });
				var html = template.process(ctx, { throwExceptions: true });
				return html;
			},

			/**
			* Render an arbitrary template in the context of this widget, and insert the results into the specific element
			* The advantage of this over just using TrimPath directly is that you get access to all the buzz BuiltIns and Modifiers automatically.
			* It also handles detection of DOM-based templates vs in-memory strings
			* Note that unlike the buzz's native template, templates passed into here are not cached, so they will be a bit slower.
			* Also, AddPostRenderEvent(), RenderToolbar() and LoadBuzz() are supported by this function.
			*
			* @param (String) templateOrIdOrText Either a parsed TrimPath template, the template string itself, or the ID of the DOM node containing the template
			* @param (String) targetId Element to insert the resulting HTML into
			* @param (Object) data Data to be passed into the template. All the properties of this object will be available inside the template.
			*/
			insertTemplate: function(templateOrIdOrText, targetId, data) {
				try {
					var settingsObj = this.getSettingsObj();
					var postRenderQueue = [];
					settingsObj.AddPostRenderCallback = function(arg) {
						postRenderQueue.push(arg);
					}

					var html = this.renderTemplate(templateOrIdOrText, data);

					var targetElem = document.getElementById(targetId);
					targetElem.innerHTML = html;

					this.executeEventQueue(postRenderQueue, { success: true, targetElem: targetElem, buzzObj: this });
				} catch (e) {
					this.debug("Error running insertTemplate", e.message);
				}
			},

			/**
			* Invoke all the various callbacks that we do after rendering has succeeded or failed
			* 
			* @param (Function) renderCallbackFunc The callback function that was passed as an argument to the render() method (if any)
			* @param {Boolean} success Whether rendering was successful
			* @param {Element} tgtElem The element that received the render output
			* @param (String) text The raw text that was output from the template and shoved into tgtElem
			*/
			_invokePostRenderCallbacks: function(renderCallbackFunc, success, tgtElem, text) {
				this.fireEvent("postRender", { success: success, targetElem: tgtElem, buzzObj: this });

				if (renderCallbackFunc) {
					try {
						renderCallbackFunc(success, text, this);
					} catch (e) { }
				}

				//Check for a global ngBuzzLoaded callback function
				if (typeof ngBuzzLoaded != "undefined" && ngBuzzLoaded.apply) {
					try {
						ngBuzzLoaded(true, text, tgtElem, this);
					} catch (e) { }
				}

				//execute Lookery tracking functionality
				/*
				_lkpid = "df6c38bd1f92f770037fbeaeeda3e8de";
				_lkenc = "0";
    	
				if (window.lkTrk != undefined)
				{
				//Lookery code already exists within page
				lkTrk(); //call lookery tracking function
				}
				else
				{
				//Lookery code is not contained within page
				this.debug("Appending Lookery code to page")
				ng.dsr.createScriptBlock("http://pub.lookery.com/js/1/", {}); //script automatically executes lookery code
				}
				*/
			},

			/**
			* Add a post render callback
			* @param {Function} arg
			*/
			addPostRenderCallback: function(callbackFunc) {
				this.addEvent("postRender", function(eventArgsObj) {
					//interpret the arguments back into the old-style postrender
					if (callbackFunc.apply) {
						callbackFunc(eventArgsObj.success, eventArgsObj.targetElem, eventArgsObj.buzzObj);
					} else {
						//If they gave us text instead of an actual function, eval it
						eval(callbackFunc);
					}
				});
			},

			addEvent: function(eventName, func) {
				var queue = this.events[eventName] || [];

				queue.push(func);
				this.events[eventName] = queue;
			},

			fireEvent: function(eventName, eventArgs) {
				var queue = this.events[eventName];
				this.executeEventQueue(queue, eventArgs);
			},

			executeEventQueue: function(queue, eventArgs) {
				if (queue && queue.length) {
					var callback;
					while ((callback = queue.shift()) != null) {
						try {
							if (!callback.apply && callback.substr) {//looks like a string, try to eval it instead
								callback = new Function("", callback);
							}
							callback.apply(this, [eventArgs]);
						} catch (e) {
							this.debug("Error invoking event handler : ", e.toString());
						}
					}
				}
			},

			/**
			* Log a post-related event to analytics
			*/
			logPostEvent: function(action, postId, feedId, link, evt, node) {
				this.analytics.logPostEvent(action, postId, feedId, link, evt, node);
			},

			/**
			* Log a generic (non-post-related) event to analytics
			*/
			logGenericEvent: function(action, evt, node) {
				this.analytics.logGenericEvent(action, evt, node);
			},

			/**
			* get the text for a javascript call to log attention data 
			* @param {Object} attnObj Post to pull post data from
			* @param {Number} attnType
			*/
			_getAttentionJS: function(attnObj, attnType) {
				if (!attnType) {
					return "";
				}

				if (attnObj) {
					var pid = parseInt(attnObj.PostId || attnObj.postId || attnObj.PostID || attnObj.postID || -1, 10);
					var fid = parseInt(attnObj.FeedId || attnObj.feedId || attnObj.FeedID || attnObj.feedID || -1, 10);
					var url = attnObj.HtmlUrl || attnObj.Link;

					if (pid > 0 && fid > 0) {
						return "ng.buzz['" + this.uniqueId + "'].logPostEvent('" + ng_JST_Modifiers.js(attnType) + "', " + pid + ", " + fid + ", '" + ng_JST_Modifiers.js(url) + "', event, this)";
					}
				}

				return "ng.buzz['" + this.uniqueId + "'].logGenericEvent('" + ng_JST_Modifiers.js(attnType) + "', event, this)";
			},

			/**
			* Gets the custom context object to send into JST for template processing
			* This is the stuff referred to as "Built Ins" in the documentation.
			* Modifiers are defined in the ng_JST_Modifiers object
			*/
			getSettingsObj: function() {
				return this.settingsObj || (this.settingsObj = new ng.buzz.RenderContext(this));
			},

			toString: function() {
				return "Buzzlet[buzzId:" + this.buzzId + ", targetId:" + this._targetId + "]";
			},

			debug: function() {
				var args = [this.toString()];
				for (var i = 0; i < arguments.length; i++) {
					args.push(arguments[i]);
				}
				ng_debug.apply(this, args);
			}
		};
	} //end ng.buzz.Buzzlet.prototype





	/**
	* ng.buzz.RenderContext
	**/
	ng.buzz.RenderContext = function(buzzObj) {
		var _this = buzzObj;
		ng_mixin(this, {
			_MODIFIERS: ng_JST_Modifiers,
			ExtraArgs: buzzObj._extraArgs,
			BuzzId: buzzObj.buzzId,
			BuzzName: buzzObj.name,
			UserId: 0, /* this was once set, but is now never set and is deprecated */
			OrgCode: buzzObj.orgCode,
			ApiToken: buzzObj.apiToken,
			WidgetConfiguration: buzzObj.WidgetConfiguration,
			BuzzAppUrl: buzzObj.buzzAppUrl,
			NGBaseUrl: buzzObj.ngBaseUrl,
			DirectUrl: buzzObj.directUrl,
			DirectAppUrl: buzzObj.directAppUrl,
			UniqueId: buzzObj.uniqueId,
			FacebookApiKey: buzzObj._extraArgs.fbApiKey || buzzObj.fbApiKey,
			BuzzObj: buzzObj,
			BuzzObjJS: "ng.buzz." + buzzObj.uniqueId,
			LogPostEvent: function(eventName, post, evt, node) {
				buzzObj.logPostEvent(eventName, post.PostId, post.FeedId, post.HtmlUrl, evt, node);

			},
			LogGenericEvent: buzzObj.logGenericEvent,
			getBuzzFooter: window.getBuzzFooter,
			returnBuzzFooter: window.returnBuzzFooter
		});
	};

	ng.buzz.RenderContext.prototype = {
		GetPostObjJS: function(post) {
			if (post) {
				try {
					for (var i = 0; i < this.Data.length; i++) {
						if (this.Data[i] == post) {
							return this.BuzzObjJS + ".dataObj.Data[" + i + "]";
						}
					}
				} catch (e) {
					this.Debug("Error trying to get post object JS for post", post);
				}
			}
			return "";
		},

		LoadCSS: function(url, args) {
			this.BuzzObj._loadCSS(url, args);
		},

		//Load an external script into the browser
		LoadScript: function(src, rerunWhen, timeoutSeconds) {
			var buzzObj = this.BuzzObj;
			return loadScript(src, rerunWhen, timeoutSeconds, function() {
				buzzObj.render();
			});
		},

		AddPostRenderCallback: function(arg) {
			this.BuzzObj.addPostRenderCallback(arg);
		},


		/**
		* Render the toolbar
		* @param {Object} post the post the toolbar is related to
		* @param {Object} opt a hash of options for the toolbar (if not specified defaults will be used)
		*/
		RenderToolbar: function(post, opt) {
			//What we're going to do is create the HTML for a <div> tag with all the attributes the toolbar wants
			//The options hash is used to specify all those attributes
			//That HTML will be sent back into TrimPath to go into the template output
			//We'll also register a post render callback to invoke the toolbar code on that div

			//Set up default options if they weren't specified by the caller
			var options = opt || {};
			options.ngPostId = options.ngPostId || post.PostId;
			options.ngOrg = options.ngOrg || this.OrgCode;
			options.ngPostLink = options.ngPostLink || post.HtmlUrl;
			options.ngPostTitle = options.ngPostTitle || post.Title;
			options.ngEmailTitle = options.ngEmailTitle || options.ngPostTitle || post.Title;
			options.ngFeed = options.ngFeed || post.FeedId;
			//options.ngUserId = options.userId || this.UserId;
			options.id = options.id || "ng_toolbar_" + this.UniqueId + post.PostId;
			options.ngPostDescription = options.ngPostDescription || post.Description;

			//We're going to force this one so we can do upgrades more easily later, by just changing it here
			options.ngVersion = "4.0";

			//Make sure it has the ng_toolbar class on it, the toolbar code looks for this
			if (options.className && !(/ng_toolbar/).test(options.className)) {
				options.className += " ng_toolbar";
			} else {
				options.className = "ng_toolbar";
			}

			//Make sure it's "display:none" so that it doesn't show a flash of the templates before styles load
			if (options.style && options.style.indexOf("display") < 0) {
				options.style += "; visibility:hidden;";
			} else {
				options.style = "visibility:hidden;";
			}
			options.style += ";height:16px;";

			//Create the HTML for that div
			var html = "<div ";
			for (var key in options) {
				var val = options[key];
				val = (typeof val != "undefined" ? val.toString() : "");
				html += (key + "=\"" + ng_JST_Modifiers.html(val) + "\" ");
			}
			html += ">\n";
			//Stick the post description inside the DIV so it's available to the toolbar code
			//html += (options.ngPostDescription || post.Description);
			html += "\n</div>";


			var ctxObj = this;
			//var buzzObj = this.BuzzObj;
			//var loadScript = this.LoadScript;

			//Add the post render callback to load the toolbar
			this.AddPostRenderCallback(function() {
				ctxObj.Debug("Loading toolbar: " + options.id);
				function doRender() {
					new NewsGator.PrivateLabel.NGToolBar4(options.id, ctxObj.NGBaseUrl, ctxObj.BuzzObj, options);
				}

				//Load the script. If script is already loaded, loadScript() will return true
				if (loadScript(ctxObj.BuzzObj.buzzAppUrl + "load.ashx/toolbar", "window.NewsGator && NewsGator.PrivateLabel && NewsGator.PrivateLabel.NGToolBar4", 30, doRender)) {
					doRender();
				}
			});

			return html;
		},

		//Load another buzz box and render it in place
		LoadBuzz: function(buzzId, extraArgs, templateId, targetId, apiToken) {
			try {
				var buzzObj = this.BuzzObj;
				apiToken = apiToken || buzzObj.apiToken;
				targetId = targetId || "buzzTgt_" + (Math.floor(Math.random() * 1000000000));
				var extra = ng_mixin(ng_clone(buzzObj._extraArgs), extraArgs);

				//				console.log(extra);
				//				console.log(extraArgs);

				this.AddPostRenderCallback(function() {
					buzzObj.loadSubordinateWidget(buzzId, targetId, extra, function() {
						this.render();
					});
				});
				return '<span id="' + targetId + '"></span>';
			} catch (e) {
				this.Debug("Error in LoadBuzz()", e);
				throw e;
			}
		},

		//Render Javascript that will log a Click attention event
		AttentionClickJS: function(obj) {
			return this.AttentionJS("click", obj);
		},

		//Render Javascript that will log an IM attention event
		AttentionIMJS: function(obj) {
			return this.AttentionJS("IM", obj);
		},

		//Render Javascript that will log an attention event
		AttentionJS: function(action, obj) {
			return this.BuzzObj._getAttentionJS(obj, action);
		},

		//Debugging...
		Debug: function() {
			this.BuzzObj.debug.apply(this, arguments);
		},

		toString: function() {
			return "BuzzRenderContext[buzzId: " + this.BuzzId + "]";
		}
	};


	/**
	* END ng.buzz.RenderContext
	**/







	/**
	* The following functions are inside a closure, so the code in here can see them but nobody else can.
	* This is handy for privacy sake, and also because it allows the JS compressors to compress the functions better
	*/
	function postProcessBuzzData(data) {
		if (data) {

			//Originally all Topics, Keywords & Keyphrases were simple strings
			//Now they're objects, so we can attach additional data to them
			//for backwards compatibility, we'll make their toString() function return the term, which is similar to the behavior before
			if (data.Topics || data.Keywords || data.Keyphrases) {
				function termToString() {
					return this.Term;
				}

				var allTopics = [].concat(data.Topics || []).concat(data.Keywords || []).concat(data.Keyphrases || []);
				for (var i = 0; i < allTopics.length; i++) {
					allTopics[i].toString = termToString;
				}
			}


			if (data.Data && data.Data.length) {
				for (var i = 0; i < data.Data.length; i++) {
					var post = data.Data[i];

					/*
					Our initial implementation of the MediaRSS parser had a bug in it.
					We only kept one Thumbnail element, when there could be multiples.
					That bug is now fixed, but to prevent pre-existing widgets from breaking, we're going to fake the Thumbnail field here.
					Just pick off the first element of the Thumbnails array and assign it to the Thumbnail field.
					*/
					if (post.MediaRSS && post.MediaRSS.Thumbnails && post.MediaRSS.Thumbnails.length > 0) {
						post.MediaRSS.Thumbnail = post.MediaRSS.Thumbnails[0];
					}

					/** These were never really used, so they're not being sent down anymore. Faking them in here just in case **/
					post.NumRatings = 0;
					post.AvgRating = 0;
					post.NumIncomingLinks = 0;
					post.NumComments = 0;
					post.NumClippings = 0;
				}
			}
		}
		return data;
	}

	function scrubId(s) {
		return s.replace(/-/g, "_").replace(/\s/g, "__");
	}

	/**
	* Attempts to detect the character set in use on this page
	*/
	function detectCharset(widget) {
		//character set was already specified, don't bother
		if (widget._extraArgs.enc) {
			return;
		}

		if (ng_isIE || (ng_isSafari && navigator.userAgent.indexOf("Version") < 0)) {
			var charset = window.ngbuzz_charset || document.charset || document.characterSet;

			//Charset detection doesn't work for Safari 2.0, and it doesn't handle UTF-8 right, so force it to latin-1. I love you Safari.
			if (!charset && navigator.userAgent && navigator.userAgent.indexOf("Safari") >= 0 && navigator.userAgent.indexOf("Version") < 0) {
				charset = "ISO-8859-1";
			}

			if (charset) {
				window.ngbuzz_charset = charset;
				if (!(/utf.*8/i).test(charset)) {
					widget._extraArgs.enc = charset;
				}
			}
			ng_debug("detected charset: " + charset);
		}
	}

	//Load an external script into the browser
	function loadScript(src, rerunWhen, timeoutSeconds, callback) {
		timeoutSeconds = timeoutSeconds || 20;

		function runCheck() {
			try {
				if (!rerunWhen) {
					return true;
				} else {
					return eval(rerunWhen);
				}
			} catch (e) {
				ng_debug("Error executing checkstring: '" + rerunWhen + "' to load script: " + src);
				ng_debug(e);
				return false;
			}
		}

		if (ng.buzz.scriptsLoaded[src] || (rerunWhen && runCheck())) {
			//If the script is already loaded, return...
			//this.Debug("Script already loaded: " + src);
			ng.buzz.scriptsLoading[src] = null;
			return true;
		} else {
			if (!ng.buzz.scriptsLoading[src]) {
				ng.buzz.scriptsLoading[src] = true;
				ng_debug("Loading script: " + src);
				ng.dsr.createScriptBlock(src, {});
			}

			if (rerunWhen) {
				/*
				It's hard to know when the browser has actually loaded the script we asked for.
				So we create a function that checks, and if buzz isn't loaded it waits and then calls itself back.
				This would be an obvious place to use a setInterval(), but we had problems in some sites (primarily blogspot) that would cause it to never clear the interval, and madness would ensue.
				*/


				ng_debug("Starting loop to check whether '" + src + "' has loaded by evaling '" + rerunWhen + "'");
				var expireTime = new Date(new Date().getTime() + (timeoutSeconds * 1000));

				var check = function() {
					if (runCheck() || expireTime < (new Date())) {
						ng_debug(src + " has loaded or timed out");
						ng.buzz.scriptsLoaded[src] = true;
						callback && callback();
					} else {
						//ng_debug("No it has not")
						setTimeout(check, 100);
					}
				};

				check();
			}
		}
		return false;
	}
})();

/**
NOTE: This code contains a Newsgator modification to fix a bug.
The bug is that whenever a modifier argument has a colon, you get script errors.
The fix is the following two lines in the emitExpression() function.

if(parts.length > 2)
	parts = [parts[0], parts.slice(1).join(":")];
**/

/**	
NOTE 2: There is another fix applied to handle some escaping issues when loading from DOM templates
The fixes were for the following four lines in the parseDOMTemplate() function:

content = content.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/%7B/g, "{").replace(/%7D/g, "}").replace(/%7C/g, "|");
var unquotedAttributeRegex = new RegExp("(<[^>]+?\\s+\\w+=)([^'\"].*?)((?:\\s.*>)|(?:>))", "g");
while(content.search(unquotedAttributeRegex) >= 0){
	content = content.replace(unquotedAttributeRegex, '$1"$2"$3');
}		
**/

/**
NOTE 3: There are several more modifications to add the {stringify} block
**/

/**
NOTE 4: Also includes a fix for a bug that caused unterminated string errors on certain template inputs.
See http://code.google.com/p/trimpath/issues/detail?id=16
and http://trimpath.com/forum/viewtopic.php?id=1998
**/

/**
NOTE 5: Added in an option to pass in a scope in which to run the template function. So you can tell it what "this" should be within the function.
**/

/**
NOTE 6: Modified the {for} loop code to not loop over functions or properties added to the Array.prototype
Some libraries (eg, MooTools) do this.
**/

/**
* TrimPath Template. Release 1.0.38.
* Copyright (C) 2004, 2005 Metaha.
* 
* TrimPath Template is licensed under the GNU General Public License
* and the Apache License, Version 2.0, as follows:
*
* This program is free software; you can redistribute it and/or 
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* 
* This program is distributed WITHOUT ANY WARRANTY; without even the 
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
* See the GNU General Public License for more details.
* 
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var TrimPath;

// TODO: Debugging mode vs stop-on-error mode - runtime flag.
// TODO: Handle || (or) characters and backslashes.
// TODO: Add more modifiers.

(function() {               // Using a closure to keep global namespace clean.
    var global = this;
    
    if (TrimPath == null)
        TrimPath = new Object();
    if (TrimPath.evalEx == null)
        TrimPath.evalEx = function(src) { return eval(src); };

    var UNDEFINED;
    if (Array.prototype.pop == null)  // IE 5.x fix from Igor Poteryaev.
        Array.prototype.pop = function() {
            if (this.length === 0) {return UNDEFINED;}
            return this[--this.length];
        };
    if (Array.prototype.push == null) // IE 5.x fix from Igor Poteryaev.
        Array.prototype.push = function() {
            for (var i = 0; i < arguments.length; ++i) {this[this.length] = arguments[i];}
            return this.length;
        };

    TrimPath.parseTemplate = function(tmplContent, optTmplName, optEtc) {
        if (optEtc == null)
            optEtc = TrimPath.parseTemplate_etc;
        var funcSrc = parse(tmplContent, optTmplName, optEtc);
        var func = TrimPath.evalEx(funcSrc, optTmplName, 1);
        if (func != null)
            return new optEtc.Template(optTmplName, tmplContent, funcSrc, func, optEtc);
        return null;
    }
    
    try {
        String.prototype.process = function(context, optFlags) {
            var template = TrimPath.parseTemplate(this, null);
            if (template != null)
                return template.process(context, optFlags);
            return this;
        }
    } catch (e) { // Swallow exception, such as when String.prototype is sealed.
    }
    
    TrimPath.parseTemplate_etc = {};            // Exposed for extensibility.
    TrimPath.parseTemplate_etc.statementTag = "forelse|for|if|elseif|else|var|macro";
    TrimPath.parseTemplate_etc.statementDef = { // Lookup table for statement tags.
        "if"     : { delta:  1, prefix: "if (", suffix: ") {", paramMin: 1 },
        "else"   : { delta:  0, prefix: "} else {" },
        "elseif" : { delta:  0, prefix: "} else if (", suffix: ") {", paramDefault: "true" },
        "/if"    : { delta: -1, prefix: "}" },
        "for"    : { delta:  1, paramMin: 3, 
                     prefixFunc : function(stmtParts, state, tmplName, etc) {
                        if (stmtParts[2] != "in")
                            throw new etc.ParseError(tmplName, state.line, "bad for loop statement: " + stmtParts.join(' '));
                        var iterVar = stmtParts[1];
                        var listVar = "__LIST__" + iterVar;
                        
                        return ["var ", listVar, " = ", stmtParts[3], ";",
				// Fix from Ross Shaull for hash looping, make sure that we have an array of loop lengths to treat like a stack.
                             "var __LENGTH_STACK__;",
                             "if(typeof(__LENGTH_STACK__) == 'undefined' || !__LENGTH_STACK__.length) __LENGTH_STACK__ = new Array();",
                             "__LENGTH_STACK__[__LENGTH_STACK__.length] = 0;", // Push a new for-loop onto the stack of loop lengths.
                             "if(", listVar, " != null){ ",
								"var ",listVar,"_isArray = ",listVar,".constructor.toString().indexOf('Array') >= 0;",
				//"var ", iterVar, "_ct = 0;",       // iterVar_ct variable, added by B. Bittman     
                             "for (var ", iterVar, "_index in ", listVar, "){ ",
				//iterVar, "_ct++;",
                             "var ", iterVar, " = ", listVar, "[", iterVar, "_index];",

				//Don't iterate functions or other added monkeypatched properties on Arrays
								//"console.log('listVar: ', ", listVar, ", 'iterVar: ', ", iterVar, ", 'iterVar_index: ', ", iterVar, "_index);",
								//"console.log('listVar.constructor', ",listVar,".constructor);",
                             "if(typeof ",iterVar," == 'function' || (",listVar,"_isArray && isNaN(parseInt(", iterVar, "_index, 10)) && typeof Array.prototype[", iterVar, "_index] != 'undefined')) {continue;} ",
                             "__LENGTH_STACK__[__LENGTH_STACK__.length - 1]++;"].join("");
			
//                        return [ "var ", listVar, " = ", stmtParts[3], ";",
//                             // Fix from Ross Shaull for hash looping, make sure that we have an array of loop lengths to treat like a stack.
//                             "var __LENGTH_STACK__;",
//                             "if (typeof(__LENGTH_STACK__) == 'undefined' || !__LENGTH_STACK__.length) __LENGTH_STACK__ = new Array();", 
//                             "__LENGTH_STACK__[__LENGTH_STACK__.length] = 0;", // Push a new for-loop onto the stack of loop lengths.
//                             "if ((", listVar, ") != null) { ",
//                             "var ", iterVar, "_ct = 0;",       // iterVar_ct variable, added by B. Bittman     
//                             "for (var ", iterVar, "_index in ", listVar, ") { ",
//                             iterVar, "_ct++;",
//                             "if (typeof(", listVar, "[", iterVar, "_index]) == 'function') {continue;}", // IE 5.x fix from Igor Poteryaev.
//                             "__LENGTH_STACK__[__LENGTH_STACK__.length - 1]++;",
//                             "var ", iterVar, " = ", listVar, "[", iterVar, "_index];" ].join("");
                     } },
        "forelse" : { delta:  0, prefix: "} } if (__LENGTH_STACK__[__LENGTH_STACK__.length - 1] == 0) { if (", suffix: ") {", paramDefault: "true" },
        "/for"    : { delta: -1, prefix: "} }; delete __LENGTH_STACK__[__LENGTH_STACK__.length - 1];" }, // Remove the just-finished for-loop from the stack of loop lengths.
        "var"     : { delta:  0, prefix: "var ", suffix: ";" },
        "macro"   : { delta:  1, 
                      prefixFunc : function(stmtParts, state, tmplName, etc) {
                          var macroName = stmtParts[1].split('(')[0];
                          return [ "var ", macroName, " = function", 
                                   stmtParts.slice(1).join(' ').substring(macroName.length),
                                   "{ var _OUT_arr = []; var _OUT = { write: function(m) { if (m) _OUT_arr.push(m); } }; " ].join('');
                     } }, 
        "/macro"  : { delta: -1, prefix: " return _OUT_arr.join(''); };" }
    }
    TrimPath.parseTemplate_etc.modifierDef = {
        "eat"        : function(v)    { return ""; },
        "escape"     : function(s)    { return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); },
        "capitalize" : function(s)    { return String(s).toUpperCase(); },
        "default"    : function(s, d) { return s != null ? s : d; },
        "js"		 : function(s)	  {return String(s).replace(/\n/g, "\\n").replace(/"/g, "\\042").replace(/'/g, "\\047");}
    }
    TrimPath.parseTemplate_etc.modifierDef.h = TrimPath.parseTemplate_etc.modifierDef.escape;

    TrimPath.parseTemplate_etc.Template = function(tmplName, tmplContent, funcSrc, func, etc) {
        this.process = function(context, flags) {
            if (context == null)
                context = {};
            if (context._MODIFIERS == null)
                context._MODIFIERS = {};
            if (context.defined == null)
                context.defined = function(str) { return (context[str] != undefined); };
            for (var k in etc.modifierDef) {
                if (context._MODIFIERS[k] == null)
                    context._MODIFIERS[k] = etc.modifierDef[k];
            }
            if (flags == null)
                flags = {};
            var resultArr = [];
            var resultOut = { write: function(m) { resultArr.push(m); } };
            
            var scope = flags.scope || global;
            
            try {
                func.apply(scope, [resultOut, context, flags]);
            } catch (e) {
                if (flags.throwExceptions == true)
                    throw e;
                var result = new String(resultArr.join("") + "[ERROR: " + e.toString() + (e.message ? '; ' + e.message : '') + "]");
                result["exception"] = e;
                return result;
            }
            return resultArr.join("");
        }
        this.name       = tmplName;
        this.source     = tmplContent; 
        this.sourceFunc = funcSrc;
        this.toString   = function() { return "TrimPath.Template [" + tmplName + "]"; }
    }
    TrimPath.parseTemplate_etc.ParseError = function(name, line, message) {
        this.name    = name;
        this.line    = line;
        this.message = message;
    }
    TrimPath.parseTemplate_etc.ParseError.prototype.toString = function() { 
        return ("TrimPath template ParseError in " + this.name + ": line " + this.line + ", " + this.message);
    }
    
    var parse = function(body, tmplName, etc) {
        body = cleanWhiteSpace(body);
        var funcText = [ "var TrimPath_Template_TEMP = function(_OUT, _CONTEXT, _FLAGS) { with (_CONTEXT) {" ];
        var state    = { stack: [], line: 1 };                              // TODO: Fix line number counting.
        var endStmtPrev = -1;
        while (endStmtPrev + 1 < body.length) {
            var begStmt = endStmtPrev;
            // Scan until we find some statement markup.
            begStmt = body.indexOf("{", begStmt + 1);
            while (begStmt >= 0) {
                var endStmt = body.indexOf('}', begStmt + 1);
                var stmt = body.substring(begStmt, endStmt);
                var blockrx = stmt.match(/^\{(cdata|minify|eval|stringify)/); // From B. Bittman, minify/eval/cdata implementation.
                if (blockrx) {
                    var blockType = blockrx[1]; 
                    var blockMarkerBeg = begStmt + blockType.length + 1;
                    var blockMarkerEnd = body.indexOf('}', blockMarkerBeg);
                    if (blockMarkerEnd >= 0) {
                        var blockMarker;
                        if( blockMarkerEnd - blockMarkerBeg <= 0 ) {
                            blockMarker = "{/" + blockType + "}";
                        } else {
                            blockMarker = body.substring(blockMarkerBeg + 1, blockMarkerEnd);
                        }                        
                        
                        var blockEnd = body.indexOf(blockMarker, blockMarkerEnd + 1);
                        if (blockEnd >= 0) {                            
                            emitSectionText(body.substring(endStmtPrev + 1, begStmt), funcText);
                            
                            var blockText = body.substring(blockMarkerEnd + 1, blockEnd);
                            if (blockType == 'cdata') {
                                emitText(blockText, funcText);
                            } else if (blockType == 'minify') {
                                emitText(scrubWhiteSpace(blockText), funcText);
                            } else if (blockType == 'stringify' && blockMarker[0] != "{") {
								funcText.push("_CONTEXT['" + blockMarker + "'] = \"" + TrimPath.parseTemplate_etc.modifierDef.js(blockText) + "\";");
							} else if (blockType == 'eval') {
                                if (blockText != null && blockText.length > 0) // From B. Bittman, eval should not execute until process().
                                    funcText.push('_OUT.write( (function() { ' + blockText + ' }).apply(this, []) );');
                            }
                            begStmt = endStmtPrev = blockEnd + blockMarker.length - 1;
                        }
                    }                        
                } else if (body.charAt(begStmt - 1) != '$' &&               // Not an expression or backslashed,
                           body.charAt(begStmt - 1) != '\\') {              // so check if it is a statement tag.
                    var offset = (body.charAt(begStmt + 1) == '/' ? 2 : 1); // Close tags offset of 2 skips '/'.
                                                                            // 10 is larger than maximum statement tag length.
                    if (body.substring(begStmt + offset, begStmt + 10 + offset).search(TrimPath.parseTemplate_etc.statementTag) == 0) 
                        break;                                              // Found a match.
                }
                begStmt = body.indexOf("{", begStmt + 1);
            }
            if (begStmt < 0)                              // In "a{for}c", begStmt will be 1.
                break;
            var endStmt = body.indexOf("}", begStmt + 1); // In "a{for}c", endStmt will be 5.
            if (endStmt < 0)
                break;
            emitSectionText(body.substring(endStmtPrev + 1, begStmt), funcText);
            emitStatement(body.substring(begStmt, endStmt + 1), state, funcText, tmplName, etc);
            endStmtPrev = endStmt;
        }
        emitSectionText(body.substring(endStmtPrev + 1), funcText);
        if (state.stack.length != 0)
            throw new etc.ParseError(tmplName, state.line, "unclosed, unmatched statement(s): " + state.stack.join(","));
        funcText.push("}}; TrimPath_Template_TEMP");
        return funcText.join("");
    }
    
    var emitStatement = function(stmtStr, state, funcText, tmplName, etc) {
        var parts = stmtStr.slice(1, -1).split(' ');
        var stmt = etc.statementDef[parts[0]]; // Here, parts[0] == for/if/else/...
        if (stmt == null) {                    // Not a real statement.
            emitSectionText(stmtStr, funcText);
            return;
        }
        if (stmt.delta < 0) {
            if (state.stack.length <= 0)
                throw new etc.ParseError(tmplName, state.line, "close tag does not match any previous statement: " + stmtStr);
            state.stack.pop();
        } 
        if (stmt.delta > 0)
            state.stack.push(stmtStr);

        if (stmt.paramMin != null &&
            stmt.paramMin >= parts.length)
            throw new etc.ParseError(tmplName, state.line, "statement needs more parameters: " + stmtStr);
        if (stmt.prefixFunc != null)
            funcText.push(stmt.prefixFunc(parts, state, tmplName, etc));
        else 
            funcText.push(stmt.prefix);
        if (stmt.suffix != null) {
            if (parts.length <= 1) {
                if (stmt.paramDefault != null)
                    funcText.push(stmt.paramDefault);
            } else {
                for (var i = 1; i < parts.length; i++) {
                    if (i > 1)
                        funcText.push(' ');
                    funcText.push(parts[i]);
                }
            }
            funcText.push(stmt.suffix);
        }
    }

    var emitSectionText = function(text, funcText) {
        if (text.length <= 0)
            return;
        var nlPrefix = 0;               // Index to first non-newline in prefix.
        var nlSuffix = text.length - 1; // Index to first non-space/tab in suffix.
        while (nlPrefix < text.length && (text.charAt(nlPrefix) == '\n'))
            nlPrefix++;
        while (nlSuffix >= 0 && (text.charAt(nlSuffix) == ' ' || text.charAt(nlSuffix) == '\t'))
            nlSuffix--;
        if (nlSuffix < nlPrefix)
            nlSuffix = nlPrefix;
        if (nlPrefix > 0) {
            funcText.push('if (_FLAGS.keepWhitespace == true) _OUT.write("');
            var s = text.substring(0, nlPrefix).replace(/\n/g, '\\n'); // A macro IE fix from BJessen.
            if (s.charAt(s.length - 1) == '\n')
            	s = s.substring(0, s.length - 1);
            funcText.push(s);
            funcText.push('");');
        }
        var lines = text.substring(nlPrefix, nlSuffix + 1).split('\n');
        for (var i = 0; i < lines.length; i++) {
            emitSectionTextLine(lines[i], funcText);
            if (i < lines.length - 1)
                funcText.push('_OUT.write("\\n");\n');
        }
        if (nlSuffix + 1 < text.length) {
            funcText.push('if (_FLAGS.keepWhitespace == true) _OUT.write("');
            var s = text.substring(nlSuffix + 1).replace(/\n/g, '\\n');
            if (s.charAt(s.length - 1) == '\n')
            	s = s.substring(0, s.length - 1);
            funcText.push(s);
            funcText.push('");');
        }
    }
    
    var emitSectionTextLine = function(line, funcText) {
        var endMarkPrev = '}';
        var endExprPrev = -1;
        while (endExprPrev + endMarkPrev.length < line.length) {
            var begMark = "${", endMark = "}";
            var begExpr = line.indexOf(begMark, endExprPrev + endMarkPrev.length); // In "a${b}c", begExpr == 1
            if (begExpr < 0)
                break;
            if (line.charAt(begExpr + 2) == '%') {
                begMark = "${%";
                endMark = "%}";
            }
            var endExpr = line.indexOf(endMark, begExpr + begMark.length);         // In "a${b}c", endExpr == 4;
            if (endExpr < 0)
                break;
            emitText(line.substring(endExprPrev + endMarkPrev.length, begExpr), funcText);                
            // Example: exprs == 'firstName|default:"John Doe"|capitalize'.split('|')
            var exprArr = line.substring(begExpr + begMark.length, endExpr).replace(/\|\|/g, "#@@#").split('|');
            for (var k in exprArr) {
                if (exprArr[k].replace) // IE 5.x fix from Igor Poteryaev.
                    exprArr[k] = exprArr[k].replace(/#@@#/g, '||');
            }
            funcText.push('_OUT.write(');
            emitExpression(exprArr, exprArr.length - 1, funcText); 
            funcText.push(');');
            endExprPrev = endExpr;
            endMarkPrev = endMark;
        }
        emitText(line.substring(endExprPrev + endMarkPrev.length), funcText); 
    }
    
    var emitText = function(text, funcText) {
        if (text == null ||
            text.length <= 0)
            return;
        text = text.replace(/\\/g, '\\\\');
        text = text.replace(/\n/g, '\\n');
        text = text.replace(/"/g,  '\\"');
        funcText.push('_OUT.write("');
        funcText.push(text);
        funcText.push('");');
    }
    
    var emitExpression = function(exprArr, index, funcText) {
        // Ex: foo|a:x|b:y1,y2|c:z1,z2 is emitted as c(b(a(foo,x),y1,y2),z1,z2)
        var expr = exprArr[index]; // Ex: exprArr == [firstName,capitalize,default:"John Doe"]
        if (index <= 0) {          // Ex: expr    == 'default:"John Doe"'
            funcText.push(expr);
            return;
        }
        var parts = expr.split(':');
        if(parts.length > 2)
			parts = [parts[0], parts.slice(1).join(":")];
        funcText.push('_MODIFIERS["');
        funcText.push(parts[0]); // The parts[0] is a modifier function name, like capitalize.
        funcText.push('"](');
        emitExpression(exprArr, index - 1, funcText);
        if (parts.length > 1) {
            funcText.push(',');
            funcText.push(parts[1]);
        }
        funcText.push(')');
    }

    var cleanWhiteSpace = function(result) {
        result = result.replace(/\t/g,   "    ");
        result = result.replace(/\r\n/g, "\n");
        result = result.replace(/\r/g,   "\n");
        result = result.replace(/^(\s*\S*(\s+\S+)*)\s*$/, '$1'); // Right trim by Igor Poteryaev.
        return result;
    }

    var scrubWhiteSpace = function(result) {
        result = result.replace(/^\s+/g,   "");
        result = result.replace(/\s+$/g,   "");
        result = result.replace(/\s+/g,   " ");
        result = result.replace(/^(\s*\S*(\s+\S+)*)\s*$/, '$1'); // Right trim by Igor Poteryaev.
        return result;
    }

    // The DOM helper functions depend on DOM/DHTML, so they only work in a browser.
    // However, these are not considered core to the engine.
    //
    TrimPath.parseDOMTemplate = function(elementId, optDocument, optEtc) {
        if (optDocument == null)
            optDocument = document;
        var element = optDocument.getElementById(elementId);
        var content = element.value;     // Like textarea.value.
        if (content == null)
            content = element.innerHTML; // Like textarea.innerHTML.
        content = content.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/%7B/g, "{").replace(/%7D/g, "}").replace(/%7C/g, "|");
        content = content.replace(/^\s*\[<!--\s*/, "").replace(/\s*-->\]\s*$/, "");
        
        var unquotedAttributeRegex = new RegExp("(<[^>]+?\\s+\\w+=)([^'\"].*?)((?:\\s.*>)|(?:>))", "g");
        while(content.search(unquotedAttributeRegex) >= 0){
			content = content.replace(unquotedAttributeRegex, '$1"$2"$3');
		}
		
        return TrimPath.parseTemplate(content, elementId, optEtc);
    }

    TrimPath.processDOMTemplate = function(elementId, context, optFlags, optDocument, optEtc) {
        return TrimPath.parseDOMTemplate(elementId, optDocument, optEtc).process(context, optFlags);
    }
}) ();


/**
 * Begin ng_JST_Modifiers
 */
var ng_JST_Modifiers = {
	//Round a number to the specified precision
	//eg, 1, 0.25, 0.5, whatever
	round : function(val, strRoundTo){
		strRoundTo = strRoundTo || 1;
		if(isNaN(val)){
			return val;
		} else {
			var n = parseFloat(val);
			var roundTo = parseFloat(strRoundTo);
			n /= roundTo;
			n = Math.round(n);
			n *= roundTo;
			return n;
		}
	},
	
	//Overriding the built-in "default" modifier so that it handles empty strings as well
	"default" : function(s, d) { 
		return (s == null || (s.length != null && s.length == 0) ? d : s); 
	},
	
	//Escape for use in a Javascript string
	//Note that we have to escape backslashes in case a backslash precedes a character the is turned into a backslash escape sequence
	//eg, the string " \' " must be escaped into " \\\047 " instead of " \\047 "
	js : function(str){
		return (str ? str.toString().replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/"/g, "\\042").replace(/'/g, "\\047") : str);
	},
	
	//Escape doublequotes
	dq : function(val){
		return (val ? val.replace(/"/g, '\\"') : val);
	},
	
	//Escape singlequotes
	sq : function(val){
		return (val ? val.replace(/'/g, "\\'") : val);
	},
	
	//Escape for use in a URL
	url : function(val){
		return (val ? escape(val) : val);
	},
	
	//Escape for use in an html attribute
	htmlAttribute : function(val){
		return this.html(val); //call the TT_Filter version, defined later
	},
	
	//Trim whitespace in an HTML-aware manner
	htmlTrim : function(str) {
		return (str ? str.toString().replace(/^(?:(\s)|(<br\s*?\/?>)|(&nbsp;))+/, '').replace(/(?:(\s)|(<br\s*?\/?>)|(&nbsp;))+$/, '') : str);
	},
	
	prefix : function(str, prefix){
		return (str && str.length ? prefix + str : str);
	},
	
	suffix : function(str, suffix){
		return (str && str.length ? str + suffix : str);
	},
	
	//function to format dates as the approximate time before now
	//for example, "2 hours ago" or "37 minutes ago"
	//the argument is the value of the token, which will be a date in this case
	//if the return value is a date the format() modifier will pick it up and format it normally
	//if the return value is a string the format() modifier will ignore it
	timeSince : function(val){
		//define constants for different periods of time in milliseconds
		var ONE_MINUTE = 60 * 1000;
		var ONE_HOUR = ONE_MINUTE * 60;
		var ONE_DAY = ONE_HOUR * 24;
		var ONE_WEEK = ONE_DAY * 7;
		var ONE_YEAR = ONE_DAY * 365.25;

		function pluralize(diff, period, singular, plural){
			var val = Math.floor(diff / period);
			return (val > 1 ? val + " " + plural : val + " " + singular);
		}
		
		try{
			//find the elapsed time since the date passed in
			var diff = (new Date()).getTime() - Date.parse(val);
			
			if(diff < 0){
				return val; //the date is in the future, just return the date
			} else if(diff < ONE_MINUTE){
				return "seconds ago";
			} else if(diff < ONE_HOUR){
				return pluralize(diff, ONE_MINUTE, "minute ago", "minutes ago");
			} else if(diff < ONE_DAY){
				return pluralize(diff, ONE_HOUR, "hour ago", "hours ago");
			} else if(diff < ONE_WEEK){
				return pluralize(diff, ONE_DAY, "day ago", "days ago");
			} else if(diff < ONE_YEAR){
				return pluralize(diff, ONE_WEEK, "week ago", "weeks ago");
			} else {
				return pluralize(diff, ONE_YEAR, "year ago", "years ago");
			}
		} catch(e){
			return val; //something went wrong, return the date raw
		}
	},
	
	MONTH_NAMES:['January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
	DAY_NAMES:['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
	LZ : function(x) {return(x<0||x>9?"":"0")+x},
	/** 
	Date formatter, uses Java-style format string
	This function from the Date library by Matt Kruse <matt@mattkruse.com> http://www.mattkruse.com/ 
	**/
	format : function(date, format, monthNames, dayNames){
		if(!date){
			return date;
		}
		
		monthNames = monthNames || this.MONTH_NAMES;
		dayNames = dayNames || this.DAY_NAMES;
		
		format=format+"";
		var result="";
		var i_format=0;
		var c="";
		var token="";
		var y=date.getYear()+"";
		var M=date.getMonth()+1;
		var d=date.getDate();
		var E=date.getDay();
		var H=date.getHours();
		var m=date.getMinutes();
		var s=date.getSeconds();
		var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
		// Convert real date parts into formatted versions
		var value=new Object();
		if (y.length < 4) {y=""+(y-0+1900);}
		value["y"]=""+y;
		value["yyyy"]=y;
		value["yy"]=y.substring(2,4);
		value["M"]=M;
		value["MM"]=this.LZ(M);
		value["MMM"]=monthNames[M-1];
		value["NNN"]=monthNames[M+11];
		value["d"]=d;
		value["dd"]=this.LZ(d);
		value["E"]=dayNames[E+7];
		value["EE"]=dayNames[E];
		value["H"]=H;
		value["HH"]=this.LZ(H);
		if (H==0){value["h"]=12;}
		else if (H>12){value["h"]=H-12;}
		else {value["h"]=H;}
		value["hh"]=this.LZ(value["h"]);
		if (H>11){value["K"]=H-12;} else {value["K"]=H;}
		value["k"]=H+1;
		value["KK"]=this.LZ(value["K"]);
		value["kk"]=this.LZ(value["k"]);
		if (H > 11) { value["a"]="PM"; }
		else { value["a"]="AM"; }
		value["m"]=m;
		value["mm"]=this.LZ(m);
		value["s"]=s;
		value["ss"]=this.LZ(s);
		while (i_format < format.length) {
			c=format.charAt(i_format);
			token="";
			while ((format.charAt(i_format)==c) && (i_format < format.length)) {
				token += format.charAt(i_format++);
				}
			if (value[token] != null) { result=result + value[token]; }
			else { result=result + token; }
			}
		return result;
	},
	
	//Strip all HTML tags from the string
	stripHtml : function(str){
		//strip out all HTML tags
		//return (str ? str.toString().replace(/(?:<[^>]*>)|(?:&nbsp;)/ig, "") : str);
		if(!str){return str;}
		return this.excerpt(str, 999999999, null, false, ["*"], true, null);
	},
	
	//Strip only certain HTML tags from the string
	stripTags : function(str, tagsArr){
		if(!str || !tagsArr || tagsArr.length < 0){return str;}
//		var tags = "(?:(?:" + tagsArr.join(")|(?:") + "))";
//		var re = new RegExp("(?:</?\s*" + tags + "[^>]*>)", "ig");
//		return str.toString().replace(re, "");
		return this.excerpt(str, 999999999, null, false, tagsArr, true, null);
	},
	
	//Strip out all HTML tags except for those specified
	stripTagsExcept : function(str, tagsArr){
		if(!str || !tagsArr || tagsArr.length < 0){return str;}
		return this.excerpt(str, 999999999, null, false, ["*"], true, tagsArr);
	},
	
	//Excerpt a string in an HTML-aware way (ie, don't cut in the middle of tags, don't count HTML towards the total)
	//Also has options to always remove certain tags, and some other stuff
	excerpt : function(str, length, suffix, removeTagsAfterMaxReached, alwaysKillTagsArr, keepContentsOfKilledNodes, alwaysKeepTagsArr){
		if(!str){return str;}
		return ng_nodeProcessor.excerpt(str, length, suffix, removeTagsAfterMaxReached, alwaysKillTagsArr, keepContentsOfKilledNodes, alwaysKeepTagsArr);
	},
	
	/**
	 * Modifies all links (HTML <a> tags) to have the specified target
	 * @target (String) The target to set it to. Optional, default value is "_blank"
	 */
	retargetLinks : function(str, target){
		target = target || "_blank";
		return this.processTags(str, function(n){
			if(n.tagName.toUpperCase() == "A"){
				n.target = target;
			}
		});
	},
	
	/**
	 * Applies a function to all the HTML tags in the given string. The function will be passed the node as a parameter.
	 * Any return value from the function is ignored.
	 * @func (Function) The function to apply to each HTML tag
	 */
	processTags : function(str, func){
		if(!func){
			return str;
		}
		
		return this.mapTags(str, function(n){
			func.apply(n, [n])
			return {nodes:[n], processNewNodes:false, processNewNodeContents:true}; 
		});
	},
	
	/**
	 * Applies a tag mapping function to all the HTML tags in the given string.
	 * The function will be passed a DOM node, and is expected to return a result object that looks like this: 
	 *	{nodes:[SomeHtmlNodesHere],processNewNodes:true,processNewNodeContents:true}
	 *  the nodes array will be used to replace the node passed in to the mapping function (can be null to remove the node)
	 *	If processNewNodes is true, the mapping function will be applied to the nodes before they are put into the output
	 *  If processNewNodeContents is true, then the children of the nodes will be examined even if the nodes themselves are not
	 */
	mapTags : function(str, func){
		if(!func){
			return str;
		}
		
		return this.mapNodes(str, function(n){
			if(n.nodeType != ng_nodeProcessor.ELEMENT_NODE_TYPE){
				return {nodes:[n], processNewNodes:false, processNewNodeContents:true};
			} else {
				return func.apply(n, [n]);
			}
		});
	},
	
	/**
	 * Applies a mapping function to all DOM nodes in the given string, including text nodes and anything else the browser gives you.
	 * The function will be passed a DOM node, and is expected to return a result object that looks like this: 
	 *	{nodes:[SomeHtmlNodesHere],processNewNodes:true,processNewNodeContents:true}
	 *  the nodes array will be used to replace the node passed in to the mapping function (can be null to remove the node)
	 *	If processNewNodes is true, the mapping function will be applied to the nodes before they are put into the output
	 *  If processNewNodeContents is true, then the children of the nodes will be examined even if the nodes themselves are not
	 */
	mapNodes : function(str, mapFunc){
		if(!str || !str.length){
			return str;
		}
		
		var bogusRootNode = ng_nodeProcessor.htmlToNodes(str, true);
		var newNodeArr = ng_nodeProcessor.mapNodesRecursive(bogusRootNode.childNodes, mapFunc);
		if(!newNodeArr || newNodeArr.length == 0){
			return str;
		} else {
			ng_nodeProcessor.replaceChildNodes(bogusRootNode, newNodeArr);
			return bogusRootNode.innerHTML;
		}
		
//		var node = this.htmlToNodes(str, true);
//		var newNodeArr = this.mapNodesRecursive(node.childNodes, mapFunc);
//		if(!newNodeArr || newNodeArr.length == 0){
//			return "";
//		} else {
//			this.replaceChildNodes(node, newNodeArr);
//			return node.innerHTML;
//		}
	},

	/** 
		The following filters are taken from Brian Bittman's TT_Filter library
		However they have been modified to not throw errors if the arguments are null/undefined
		http://trimpath.com/project/attachment/wiki/JavaScriptTemplateModifiers/tt_filters.js
	**/
	//Convert to upper case
	upper : function(str) { return (str ? str.toUpperCase() : str); },
	
	//convert to lower case
	lower : function(str) { return (str ? str.toLowerCase() : str); },
    
    //upper case first character
	ucfirst : function(str) { 
		return (str ? str.substring(0, 1).toUpperCase() + str.substring(1, str.length) : str); 
	},
	
	//lower case first character
	lcfirst : function(str) { 
		return (str ? str.substring(0, 1).toLowerCase() + str.substring(1, str.length) : str); 
	},
    
    //Trim whitespace from a string
    //NOTE: Seems to have issues in IE6...
	trim : function(str) {
		return (str ? str.toString().replace(/^\s+/, '').replace(/\s+$/, '') : str);
	},
    
    //Collapse whitespace
	//NOTE: Seems to have issues in IE6...
	collapse : function(str) {
		return (str ? str.toString().replace(/^\s+/, '').replace(/\s+$/, '').replace(/\s+/g, ' ') : str);
	},
    
    //HTML escape
	html : function(str) {        
		return (str ? 
			str.toString()
				.replace(/&/g, '&amp;')
				.replace(/\</g, '&lt;')
				.replace(/\>/g, '&gt;')
				.replace(/"/g, "&quot;")
				.replace(/'/g, "&#39;")  //replace apostrophes -- note the IE6 doesn't support &apos; notation
			: str);
	},
        
    //Truncate a string to a certain number of characters (like substring)
	truncate : function(str, length) {
		if(!str)return str;
		if(!length) length = 32;
		if(str.length <= length) return str;
		return str.substring(0, length - 3) + "...";
	},
	
	replace : function(str, regex, replacement){
		if(!str || !regex)return str;
		replacement = replacement || "";
		return str.toString().replace(regex, replacement);
	},
	
	//pipe to window.alert() and then eat the text
	alert : function(str) {
		alert(str);
		return "";
	}
	/** End TT_Filters code **/
};

//Namespacing object for a bunch of HTML/node munging stuff
var ng_nodeProcessor = {
	ELEMENT_NODE_TYPE : 1,
	//ATTRIBUTE_NODE_TYPE : 2,
	TEXT_NODE_TYPE : 3,
	
	
	//The Uber-Excerpter. Cuts the text passed in to approximately the length specified. Approximate because it looks for a space to cut at
	//The string may contain arbitrary HTML. Tags will not be taken into account for text length.
	//tags may be 
	//arguments are:
	//	str: (required) the string to cut down
	//	length: (required) the approximate length to cut down to.
	//	suffix: (optional) Iff the content has been modified, this text will be appended 
	//	removeTagsAfterMaxReached: boolean, after we've reached the maximum length, should other HTML tags be removed? 
	//								If false, tags will be retained but text will be removed. Note that this may leave empty <P>, <DIV>, <SPAN>, etc. 
	//								Optional, default true.
	//
	//	alwaysKillTagsArr: an array of tag names that should always be removed regardless of position. Example, ["img", "p", "embed"]
	//	keepContentsOfKilledNodes: indicates whether nodes from the alwaysKillTagsArr should still have any text content retained. Optional, default true.
	excerpt : function(str, length, suffix, removeTagsAfterMaxReached, alwaysKillTagsArr, keepContentsOfKilledNodes, alwaysKeepTags){
		//turn the kill/keep tags arrays into hashes for easier lookups later
		var killTags = {};
		var keepTags = {};
		removeTagsAfterMaxReached = (removeTagsAfterMaxReached == undefined ? true : removeTagsAfterMaxReached);
		keepContentsOfKilledNodes = (keepContentsOfKilledNodes == undefined ? true : keepContentsOfKilledNodes);
		if(alwaysKillTagsArr){
			for(var i = 0; i < alwaysKillTagsArr.length; i++){
				killTags[alwaysKillTagsArr[i].toUpperCase()] = true;
			}
		}
		if(alwaysKeepTags){
			for(var i = 0; i < alwaysKeepTags.length; i++){
				keepTags[alwaysKeepTags[i].toUpperCase()] = true;
			}
		}
		
		
		var mapState = {count:0, maxLength:length};
		
		//This is function that actually does the excerpting
		var mapFunc = function(node){
			var tagName = (node.tagName ? node.tagName.toUpperCase() : "NONE");
			var killTag = !keepTags[tagName] && tagName != "NONE" && (killTags[tagName] || killTags["*"] || killTags["ALL"]);
			var resultNodes = [node];
			
			if(killTag){
				mapState.modified = true;
				//if this node is on the "always kill" list, then just get rid of it
				if(keepContentsOfKilledNodes && node.childNodes){
					return {nodes:node.childNodes,processNewNodes:true};
				} else {
					return {nodes:null};
				}
			} else if(mapState.count >= mapState.maxLength){
				//if we're over length limit, kill all text nodes
				//for other nodes if they set the option to kill everything after the end, and this isn't a keepTag, kill it
				if(node.nodeType == this.TEXT_NODE_TYPE || removeTagsAfterMaxReached){
					mapState.modified = true;
					return {nodes:null};
				} 
			} else if(node.nodeType == this.TEXT_NODE_TYPE){
				if((node.nodeValue.length + mapState.count) > mapState.maxLength){ 
					//this text would put us over limit, so we need to truncate in the middle of the node
					//we're going to try to break at the next whitespace character if we can, 
					//but if it would make the string twice as long we'll just cut in the middle of a really long word
					mapState.modified = true;
					node.nodeValue = this.cutTextToLength(node.nodeValue, (mapState.maxLength - mapState.count), (mapState.maxLength * 0.25));
					
					//We're going to cheat here, because cutTextToLength might trim off a couple characters and put us back under limit, which looks weird
					//So this forces us to stop putting in text at this point
					mapState.count = mapState.maxLength; 
				} else {
					mapState.count += node.nodeValue.length;
				}
				
				//If a suffix was specified, append it onto the last textnode
				if(suffix && suffix.length > 0 && mapState.count >= mapState.maxLength){
                    var suffixNodes = ng_nodeProcessor.htmlToNodes(suffix);
                    if(suffixNodes){
                        for(var i = 0; i < suffixNodes.length; i++){
                            resultNodes.push(suffixNodes[i]);
                        }
                    }
				}
				
				return {nodes:resultNodes,processNewNodes:false, processNewNodeContents:false};
			}

			return {nodes:resultNodes,processNewNodes:false, processNewNodeContents:true};
		}
		
		var node = this.htmlToNodes(str, true);
		var newNodeArr = this.mapNodesRecursive(node.childNodes, mapFunc);
		if(!newNodeArr || newNodeArr.length == 0){
			return "";
		} else {
			this.replaceChildNodes(node, newNodeArr);
			return node.innerHTML;
		}
	},
	
	//Cut a piece of text, preferably on a whitespace boundary. Also attempts to trim uninteresting characters from the end
	//params: 
	//	txt: the text to cut
	//	desiredLength: the minimum length
	//	maxExtraLength: the maximum number of characters to go past minLength to look for a space
	cutTextToLength : function(txt, minLength, maxExtraLength){
		var substr = txt.substring(minLength, minLength + maxExtraLength); //grab everything after the desired cutpoint
        var re = new RegExp("[\\s\\.\\-_!@#$%^&*()\\[\\]{},<>/?~`]");
		var nextSpace = substr.search(re); //look for the first non-word character
		
		if(nextSpace >= 0){
			txt = txt.substring(0, minLength + nextSpace);
		} else {
			txt = txt.substring(0, minLength);
		}
		
		//If there's some goofy punctuation or a very short word at the end just throw it away
		//Note: Commenting this out because it treats non-ASCII letters as junk and throws them away. Whoops.
		//txt = txt.replace(/[^\w\d]((\S{1,2})|([^a-zA-Z0-9]*))$/, "");
		
		return txt;
	},
	
	//recurse through a node and it's children, accumulating all the text from text nodes inside it
	getInnerTextRecursive : function(node){
		if(node.nodeType == this.TEXT_NODE_TYPE){
			return node.nodeValue;
		} else if(node.childNodes){
			var s = "";
			for(var i = 0; i < node.childNodes.length; i++){
				s += " " + this.getInnerTextRecursive(node.childNodes[i]);
			}
			return s;
		}
	},
	
	//This isn't exactly useful as a modifier by itself, but it is useful for other modifiers
	//Basically takes text and turns it into DOM nodes
	//if returnParent is true, then it returns the artificial parent node it created to contain the nodes from the text
	//otherwise it just returns the nodes from the text in the first argument
	htmlToNodes : function(txt, returnParent){
		try{
			var tn = document.createElement("div");
			
			//Note, FF3 does not support innerHTML on document fragments, otherwise we would just do that
			var frag = document.createDocumentFragment();
			frag.appendChild(tn);
			
			var tableType = "none";
			if((/^\s*<t[dh][\s\r\n>]/i).test(txt)) {
				txt = "<table><tbody><tr>" + txt + "</tr></tbody></table>";
				tableType = "cell";
			} else if((/^\s*<tr[\s\r\n>]/i).test(txt)) {
				txt = "<table><tbody>" + txt + "</tbody></table>";
				tableType = "row";
			} else if((/^\s*<(thead|tbody|tfoot)[\s\r\n>]/i).test(txt)) {
				txt = "<table>" + txt + "</table>";
				tableType = "section";
			}
			tn.innerHTML = txt;
			tn.normalize();

			var parent = null;
			switch(tableType) {
				case "cell":
					parent = tn.getElementsByTagName("tr")[0];
					break;
				case "row":
					parent = tn.getElementsByTagName("tbody")[0];
					break;
				case "section":
					parent = tn.getElementsByTagName("table")[0];
					break;
				default:
					parent = tn;
					break;
			}
			
			if(returnParent){
				return parent;
			} else {
				var nodes = [];
				for(var x=0; x<parent.childNodes.length; x++){
					nodes.push(parent.childNodes[x].cloneNode(true));
				}
				return nodes;
			}
		} catch(e){
			return null;
		}
	},
	
	//Recursively walks through a set of nodes and applies a mapping function to each one
	//the mapping function is expected to return a result object that looks like this: 
	//	{nodes:[SomeHtmlNodesHere],processNewNodes:true}
	//  the nodes array will be used to replace the node passed in to the mapping function (can be null to remove the node)
	//	If processNewNodes is true, the mapping function will be applied to the nodes before they are put into the output
	mapNodesRecursive : function(nodes, mapFunc){
		var newNodes = [];
		for(var i = 0; i < nodes.length; i++){
			var res = mapFunc.apply(this, [nodes[i]]);
			
			if(!res || !res.nodes || res.nodes.length == 0){ //kill the node (ie, don't add to the newNodes list)
				continue;
			} else {
				if(res.processNewNodes){ //returned a set of nodes, but we need to run them back through the function as well
					res.nodes = this.mapNodesRecursive(res.nodes, mapFunc);
					if(!res.nodes || res.nodes.length == 0){
						continue;
					}
				} else if(res.processNewNodeContents){
					for(var j = 0; j < res.nodes.length; j++){
						var n = res.nodes[j];
						try{
							if(n && n.childNodes && n.childNodes.length > 0){
								var newChildren = this.mapNodesRecursive(n.childNodes, mapFunc);
								this.replaceChildNodes(n, newChildren);
							}
						} catch(e){
						}
					}
				}
				
				for(var j = 0; j < res.nodes.length; j++){
					var n = res.nodes[j];
					if(n){
						newNodes.push(n);
					}
				}
			}
		}
		return newNodes;
	},
	
	destroyNode : function(node){
		if(node.parentNode){
			node.parentNode.removeChild(node);
		}
//		if(node.nodeType != 3){ // ignore TEXT_NODE
//			if(node.outerHTML){
//				node.outerHTML=''; 
//			}
//		}
	},
	
	removeChildNodes : function(node){
		while(node.childNodes && node.childNodes.length){
			//Interestingly, if your markup is fucked up enough IE6/7 can get into a case where there are childnodes, 
			//but node.lastChild is still null. So don't use the following code, even though it's clearer & more succinct
			//this.destroyNode(node.lastChild);
			
			this.destroyNode(node.childNodes[node.childNodes.length - 1]);
		}
	},
	
	addChildNodes : function(node, childNodes){
		for(var i = 0; i < childNodes.length; i++){
			node.appendChild(childNodes[i]);
		}
	},
	
	replaceChildNodes : function(node, childNodes){
		this.removeChildNodes(node);
		this.addChildNodes(node, childNodes);
	}
};

typeof ng=="undefined"&&(window.ng={}),typeof ng.dsr=="undefined"&&(ng.dsr={_onDocReady:[],_onScriptLoadBound:!1,_dsrRequests:{},_dsrIndex:0,_docReady:!1,isDocumentReady:function(){return!this._docReady&&!/Apple/i.test(navigator.vendor)&&document.readyState&&document.readyState!="complete"&&document.readyState!="loaded"?!1:this._docReady=!0},bind:function(n,t,i,r,u){var f,e;if(!this.isDocumentReady()){var l=document.onreadystatechange,h=!1,c,s=arguments,o=function(){!h&&ng.dsr.isDocumentReady()&&(h=!0,clearInterval(c),s.callee.apply(ng.dsr,s),f&&f())};document.onreadystatechange=o,c=setInterval(o,1e3);return}t=t||{},i=i||function(){},this._onScriptLoadBound||(f=window.ng_scriptload,window.ng_scriptload=function(n){ng.dsr._finishDsrRequest(n);if(f)try{f()}catch(t){}},this._onScriptLoadBound=!0),t._dsrId=u=u||"_ngdsr_"+this._dsrIndex++,ng.dsr._dsrRequests[u]?(e=ng.dsr._dsrRequests[u],e.callbacks.push(i)):(e={callbacks:[i]},r&&(e.timeoutHandle=setTimeout(function(){ng.dsr._finishDsrRequest({id:t._dsrId,status:999,statusText:"timeout"})},r)),ng.dsr._dsrRequests[u]=e,this.createScriptBlock(n,t))},isObject:function(n){return typeof n=="undefined"?!1:typeof n=="object"||n===null||n instanceof Array||typeof n=="array"||n instanceof Function||typeof n=="function"},createScriptBlock:function(n,t){var f="?",r,u,e,i;for(r in t)u=t[r],e=typeof u=="undefined"?"":encodeURIComponent(u),n+=f+r+"="+e,f="&";return i=document.createElement("script"),i.type="text/javascript",i.src=n,i.defer="defer",i.lang=i.language="javascript",i.className="NGDsrRequest",t._dsrId&&(i.id=t._dsrId),document.getElementsByTagName("HEAD")[0].appendChild(i),i},_finishDsrRequest:function(n){var t,r,i;if(!n||!n.id)return;t=ng.dsr._dsrRequests[n.id];if(!t)return;for(setTimeout(function(){var t=document.getElementById(n.id);t&&t.parentNode&&t.parentNode.removeChild(t)},0),t.timeoutHandle&&clearTimeout(t.timeoutHandle),ng.dsr._dsrRequests[n.id]=null,r=0;r<t.callbacks.length;r++)try{i=t.callbacks[r],n.status==999?i(!1,n.response,"timeout"):n.status==200?i(!0,n.response,"successful"):i(!1,n,"error")}catch(u){}}});

if(typeof ng == "undefined"){
	ng = {};
}

/**
 * constructor for an analytics object
 @ param {Object} config An object to configure this analytics engine. Expects these properties: ngAcct, clientAcct, clientAnalytics, getPrefix
 */
ng.Analytics = function(config){
	this.disabled = config.disabled;
	this.googleDisabled = config.googleDisabled;
	this.ngAcct = config.ngAcct || "UA-130916-4";
	this.clientAcct = config.clientAcct;
	this.clientAnalytics = config.clientAnalytics || "Google";
	this.orgCode = config.orgCode || "NoOrg";
	this.location = document.location.host;
	this.buzzObj = config.buzzObj;
	this.directUrl = config.directUrl || (config.buzzObj ? config.buzzObj.directUrl : null) || "http://hosted.newsgator.com/";
	
	if(config.buzzObj && config.buzzObj.buzzTracking){
		var bt = config.buzzObj.buzzTracking;
		this.buzzTrackingId = bt.myTrackingId;
		this.parentTrackingId = bt.parentTrackingId;
		this.masterBuzzId = bt.masterBuzzId;
	}
	
	//this.bootstrap();
};

ng.Analytics.prototype = {
	_loadUrchin : function(){
		if(this.disabled){
			return;
		}
		
		if(!this.googleDisabled){
			//ensure google tracking code is loaded
			//note we're just going to fire-and-forget the google code
			if(!ng.Analytics.urchinLoaded){
				ng.Analytics.urchinLoaded = true;
				var urchin = "http://www.google-analytics.com/urchin.js";
				
				//Check to see if there's a script block on the page for loading the urchin
				var urchinRegexp = new RegExp("^" + urchin, "i");
				var blocks = document.getElementsByTagName("SCRIPT");
				var urchinFound = false;
				for(var i = 0; i < blocks.length; i++){
					if(blocks[i].src && urchinRegexp.test(blocks[i].src)){
						urchinFound = true;
						break;
					}
				}
				
				if(typeof _uacct == "undefined" && !urchinFound){
					ng.dsr.createScriptBlock(urchin, {});
				}
			}
		}
	},
	
	_waitForUrchin : function(callback){
		ng.Analytics.urchinCallbacks = (ng.Analytics.urchinCallbacks || []);
		ng.Analytics.urchinCallbacks.push(callback);
		
		if(!ng.Analytics.urchinInterval){
			this.debug("Creating interval to wait for GA urchin tracker to load");
			var _this = this;
			ng.Analytics.urchinInterval = setInterval(function(){
				if(typeof urchinTracker != "undefined"){
					_this.debug("urchin tracker loaded, executing queued callbacks");
					
					clearInterval(ng.Analytics.urchinInterval);
					ng.Analytics.urchinInterval = null;
					var cb;
					while((cb = ng.Analytics.urchinCallbacks.shift()) != null){
						try{
							cb();
						} catch(e){}
					}
				}
			}, 250);
		}
	},

	/**
	 * Log an event related to a particular post
	 * @param {String} eventName The type of event to log (eg, "click", "email", "im")
	 * @param {Number} postId The postId the event is related to
	 * @param {String} link The post's link (aka HtmlUrl)
	 */
	logPostEvent : function(eventName, postId, feedId, link, evt, node){
		this._logNG(eventName, postId, feedId);
		
		var e = eventName + "/" + (postId || 0);
		if(link){
			var fixedLink = (link ? link.replace(/https?:\/\//i, "") : "");
			e += "/" + fixedLink;
		} 
		this._log(e);
		this._callCustom(eventName, postId, link, evt, node);
	},
	
	logGenericEvent : function(eventName, evt, node){
		this._logNG(eventName);
		this._log(eventName);
		this._callCustom(evt, node, eventName, null, null);
	},
	
	_callCustom : function(eventName, postId, link, evt, node){
		//Look for the generic analytics wrapper function
		if(!this.disabled && typeof ng_logAnalytics != "undefined"){
			evt = evt || window.event;
			ng_logAnalytics(this.buzzObj, eventName, this.orgCode, this.location, this.identifier, postId, link, evt, node);
		}
	},

	/**
	 * Log an event to a Google Analytics account. Will use the Newsgator account by default, otherwise another one can be specifed.
	 * @param {String} eventName The value to be passed to Google Analytics for logging
	 */
	_log : function(eventName){
		if(this.disabled){
			this.debug("Attempted to log event '" + eventName + "' but analytics is disabled");
			return;
		}
		
		
		if(!this.googleDisabled){
			if(!ng.Analytics.urchinLoaded){
				this._loadUrchin();
			}

			try{
				if(typeof urchinTracker == "undefined"){
					var _this = this;
					this._waitForUrchin(function(){
						_this._log(eventName);
					});
					return;
				}
				
				//Stick all the stuff into the event
				var e = this.location + "/" + this.orgCode + "/" + this.buzzObj.buzzId + "/" + eventName;
				
				var olduacct = _uacct; //save off the old _uacct info
				if(this.ngAcct){
					_uacct = this.ngAcct;
					urchinTracker(e);
				}
				
				if(this.clientAcct){
					if(this.clientAnalytics == "Google"){
						_uacct = this.clientAcct;
						urchinTracker(e);
					}
				
	//				if(this.analytics.clientAnalytics == "Omniture"){
	//					//do stuff
	//				}
				}
				_uacct = olduacct;
			} catch(e){
				this.debug("Error logging analytics event: " + e.toString());
			}
		}
	},
	
	_logNG : function(eventName, postId, feedId){
		if(this.disabled){
			this.debug("Attempted to log event '" + eventName + "' but analytics is disabled");
			return;
		}
		
		try{
			var url = this.directUrl + "NGBuzz/attn.ashx?eventName=" + encodeURIComponent(eventName);
			
			var args = {
				pid:postId,
				fid:feedId,
				org:this.orgCode,
				u:this._pageUrl(),
				t:(new Date()).getTime()
			};
			if(this.buzzObj){
				args.buzzId = this.buzzObj.buzzId;
				args.trkp = this.parentTrackingId;
				args.trkm = this.buzzTrackingId;
				args.mid = this.masterBuzzId;
			}

			//On "view" events attach the Gigya comScore tracking bug
			if ((/^\s*view\s*$/i).test(eventName)) {
				args.CXNID = "2000002.11NXC";
			}
			
			for(var key in args){
				if(args[key]){
					url += "&" + key + "=" + encodeURIComponent(args[key]);
				}
			}
			
			var img = new Image(1,1);
			img.src = url;
			img.onload = img.onerror = function(){
				img.onload = img.onerror = null;
			};
		} catch(e){
			this.debug("Error logging analytics to Newsgator: " + e.toString());
		}
	},
	
	_pageUrl : function(){
		if(this.pageUrl){
			return this.pageUrl;
		} 	
		
		try{
			this.pageUrl = document.location.toString();
			return this.pageUrl;
		} catch(e){}
	
		try{
			this.pageUrl = window.location.toString();
			return this.pageUrl;
		} catch(e){}
	
		return "";
	},
	
	debug : function(msg){
		if(this.buzzObj){
			this.buzzObj.debug("Analytics debugging:", msg);
		}
	}
};


/**
* Randomize the order of the elements in an array
*/
/*
function ng_randomizeArray(arr) {
var i = arr.length;
if (i == 0) return false;
while (--i) {
var j = Math.floor(Math.random() * (i + 1));
var tempi = arr[i];
var tempj = arr[j];
arr[i] = tempj;
arr[j] = tempi;
}
}
*/

/**
* Creates a dynamic stylesheet element and inserts CSS text into it
*/
function ng_insertCSS(cssText) {
	var styleElement = document.createElement("STYLE");
	styleElement.type = "text/css";
	styleElement.base = this.buzzAppUrl;
	styleElement.href = this.buzzAppUrl;

	if (styleElement.styleSheet) {
		styleElement.styleSheet.cssText = cssText;
	} else {
		styleElement.appendChild(document.createTextNode(cssText));
	}

	document.getElementsByTagName("HEAD")[0].appendChild(styleElement);
}

var ng_isSafari = (navigator && navigator.vendor && navigator.vendor.indexOf("Apple") >= 0);

/**
* Get the location of an event click. Basically [evt.clientX, evt.clientY], but with a fix applied for Safari being frigging stupid. 
* IE does dumb shit, but at least they've got a huge market share. What's Safari's excuse? DIE SAFARI DIE!
*/
function ng_getEventLocation(evt) {
	var e = (evt || event);
	if (ng_isSafari) {
		return [e.pageX - ng_getScrollLeft(), e.pageY - ng_getScrollTop()];
	} else {
		return [e.clientX, e.clientY];
	}
}

/* 
* Absolutely position an element at an event, taking into account scrolling and viewport
* Note this forces the element's position to absolute.
*/
function ng_positionPopupAtEvent(evt, form, offset) {
	var pos = ng_getEventLocation(evt);
	ng_positionPopupAtLocation(form, pos[0], pos[1], offset);
}

/* 
* Absolutely position an element at a location, taking into account scrolling and viewport.
* Note this forces the element's position to absolute.
*/
function ng_positionPopupAtLocation(form, x, y, offset) {
	var viewportWidth = ng_getViewportWidth();
	var viewportHeight = ng_getViewportHeight();
	var scrollTop = ng_getScrollTop();
	var scrollLeft = ng_getScrollLeft();

	ng_positionPopupWithinBounds(form, x, y, offset, scrollLeft, scrollTop, (viewportWidth + scrollLeft), (viewportHeight + scrollTop));
}

function ng_positionPopupWithinBounds(form, x, y, offset, minX, minY, maxX, maxY) {
	try {
		offset = offset || 0;
		if (form.parentNode != document.body) {
			form.parentNode.removeChild(form);
			document.body.appendChild(form);
		}


		//And now we do a bunch of crap to figure out where to put the popup...
		var left = x + offset;
		var top = y + offset;

		function getProp(prop) {
			return parseInt(ng_getStyle(form, prop)) || 0;
		}


		form.style.display = "block";
		var borderArr = ng_getBorder(form, 1);
		var paddingArr = ng_getPadding(form, 5);

		var height = form.offsetHeight + borderArr[0] + borderArr[2] + paddingArr[0] + paddingArr[2];
		var width = form.offsetWidth + borderArr[1] + borderArr[3] + paddingArr[1] + paddingArr[3];
		form.style.display = "none";

		//it's taller than the space allotted. We can't win -- just show it at minY
		if (height > (maxY - minY) || top < minY) {
			top = minY;
		} else if ((top + height) > maxY) {
			top = (maxY - height);
		}

		//it's taller than the space allotted. We can't win -- just show it at minY
		if (width > (maxX - minX) || left < minX) {
			left = minX + borderArr[3];
		} else if ((left + width) > maxX) {
			left = (maxX - width);
		}
	} catch (e) {
		ng_debug("Error positioning popup: " + e.toString());
		//something went wrong, just take a guess...
		var left = minX;
		var top = minY;
	}

	form.style.position = "absolute";
	form.style.left = left + "px";
	form.style.top = top + "px";
	return [
		(maxX - minX < width ? maxX - minX - (borderArr[1] + borderArr[3] + paddingArr[1] + paddingArr[3]) : 0),
		(maxY - minY < height ? maxY - minY - (borderArr[0] + borderArr[2] + paddingArr[0] + paddingArr[2]) : 0)
	];
}



function ng_getViewportHeight() {
	return window.innerHeight || (document.documentElement ? document.documentElement.clientHeight : null) || document.body.clientHeight || 0;
}

function ng_getViewportWidth() {
	return (document.documentElement ? (document.documentElement.clientWidth || document.documentElement.innerWidth) : null) || window.innerWidth || document.body.clientWidth || 0;
}

function ng_getScrollTop() {
	//ng_debug("window.pageYOffset: ", window.pageYOffset, "document.documentElement.scrollTop:", document.documentElement.scrollTop, "document.body.scrollTop: ", document.body.scrollTop);
	return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
}

function ng_getScrollLeft() {
	return window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
}

//var ng_isSafari = (navigator.vendor && navigator.vendor.indexOf("Apple") >= 0);

var ng_isIE = (navigator.userAgent && navigator.userAgent.indexOf("MSIE") >= 0);
var ng_ieVersion = ng_isIE && function() {
	try {
		var ver = navigator.userAgent.replace(/^.*MSIE ([\d\.]+).*$/i, "$1");
		return parseFloat(ver);
	} catch (e) {
		ng_debug("Failed to determine IE version number", e);
		return 0;
	}

} ();

/**
* Creates a blocking iframe to prevent combo boxes showing though our dialogs in IE6 & less
*/
function ng_addBackgroundIframe(node) {
	if (ng_isIE && ng_ieVersion < 7.0) {
		try {
			//If the page uses HTTPS, then IE6 will show an error if the frame source is javascript:void(0)
			//Probably the "correct" solution is to load a blank HTML page over HTTPS instead
			//But that's a PITA, so we're just going to skip the blocker frames in that case
			if ((/https/i).test(document.location.protocol)) {
				return;
			}
		
			var f = document.createElement("IFRAME");

			f.src = "javascript:void(0)";
			f.style.position = "absolute";
			f.style.left = f.style.top = "0px";
			f.style.width = f.style.height = "100%";
			f.style.zIndex = -1;
			f.frameborder = 0;
			f.scrolling = "no";
			f.style.filter = "Alpha(Opacity=\"0\");";
			f.tabIndex = -1;
			f.className = "ng_backgroundIframe";
			node.appendChild(f);
		} catch (e) {
			ng_debug("Error adding background iframe: " + e.toString());
		}
	}
}

function ng_removeBackgroundIframe(node) {
	if (node) {
		var frames = node.getElementsByTagName("iframe");
		for (var i = 0; i < frames.length; i++) {
			if (frames[i].className.indexOf("ng_backgroundIframe") >= 0) {
				frames[i].parentNode.removeChild(frames[i]);
			}
		}
	}
}

var ng_debugEnabled = function() {
	try {
		if (document.cookie && /NGBuzzDebug=([^;]+);?/.test(document.cookie)) {
			return (RegExp.$1 == "1");
		} else if (document.location.host) {
			var host = document.location.host.toLowerCase();
			switch (host) {
				case "localhost":
				case "ngwhitelabel":
				case "beta.tagware.com":
					return true;
				default:
					return false;
			}
		}
		//default to off
		return false;
	} catch (e) {
		setTimeout(function() {
			ng_debug("Error determining if debugging should be enabled, defaulting to true", e.toString());
		}, 1);
		return true; //safest thing to do is enable debugging
	}
} ();

function ng_setDebug(enabled) {
	ng_debugEnabled = enabled;
	var c = document.cookie || "";

	var d = new Date();
	d.setFullYear(d.getFullYear() + 20);
	var expires = "; expires=" + d.toUTCString() + "; path=/;";

	if (enabled && c.indexOf("NGBuzzDebug=") < 0) {
		document.cookie = "NGBuzzDebug=1" + expires;
	} else {
		document.cookie = ("NGBuzzDebug=" + (enabled ? 1 : 0)) + expires;
	}
}

/**
* Write debugging info to any available debugging console (currently FireBug, Safari JS Console, or the IE WebDev Helper sidebar)
* Note that the function takes any number of arguments and writes them all, even though there are no formal parameters.
*/
function ng_debug() {
	if (!ng_debugEnabled || (typeof window.console == "undefined" && typeof window.debugService == "undefined")) {
		return;
	}

	var canLog = ((window.console && console.log) || window.debugService);
	if (!canLog) {
		return;
	}

	var msg = "";
	if (arguments && arguments.length > 0) {
		for (var i = 0; i < arguments.length; i++) {
			try {
				if (arguments[i] === null) {
					msg += "(null)";
				} else if (arguments[i] === undefined) {
					msg += "(undefined)";
				} else {
					msg += arguments[i].toString();
				}
				msg += "  ";
			} catch (e) {
				msg += "(error rendering argument)";
			}
		}
	}
	//Log to Safari JS Console and Firebug console
	if (window.console && console.log) {
		console.log(msg);
	}
	//Log to the IE Web Dev Helper (http://projects.nikhilk.net/Projects/WebDevHelper.aspx)
	//Note, do not detect for debugService.trace. For some reason that causes it to not work.
	else if (window.debugService) {  // && debugService.trace
		debugService.trace(msg);
	}
}

/**
* Find the X position of an element
*/
function ng_findPosX(obj) {
	var curleft = 0;
	if (obj.offsetParent) {
		while (obj) {
			curleft += obj.offsetLeft;
			obj = obj.offsetParent;
		}
	} else if (obj.x) {
		curleft += obj.x;
	}
	return curleft;
}

/**
* Find the Y position of an element
*/
function ng_findPosY(obj) {
	var curtop = 0;
	if (obj.offsetParent) {
		while (obj) {
			curtop += obj.offsetTop;
			obj = obj.offsetParent;
		}
	} else if (obj.y) {
		curtop += obj.y;
	}
	return curtop;
}


/**
* Show a popup form over a buzz box. This covers the entire buzz box, if one is available. 
* If no buzz container is found a standard popup will be shown at the event location.
* Note that the popup will be stuck in the DOM directly under the BODY tag, to avoid possible styling issues.
* @param {Element} elem the element to start at
* @param {String or Element} popupIdOrNode the popup's DOM node or ID
* @param {Function} postShowCallback an optional function to call after the fade in is complete
*/
function ng_showWidgetForm(evt, startElem, popupIdOrNode, postShowCallback, maxWidth, maxHeight) {
	var buzzContainer = ng_getBuzzContainer(startElem);
	return ng_showPopupOverElement(evt, buzzContainer, popupIdOrNode, postShowCallback, maxWidth, maxHeight);
}

function ng_showPopupOverElement(evt, container, popupIdOrNode, postShowCallback, maxWidth, maxHeight) {
	var clientPopup = (popupIdOrNode.parentNode ? popupIdOrNode : document.getElementById(popupIdOrNode));

	if (container) {
		var x = ng_findPosX(container);
		var y = ng_findPosY(container);
		clientPopup.style.top = y + "px";
		clientPopup.style.left = x + "px";
		var containerWidth = (container.offsetWidth - 7);
		var containerHeight = (container.offsetHeight - 7);
		clientPopup.style.width = (maxWidth ? Math.min(maxWidth, containerWidth) : containerWidth) + "px";
		clientPopup.style.height = (maxHeight ? Math.min(maxHeight, containerHeight) : containerHeight) + "px";
		clientPopup.style.display = "block";
	} else {
		ng_positionPopupAtEvent(evt, clientPopup, 5);
	}

	document.body.appendChild(clientPopup);
	clientPopup.style.display = "block";

	ng_fadeIn(clientPopup, function() {
		ng_addBackgroundIframe(clientPopup);
		if (postShowCallback) {
			postShowCallback();
		}
	});

	//make sure the popup content is visible
	var viewportHeight = ng_getViewportHeight();
	var scrollTop = ng_getScrollTop();

	if (scrollTop > y || scrollTop + viewportHeight < y) {
		window.scrollTo(0, y);
	}
}

/**
* Hide a popup form over a buzz box. This covers the entire buzz box, if one is available.
* @param {Element} elem the element to start at
* @param {String or Element} popupIdOrNode the popup's DOM node or ID
* @param {Function} postHideCallback an optional function to call after the fade out is complete
*/
function ng_hideWidgetForm(startElem, popupIdOrNode, postHideCallback) {
	var clientPopup = (popupIdOrNode.parentNode ? popupIdOrNode : document.getElementById(popupIdOrNode));

	ng_removeBackgroundIframe(clientPopup);

	ng_fadeOut(clientPopup, function() {
		clientPopup.style.display = "none";

		//put the popup back where we found it.
		try {
			if (clientPopup.ng_originalParent && clientPopup.ng_originalParent.appendChild) {
				clientPopup.parentNode.removeChild(clientPopup);
				clientPopup.ng_originalParent.appendChild(clientPopup);
				clientPopup.ng_originalParent = null;
			}
			clientPopup.style.width = clientPopup.ng_origWidth || "";
			clientPopup.style.height = clientPopup.ng_origHeight || "";
		} catch (e) { }

		if (postHideCallback) {
			postHideCallback();
		}
	});
}

var ng_hideWidgetPopup = ng_hideWidgetForm;


/**
* Show a popup form over a buzz box. Attempts to make the popup as large as possible while not going outside the bounds of the buzz container.
* Popup will be positioned as closely as possible to the supplied event
* If no buzz container can be found, a "normal" popup will be done instead 
* @param {Event} evt Event to position at
* @param {Element} elem the element to start searching for the buzz container. 
* @param {String or Element} popupIdOrNode the popup's DOM node or ID
* @param {Function} postShowCallback an optional function to call after the fade in is complete
* @param {Number} preferredWidth Optional preferred size of the popup. The popup may end up being smaller than this, but won't be larger. 
* @param {Number} preferredHeight Optional preferred size of the popup. The popup may end up being smaller than this, but won't be larger. 
*/
function ng_showWidgetPopup(evt, startElem, popupIdOrNode, postShowCallback, preferredWidth, preferredHeight) {
	var evtPos = ng_getEventLocation(evt);
	return ng_showWidgetPopupAtLocation(evtPos[0] + ng_getScrollLeft(), evtPos[1] + ng_getScrollTop(), startElem, popupIdOrNode, postShowCallback, preferredWidth, preferredHeight);
}

/**
* Show a popup form over a buzz box. Attempts to make the popup as large as possible while not going outside the bounds of the buzz container.
* Popup will be positioned as closely as possible to the supplied event
* If no buzz container can be found, a "normal" popup will be done instead 
* @param {Number} x The X-coordinate to position the popup at. This is a screen point, not relative to the buzzbox or the viewport
* @param {Number} y The Y-coordinate to position the popup at. This is a screen point, not relative to the buzzbox or the viewport
* @param {Element} elem the element to start searching for the buzz container. 
* @param {String or Element} popupIdOrNode the popup's DOM node or ID
* @param {Function} postShowCallback an optional function to call after the fade in is complete
* @param {Number} preferredWidth Optional preferred size of the popup. The popup may end up being smaller than this, but won't be larger. 
* @param {Number} preferredHeight Optional preferred size of the popup. The popup may end up being smaller than this, but won't be larger. 
*/
function ng_showWidgetPopupAtLocation(x, y, startElem, popupIdOrNode, postShowCallback, preferredWidth, preferredHeight) {
	var buzzContainer = ng_getBuzzContainer(startElem);
	var clientPopup = (popupIdOrNode.parentNode ? popupIdOrNode : document.getElementById(popupIdOrNode));

	clientPopup.ng_originalParent = clientPopup.parentNode;
	clientPopup.parentNode.removeChild(clientPopup);
	document.body.appendChild(clientPopup);

	if (preferredWidth) {
		clientPopup.style.width = parseInt(preferredWidth) + "px";
	}
	if (preferredHeight) {
		clientPopup.style.height = parseInt(preferredHeight) + "px";
	}

	var maxSize;
	if (buzzContainer) {
		var paddingArr = (ng_isIE ? ng_getPadding(buzzContainer, 5) : [0, 0, 0, 0]);
		var borderArr = ng_getBorder(buzzContainer, 1)
		var minX = ng_findPosX(buzzContainer) + borderArr[3];
		var minY = ng_findPosY(buzzContainer) + borderArr[0];
		var maxX = minX + buzzContainer.offsetWidth - paddingArr[1] - paddingArr[3];
		var maxY = minY + buzzContainer.offsetHeight - paddingArr[0] - paddingArr[2];

		maxSize = ng_positionPopupWithinBounds(clientPopup, x, y, 0, minX, minY, maxX, maxY);

		if (maxSize[0] > 0) {
			clientPopup.ng_origWidth = clientPopup.style.width;
			clientPopup.style.width = maxSize[0] + "px";
		}

		if (maxSize[1] > 0) {
			clientPopup.ng_origHeight = clientPopup.style.height;
			clientPopup.style.height = maxSize[1] + "px";
		}
	} else {
		ng_debug("Failed to locate buzz container to position popup: " + popupIdOrNode);
	}

	ng_fadeIn(clientPopup, function() {
		ng_addBackgroundIframe(clientPopup);
		if (postShowCallback) {
			postShowCallback();
		}
	});

	return maxSize;
}

/**
* From the given starting node, walks up the DOM until it finds something that looks like a buzz container node
* @param {Element} startElem the node to start from
*/
function ng_getBuzzContainer(startElem) {
	return ng_findParentWithClass(startElem, ["ng_buzzContainer", "ng_buzzAutoTarget"]);
}

/**
* A generic fade in function
* Rolled my own here to avoid loading an effects library with every buzz box
* Basically just repeatedly change the opacity
* @param {Element} elem the DOM node to fade
* @param {Function} cb optional function to call after fading is complete
*/
function ng_fadeIn(elem, cb) {
	if (!elem) {
		return;
	}
	ng_repeat(10, 2, function(t) {
		if (t == 10) {
			elem.style.opacity = elem.style.mozOpacity = elem.style.filter = "";
		} else {
			elem.style.mozOpacity = elem.style.opacity = t * 0.1;
			elem.style.filter = "alpha(opacity=" + t * 10 + ")";
		}
	}, cb);
	elem.style.display = "block";
}

/**
* A generic fade out function
* Rolled my own here to avoid loading an effects library with every buzz box
* Basically just repeatedly change the opacity
* @param {Element} elem the DOM node to fade
* @param {Function} cb optional function to call after fading is complete
*/
function ng_fadeOut(elem, cb) {
	if (!elem) {
		return;
	}
	ng_repeat(10, 2, function(t) {
		if (t == 10) {
			elem.style.display = "none";
		} else {
			elem.style.mozOpacity = elem.style.opacity = 1.0 - (t * 0.1);
			elem.style.filter = "alpha(opacity=" + (100 - (10 * t)) + ")";
		}
	}, cb);
}

/**
* Finds the first ancestor of a node that has any of the CSS classes specified
* @param {Element} elem the DOM node to start looking at
* @param {String or Array} classNames a class name or array of names to look at
*/
function ng_findParentWithClass(elem, classNames) {
	if (classNames.join) {
		classNames = classNames.join("|");
	}

	var e = elem;
	var re = new RegExp("(^|[^\w_-])(" + classNames + ")([^\w_-]|$)");
	while (e) {
		if (re.test(e.className)) {
			return e;
		}
		e = (e.ng_originalParent || e.parentNode);
		if (!e) {
			break;
		}
	}
	return null;
}

/**
* Just calls a function repeatedly for a fixed number of times
* Sort of like setInterval(), but with a predefined end point
* @param {Number} times the number of times to call the function
* @param {Number} interval the number of milliseconds between each call
* @param {Function} func The function to execute repeatedly
* @param {Function} postFunc optional function to call after all repetitions are complete
*/
function ng_repeat(times, interval, func, postFunc) {
	function f(t) {
		if (t <= times) {
			func(t);
			setTimeout(function() {
				f(t + 1);
			}, interval);
		} else if (postFunc) {
			postFunc();
		}
	}
	f(0);
}

/**
* Get the DOM node that is the target of an event
* @param {Event} evt the event object
*/
function ng_getEventTarget(evt) {
	evt = (evt ? evt : event);
	var tgt = evt.target || evt.srcElement;
	if (tgt.nodeType == 3) { //fix Safari bug that sends events to text nodes
		tgt = tgt.parentNode;
	}
	return tgt;
}

/**
* Get the computed style for an element. This is not 100% reliable in all browsers.
*/
function ng_getStyle(el, styleProp) {
	var x = document.getElementById(el) || el;
	if (x.currentStyle)
		var y = x.currentStyle[styleProp];
	else if (window.getComputedStyle)
		var y = document.defaultView.getComputedStyle(x, null).getPropertyValue(styleProp);
	return y;
}

/**
* Get the computed padding for an element. This is not 100% reliable in all browsers.
* returns an array of the padding in the same order that CSS expects them [top, right, bottom, left]
* @param {Element} el the element to get padding for
* @param {Number} minValue the smallest value to return - useful because some browsers (IE6) don't compute all styles properly, so you can get default values back
*/
function ng_getPadding(el, minValue) {
	minValue = minValue || 0;
	if (parseInt(ng_getStyle(el, "padding"))) {
		var p = parseInt(ng_getStyle(el, "padding")) || minValue;
		return [p, p, p, p];
	} else {
		return [
			parseInt(ng_getStyle(el, "padding-top")) || minValue,
			parseInt(ng_getStyle(el, "padding-right")) || minValue,
			parseInt(ng_getStyle(el, "padding-bottom")) || minValue,
			parseInt(ng_getStyle(el, "padding-left")) || minValue
		];
	}
}

/**
* Get the computed border for an element. This is not 100% reliable in all browsers.
* returns an array of the border in the same order that CSS expects them [top, right, bottom, left]
* @param {Element} el the element to get padding for
* @param {Number} minValue the smallest value to return - useful because some browsers (IE6) don't compute all styles properly, so you can get default values back
*/
function ng_getBorder(el, minValue) {
	minValue = minValue || 0;
	if (parseInt(ng_getStyle(el, "border"))) {
		var p = parseInt(ng_getStyle(el, "border")) || minValue;
		return [p, p, p, p];
	} else {
		return [
			parseInt(ng_getStyle(el, "border-top")) || minValue,
			parseInt(ng_getStyle(el, "border-right")) || minValue,
			parseInt(ng_getStyle(el, "border-bottom")) || minValue,
			parseInt(ng_getStyle(el, "border-left")) || minValue
		];
	}
}

function ng_clone(obj) {
	if (typeof (obj) != 'object' || !obj) {
		return obj;
	}

	var newObj = {};
	for (var i in obj) {
		newObj[i] = obj[i];
	}

	return newObj;
}

/**
* Attempt to create a GUID
* Note that a true GUID is not possible, so it's just a random string formatted like a GUID, which is not as unique as a real GUID
* So if possible you should generate a real GUID somewhere (like in C#) and pass it in.
*/
function ng_CreateGuid() {
	var result = '';
	for (var i = 0; i < 32; i++) {
		if (i == 8 || i == 12 || i == 16 || i == 20) {
			result += '-';
		}
		var hexDigit = Math.floor(Math.random() * 16).toString(16).toUpperCase();
		result = result + hexDigit;
	}

	return result;
}

/**
* Copies the properties from one object into another
* @param {Object} tgt The target object
* @param {Object} src The source object
*/
function ng_mixin(tgt, src) {
	var blankObj = {};
	for (var x in src) {
		if (typeof blankObj[x] == "undefined") {
			tgt[x] = src[x];
		}
	}
	return tgt;
}


function returnBuzzFooter(n,t,i,r,u,f,e,o){return arguments.length==1?getBuzzFooter(arguments[0]):getBuzzFooter({buzzId:n,apiToken:t,buzzObj:i,ctx:r,footerTemplate:u,getThisTemplate:f,emailTemplate:e,sharingSites:o})}function getBuzzFooter(n){function r(i){var u,r,f;i=i||{},i.buzzId=i.buzzId||c,i.apiToken=i.apiToken||vt,i.trkP=i.trkP||p,i.trkM=i.trkM||ng_CreateGuid();if(t&&n.preserveExtraArgs)for(r in t._extraArgs)i[r]=t._extraArgs[r];u=[];for(r in i)f=i[r]||"",u.push(r+"="+encodeURIComponent(f));return u.join("&")}function at(n){for(var r="",i,t=0;t<n.length;t++)i=n.charCodeAt(t),r+=i.toString(16);return r}function lt(n){return i+"Embed/Blogger.aspx?"+r(n)}function et(n){return i+"Embed/Typepad.aspx?"+r(n)}function g(n){var t=i+"Embed/UWAWidget.aspx?"+r(n);return"http://www.netvibes.com/subscribe.php?module=UWA&moduleUrl="+encodeURIComponent(t)}function nt(n){var t=i+"Embed/GoogleGadget.aspx?"+r(n);return"http://www.google.com/ig/add?moduleurl="+encodeURIComponent(t)}function ut(n){var t=i+"Embed/Pageflakes.aspx?"+r(n);return"http://www.pageflakes.com/AddFlake.aspx?URL="+encodeURIComponent(t)}function ft(n){return i+"buzz.ashx?"+r(n)}function v(n){var r="www.facebook.com",i="sharing_other_facebook_fbDomain";return t&&t.WidgetConfiguration&&t.WidgetConfiguration[i]&&(r=t.WidgetConfiguration[i]||"www.facebook.com"),n?"http://"+r+"/add.php?api_key="+n:""}function rt(n){return i+"embed/VistaSidebar.aspx?"+r(n)}function tt(n){var t=i+"Embed/LiveGadgetManifest.aspx/"+at(r(n))+"/";return"http://www.live.com/?add="+encodeURIComponent(t)}function it(n){var t=i+"Embed/LiveGadgetManifest.aspx/"+at(r(n))+"/";return"http://spaces.live.com/spacesapi.aspx?wx_action=create&mkt=en-us&wx_url="+encodeURIComponent(t)}var l,w,s,h;n=n||{};var u=n.ctx||this,t=n.buzzObj||u.BuzzObj,c=n.buzzId||t.buzzId,vt=n.apiToken||t.apiToken,a=t?t.buzzTracking:null,y=t&&t.ngBaseUrl?t.ngBaseUrl:"http://nmp.newsgator.com/",yt=t&&t.directUrl?t.directUrl:"http://hosted.newsgator.com/",b=n.fbApiKey||(t?t._extraArgs.fbApiKey||t.fbApiKey:null),d="Blogger,Typepad,Netvibes,Google,Pageflakes,Email,Script,Create",ht=d+",LiveDotCom,LiveSpaces";b&&(ht+=",Facebook",d+=",Facebook"),l=n.sharingOptions||ht,l=l.replace(/(limited)|(default)/ig,d),w=!(n.buzzId||n.apiToken||n.fbApiKey||n.sharingOptions&&!/^\s*(limited)|(default)\s*$/ig.test(n.sharingOptions)||n.getThisTemplate||n.emailTemplate||n.emailSharingTemplate||!t),window.ngBuzzFooterStylesLoaded||(window.ngBuzzFooterStylesLoaded=!0,typeof window.ng_buzzFooterCss!="undefined"?(ng_insertCSS(window.ng_buzzFooterCss),window.ng_buzzFooterCss=null):(s=document.createElement("link"),s.rel="stylesheet",s.type="text/css",s.href=y+"NGBuzz/load.ashx/buzzFooter.css",document.getElementsByTagName("HEAD")[0].appendChild(s)));var k=function(n){return document.getElementById(n)?TrimPath.parseDOMTemplate(templateOrId):TrimPath.parseTemplate(n)},bt=k(n.footerTemplate||defaultBuzzFooter),st=k(n.getThisTemplate||defaultBuzzGetThis),ot=k(n.emailTemplate||defaultEmailWidget),ct=y+"NGBuzz/",i=ct.replace(/^https/i,"http"),p=a&&a.myTrackingId||"",pt=a&&a.childTrackingId||ng_CreateGuid(),o,f;t&&t.uniqueId?(o=t.uniqueId,f="ng.buzz."+t.uniqueId):(o="buzzId_"+c+"_"+Math.floor(Math.random()*1e6),f="null");var kt=u&&u.AttentionJS?function(){return u.AttentionJS.apply(u,arguments)}:function(){return""},wt=u&&u.AttentionJS?function(){return u.AttentionClickJS.apply(u,arguments)}:function(){return""},e={_MODIFIERS:ng_JST_Modifiers,ExtraArgs:t?t._extraArgs:null,BuzzId:c,OrgCode:t&&t.orgCode?t.orgCode:"",ApiToken:vt,BuzzAppUrl:ct,NGBaseUrl:y,UniqueId:o,BuzzObj:t,BuzzObjJS:f,BuzzObjRef:f,AttentionJS:kt,AttentionClickJS:wt,FbApiKey:b,WidgetConfiguration:t?t.WidgetConfiguration:{},PreserveExtraArgs:n.preserveExtraArgs,UseGigyaSharing:w,MyTrackingId:p,ChildTrackingId:pt,GetThisBoxId:"getThis_"+o,SharingOptions:l,EmailSharingTemplate:n.emailSharingTemplate||"",ShowGetThisJS:"ng_showGetThis(this, 'getThis_"+o+"', "+f+")",HideGetThisJS:"ng_hideGetThis(this, 'getThis_"+o+"', "+f+")",ShowSignupJS:"ng_showSignupDiv(event, '"+yt+"')",ShowEmailWidgetJS:"ng_showEmailWidget(event, this, '"+c+"', "+f+", '"+p+"')",BloggerLink:lt(),TypepadLink:et(),NetvibesLink:g(),GoogleLink:nt(),PageflakesLink:ut(),EmbedScriptSrc:ft(),LiveSpacesLink:it(),LiveDotComLink:tt(),FacebookLink:v(b||null),VistaLink:rt(),GetBloggerLink:lt,GetTypepadLink:et,GetNetvibesLink:g,GetGoogleLink:nt,GetPageflakesLink:ut,GetFacebookLink:v,GetEmbedScriptSrc:ft,GetLiveSpacesLink:it,GetLiveDotComLink:tt,GetFacebookLink:v,GetVistaLink:rt};try{h=bt.process(e,{throwExceptions:!0}),t?t._renderGetThis=function(n){var i,t;e.ContainerWidth=n,i=st.process(e,{throwExceptions:!0}),w||(i+=ot.process(e,{throwExceptions:!0})),t=document.createElement("DIV"),t.style.display="none",t.innerHTML=i,document.body.appendChild(t)}:(h+=st.process(e),h+=ot.process(e))}catch(s){ng_debug("Error rendering footer",s)}return h}function ng_isValidEmail(n,t){var u=/^\s*((?:(?:(?:[a-zA-Z0-9][\.\-\+_]?)*)[a-zA-Z0-9])+)\@((?:(?:(?:[a-zA-Z0-9][\.\-_]?){0,62})[a-zA-Z0-9])+)\.([a-zA-Z0-9]{2,6})\s*$/i,r,i;if(t&&n){for(r=n.split(/[,;\s]/ig),i=0;i<r.length;i++)if(r[i].length>0&&!u.test(r[i]))return!1;return!0}return u.test(n)}function ng_showEmailWidget(n,t,i,r,u){var e,f,s,o;r&&r._emailWidgetHtml&&(e=document.createElement("DIV"),e.style.display="none",e.innerHTML=r._emailWidgetHtml,document.body.appendChild(e),r._emailWidgetHtml=null),f=function(n){var t=document.getElementById(n+"_"+i);t.value&&(t.value=""),t.innerHTML&&(t.innerHTML=""),t.style.backgroundColor="white"},f("ngEmailToTxt"),f("ngEmailFromTxt"),f("ngEmailMsgTxt"),s=document.getElementById("ngEmailFeedback_"+i),s.style.color="white",o=ng_findParentWithClass(t,"ng_getThis"),window.ng_buzzEmailTrkP=u,ng_showPopupOverElement(n,o,"ngEmailFormHtmlDiv_"+i,null,300,315)}function ng_emailWidget(n,t,i,r){var u=function(n){return document.getElementById(n+"_"+i)},s=u("ngEmailToTxt"),o=u("ngEmailFromTxt"),a=u("ngEmailTitleTxt"),y=u("ngEmailMsgTxt"),e=u("ngEmailFeedback"),h=u("ngEmailMsgTemplate"),v=u("ngEmailPreserveArgs"),c=u("fbApiKey"),l=h&&h.value?h.value:"",f;s.style.backgroundColor="white",o.style.backgroundColor="white",ng_isValidEmail(s.value,!0)?ng_isValidEmail(o.value,!1)?(f={buzzId:i,comment:y.value,from:o.value,to:s.value,subject:a.value,template:l,trkP:window.ng_buzzEmailTrkP},c&&(f.fbApiKey=c.value),r&&(f.apiToken=r.apiToken,v&&(f=ng_mixin(f,r._extraArgs))),ng.dsr.bind((r?r.directAppUrl:"http://hosted.newsgator.com/ngbuzz")+"/EmailHandler.ashx",f,null),e.innerHTML="Email sent",setTimeout(function(){ng_hideWidgetForm(t,"ngEmailFormHtmlDiv_"+i)},2e3)):(o.style.backgroundColor="#EAA",e.innerHTML="Required Field",e.style.color="red"):(s.style.backgroundColor="#EAA",e.innerHTML="Required Field",e.style.color="red")}function ng_closeEmailWidgetForm(n,t,i){ng_hideWidgetForm(t,i)}function ng_showGetThis(n,t,i){var r,f,u;!document.getElementById(t)&&i?(r=ng_getBuzzContainer(n),ng_isSafari&&(f=r.style.display,r.style.display="block"),u=r.offsetWidth-25,ng_isSafari&&(r.style.display=f),i._renderGetThis(u),setTimeout(function(){ng_showWidgetForm(null,n,t,null)},1)):ng_showWidgetForm(null,n,t,null)}function ng_hideGetThis(n,t){ng_hideWidgetForm(n,t,null)}function ng_showSignupDiv(n,t){var i=document.getElementById("ngbuzz_signUpDiv"),r,u;i||(r=document.createElement("DIV"),r.style.display="none",r.innerHTML=defaultBuzzSignup,document.body.appendChild(r),i=document.getElementById("ngbuzz_signUpDiv")),i&&(u=i.getElementsByTagName("IFRAME")[0],ng_showWidgetForm(n,ng_getEventTarget(n),i,null,600),u.src||(u.src=t+"Admin/Pages/Public/BuzzSignup.aspx?t="+ +new Date))}function ng_closeSignUpDiv(){ng_hideWidgetForm(null,"ngbuzz_signUpDiv")}var NGGetThis=function(n,t){var i=this,r,e,f,u;this.buzzId=n.buzzId||"",this.apiToken=n.apiToken||"",this.differentBuzzId="",this.differentApiToken="",n.WidgetConfiguration&&(r=n.WidgetConfiguration,r.sharing_advanced_differentwidget_apiToken&&r.sharing_advanced_differentwidget_buzzId&&(i.differentBuzzId=r.sharing_advanced_differentwidget_buzzId,i.differentApiToken=r.sharing_advanced_differentwidget_apiToken),e="sharing_other_facebook_fbDomain",fbDomain=r[e]||"www.facebook.com"),this.trkP="",this.trkM="",n.buzzTracking&&(i.trkP=n.buzzTracking.parentTrackingId||"",i.trkM=n.buzzTracking.myTrackingId||""),this.config=t||new NGGetThisConfig({extraArgs:n._extraArgs}),i.config.extraArgs||(i.config.extraArgs=n._extraArgs),f="http://nmp.newsgator.com/",this.ngBaseUrl=n.ngBaseUrl,this.movieFile="ngbuzz/flash/NewsGatorSharing2.swf",i.config.isConfig||(i.config=new NGGetThisConfig(t)),this.setNGBaseUrl=function(n){return i.ngBaseUrl=n,i},this.setExtraArgs=function(n){return i.config.extraArgs=n,i},this.getMarkup=function(){var t,r;if(i.buzzId==""||i.apiToken=="")return u("You must specify the BuzzId and ApiToken"),"";var o=i.ngBaseUrl+i.movieFile,e=function(n){return i.config[n]!=""?"&"+n.toLowerCase()+"="+encodeURIComponent(i.config[n]):""},n="buzzId="+i.buzzId+"&apiToken="+i.apiToken+"&trkP="+i.trkP+"&trkM="+i.trkM+"&mode="+i.config.sharingMode+"&sharingwidth="+i.config.width+e("transparent")+e("closeButtonVisible");return i.differentApiToken!=""&&i.differentBuzzId!=""&&(n+="&differentBuzzId="+i.differentBuzzId+"&differentApiToken="+i.differentApiToken),f!=i.ngBaseUrl.toLowerCase()&&(n+="&domain="+encodeURIComponent(i.ngBaseUrl)),(i.config.extraArgsEnabled=="true"||i.config.extraArgsEnabled==!0)&&(t=i.getExtraArgsString(i.config.extraArgs),t!=""&&(n+="&extraArgs="+encodeURIComponent(t))),r='<object id="'+i.config.id+'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" \t\t\twidth="'+i.config.width+'" height="'+i.config.height+'" codebase="http://www.adobe.com/go/getflashplayer"> \t\t\t\t<param name="movie" value="'+o+"?"+n+'" /> \t\t\t\t<param name="quality" value="high" /> \t\t\t\t<param name="wmode" value="transparent" /> \t\t\t\t<param name="name" value="'+i.config.id+'" /> \t\t\t\t<param name="AllowScriptAccess" value="always" /> \t\t\t\t<embed id="'+i.config.id+'" name="'+i.config.id+'" AllowScriptAccess="always" wmode="transparent" type="application/x-shockwave-flash" quality="high" \t\t\t\t\tsrc="'+o+"?"+n+'" style="width: '+i.config.width+"px; height: "+i.config.height+'px;"/> \t\t\t</object>'},this.getExtraArgsString=function(n){var i,t;if(typeof n=="string")return n;i=[];for(t in n)i.push(t+"="+n[t]);return i.join("&")},this.setMovieFile=function(n){return i.movieFile=n,i},u=function(){return window.ng_debug?ng_debug:typeof console!="undefined"?console.debug||console.log||console.warn||function(){}:function(){}}()},NGGetThisConfig=function(n){var t=n||{},i=function(n,t){var i=new String(n);return i=="undefined"?t:i};this.id=t.id||"ngvideo"+Math.random().toString().substring(2),this.sharingMode=t.sharingMode||"html",this.width=t.width||160,this.height=230,this.transparent=i(t.transparent,"true"),this.closeButtonVisible=i(t.closeButtonVisible,"false"),this.extraArgs=t.extraArgs||"",this.extraArgsEnabled=i(t.extraArgsEnabled,"true"),this.isConfig=function(){return!0}};

function returnAdRotator(){return""}function rotateAd(){};

// create_player.js - Automagic DOM object generator for media players
// (C) 2006-07 Tony Arcieri, ClickCaster, Inc.
// All rights reserved

// media_by_mimetype - MIME type to media player mappings
// Player types:
//  video - OS native video player (i.e. WMP on Windows, Quicktime on OS X)
//  wmp   - Windows Media Player
//  qt    - Quicktime
//  flv - Flash Video Player
var media_by_mimetype = {
  'video/mpeg'                    : 'video',
  'video/x-mpeg'                  : 'video',
  'video/msvideo'                 : 'wmp',
  'video/x-msvideo'               : 'wmp',
  'video/avi'                     : 'wmp',
  'video/x-avi'                   : 'wmp',
  'video/ms-asf'                  : 'wmp',
  'video/x-ms-asf'                : 'wmp',
  'video/ms-wmv'                  : 'wmp',
  'video/x-ms-wmv'                : 'wmp',
  'video/quicktime'               : 'qt',
  'video/x-quicktime'             : 'qt',
  'video/mov'                     : 'qt',
  'video/x-mov'                   : 'qt',
  'video/m4v'                     : 'qt',
  'video/x-m4v'                   : 'qt',
  'video/mp4'                     : 'qt',
  'video/x-mp4'                   : 'qt',
  'audio/m4a'                     : 'qt', 
  'audio/x-m4a'                   : 'qt',
  'audio/m4b'                     : 'qt', 
  'audio/x-m4b'                   : 'qt',
  //'video/3gpp'					  : 'qt', //doesn't ever seem to work, rather not show them
  'video/x-shockwave-flash'       : 'flv',
  'video/flv'                     : 'flv',
  'video/x-flv'                   : 'flv',
  'audio/mpeg'                    : 'mp3', 
  'audio/x-mpeg'                  : 'mp3', 
  'audio/mp3'                     : 'mp3', 
  'audio/x-mp3'                   : 'mp3', 
  'audio/mpeg3'                   : 'mp3', 
  'audio/x-mpeg3'                 : 'mp3', 
  'audio/mpg'                     : 'mp3', 
  'audio/x-mpg'                   : 'mp3', 
  'x-audio/mp3'                   : 'mp3'
};

// mimetype_by_extension - File extension to MIME type mappings
var mimetype_by_extension = {
  'mpg'       : 'video/mpeg',
  'mpeg'      : 'video/mpeg',
  'mpe'       : 'video/mpeg',
  'mp2'       : 'video/mpeg',
  'mpv2'      : 'video/mpeg',
  'avi'       : 'video/x-msvideo',
  'asf'       : 'video/x-ms-asf',
  'wmv'       : 'video/x-ms-wmv',
  'mov'       : 'video/quicktime',
  'm4v'       : 'video/x-m4v',
  'mp4'       : 'video/x-mp4',
  'flv'       : 'video/x-flv',
  'mp3'       : 'audio/mpeg',
  'm4a'       : 'audio/x-m4a',
  'm4b'       : 'audio/x-m4b'
  //'3gp'		  : 'video/3gpp'  //doesn't ever seem to work, rather not show them
};

// Return the file extension at the end of the path contained in a URL
function extract_file_extension_from_url(url)
{
  // Strip possible arguments from the URL
  path = url.split('?').shift();
  
  // Look for the last '.' delimited entity
  return path.split('.').pop();
}

// identify_player - Return the appropriate player for a url/mimetype pair
function identify_player(url, mimetype)
{
  // Check for YouTube URLs
	//#pragma NoCompStart  -- Tell the runtime compressor to not screw up our regexes
	if(/^\w{4,5}:\/\/youtube.com\//i.test(url)) {
	//#pragma NoCompEnd  -- Tell the runtime compressor to not screw up our regexes
		return 'youtube';
	}

  // Experience shows file extensions are a more reliable identifier than
  // MIME types, so attempt identification by extension first
  extension = extract_file_extension_from_url(url);
  if(extension) {
    extension_type = mimetype_by_extension[extension];
    if(extension_type)
      return media_by_mimetype[extension_type];
  }
    
  // If the extension cannot be identified, fall back on the MIME type
  return media_by_mimetype[mimetype];
}

function create_player_embed(params, local_params)
{
	local_params = local_params || {};

	var id = local_params.id || local_params.name;
	var name = local_params.name || local_params.id;

	var markup = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://www.adobe.com/go/getflashplayer" ';
	if(params.width){
		markup += 'width="' + params.width + '" ';
	}
	if(params.height){
		markup += 'height="' + params.height + '" ';
	}
	if(id){
		markup += 'id="' + id + '" ';
	}
	
	markup += ">\n";

	for(name in params){
		markup += '<param name="' + name + '" value="' + params[name] + '" />\n';
	}
	for(name in local_params){
		markup += '<param name="' + name + '" value="' + local_params[name] + '" />\n';
	}
  
	markup += '<embed ';

	for(name in params){
		markup += name + '="' + params[name] + '" ';
	}

	for(name in local_params){
		markup += name + '="' + local_params[name] + '" ';
	}

	return markup + '></embed></object>';
}

function create_generic_player(url, mimetype, width, height, params)
{
  return create_player_embed({
    src		: url,
    type	: mimetype, 
    width	: width || 320, 
    height	: height || 240,
    wmode: "transparent"
    }, params);
}

function create_wmplayer(url, width, height, autostart, params)
{
  // Generate object tag for IE
  if(window.ActiveXObject) {
		return '<object classid="clsid:6BF52A52-394A-11d3-B153-00C04F79FAA6" ' +
		'width="' + (width || 320) + '" height="' + (height || 285) + '">\n' +
		'<param name="URL" value="' + url + '">\n' +
		'<param name="autoStart" value="' + autostart + '">\n' +
		'<param name="uiMode" value="full">\n' +
		'</object>';
	}
  
  return create_player_embed({
    'src'         : url,
    'type'        : 'application/x-mplayer2',
    'pluginspage' : 'http://www.microsoft.com/Windows/MediaPlayer',
    'width'       : width || 320,
    'height'      : height || 285,
    'uiMode'      : 'full',
    'autoStart'   : (autostart == true || autostart == 'true') ? 1 : 0},
    params
  );
}

function create_quicktime_player(url, width, height, autostart, params) 
{
   // Generate object tag for IE
   if (window.ActiveXObject) {
       return '<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" codebase="http://www.apple.com/qtactivex/qtplugin.cab"' +
		'width="' + (width || 320) + '" height="' + (height || 285) + '">\n' +
		'<param name="qtsrc" value="' + url + '">\n' +
		'<param name="autoplay" value="' + autostart + '">\n' +
		'<param name="controller" value="true">\n' +
		'</object>';
  }
   
  return create_player_embed({
    'width'     : width || 320,
    'height'    : height || 256,
    'src'       : url,
    'scale'     : 'aspect',
    'controller': true,
    'bgcolor'   : 'white',
    'cache'     : 'true',
    'autoplay'  : autostart,
    'type'      : 'video/quicktime'},
    params
  );
}

function create_flv_player(url, width, height, autostart, params)
{
	width = width || 400;
	height = height || 300;
	
	//build config
	var configObj = {
		url: url,
		width: width,
		height: height,
		autostart: autostart
	};
	
	//note: lowercase properties for comparison
	var excludedMappings = {
		isconfig: "",
		url: "",
		width: "",
		height: "",
		autostart: ""
	};

	var videoConfig = new NGFlashVideoConfig();
	var sentFlashVars = "";
	var hasId = false;
	var altName = "";
	for (var key in params)
	{
		var lowerKey = key.toLowerCase();
		for (var item in videoConfig)
		{
			if (lowerKey == item.toLowerCase())
			{
				if (!(key in excludedMappings && params[key]))
				{
					configObj[item] = params[key];
				}
			}
		}
		
		//get the flash vars
		if (lowerKey == "flashvars")
		{
			sentFlashVars = params[key];
		}
		
		if (lowerKey == "id")
		{
			hasId = true;
		}
		
		if (lowerKey == "name")
		{
			altName = params[key];
		}
	}

	//split flash vars and override defaults
	var vars = sentFlashVars.split("&");
	var varsToSend = [];

	for (var x = 0; x < vars.length; x++)
	{
		var matchedItem = false;
		temp = vars[x].split("=");
		if (temp.length == 2)
		{
			for (var item in videoConfig)
			{
				if (temp[0].toLowerCase() == item.toLowerCase())
				{
					configObj[item] = temp[1];
					matchedItem = true;
				}
			}
			if (matchedItem == false)
			{
				varsToSend.push(vars[x]);
			}
		}
	}
	configObj.additionalFlashVars = varsToSend.join("&");
	
	//attempt to use their name if they set one and don't have an id
	if (hasId == false && altName != "")
	{
		configObj.id = altName;
	}
	
	//get the flash video object
	var flv = new NGFlashVideo(null, new NGFlashVideoConfig(configObj));
	
	if (params.buzzAppUrl)
	{
		flv.setBuzzAppUrl(params.buzzAppUrl);
	}
	else if (window.location.host == "localhost")
	{
		flv.setBuzzAppUrl("http://localhost/ngbuzz/");
	}

	return flv.getMarkup();
}

function create_mp3_player(url, width, height, autostart, params)
{
  width = width || 250;
  height = height || 40;
  
  var flashvars = 'file=' + encodeURIComponent(url) + '&autostart=' + autostart + '&height=' + height + '&width=' + width;

  return create_player_embed({
    'style'     : 'width:'+width+'px; height:'+height+'px;',
	'width'		: width,
    'height'	: height,
    'src'       : 'http://nmp.newsgator.com/ngbuzz/scripts/ngaudio.swf?' + flashvars,
    'quality'   : 'high',
    'type'      : 'application/x-shockwave-flash',
	'allowScriptAccess': 'always',
    'wmode'     : 'transparent'},
    params
  ); 
}

function create_youtube_player(url, width, height, autostart, params) {
	if(url.substr(url.length - 4, 4) != '.swf') {
		url += '.swf';
	}

  return create_player_embed({
    width			: width || 403,      
    height			: height || 329,     
	src				: url, //use the native youtube player
    quality			: 'high',     
    type			: 'application/x-shockwave-flash',
    wmode			: "transparent",
    allowFullScreen	: 'true'},
    params
  );
}

function create_player_markup(url, mimetype, width, height, autostart, force, params)
{
  // Set defaults for unspecified arguments
  autostart   = (autostart == null) ? true : autostart;
  force       = (force == null)     ? true : force;

  // Attempt to identify the player given the url and mimetype
  var player = identify_player(url, mimetype);
  
  // Create the appropriate player for the given media type
  switch(player) {
    case 'wmp':
      return create_wmplayer(url, width, height, autostart, params);
    case 'qt':
      return create_quicktime_player(url, width, height, autostart, params);
    case 'mp3':
      return create_mp3_player(url, width, height, autostart, params);
    case 'youtube':
      return create_youtube_player(url, width, height, autostart, params);
    case 'flv':
      return create_flv_player(url, width, height, autostart, params);
  }
  
  if(force)
    return create_generic_player(url, mimetype, width, height, params);
    
  return null;
}

// create_player - Return a DOM element representing the player
// Arguments:
//   id         - Element ID into which the player is inserted
//   url        - Resource to generate a player for (required)
//   mimetype   - MIME type of the player if known (otherwise null)
//   width      - Width of the player
//   height     - Height of the player
//   autostart  - Automatically begin playing video
//   force      - Return a generic embed tag for the given MIME type if nothing better can be found
//                (if this paramater is null or omitted, then the function will return null 
//                for unknown types)
//   params     - An object containing additional HTML parameters for the embed tag
function create_player(id, url, mimetype, width, height, autostart, force, params) {
	var player_code = create_player_markup(url, mimetype, width, height, autostart, force, params);
  
	if(!player_code) {
		return false;
	}
  
	document.getElementById(id).innerHTML = player_code;
	return true;
}

/**
 * Attempts to determine if the given post object has video attached to it
 */
function HasVideo(post){
	//the !! converts to a boolean type
	return !!(GetVideoUrl(post));
}
function HasAudio(post){
	//the !! converts to a boolean type
	return !!(GetAudioUrl(post));
}


/**
 * Searches through the given post for videos and returns the first one found
 */
function GetVideoUrl(post){
	var videos = ng_GetVideos(post);
	
	if (videos.length == 0)
	{
		return null;
	}	

	var videoUrl = null;
	for (var x = 0; x < videos.length; x++)
	{
		if (videos[x].isDefault && videos[x].isDefault == true)
		{
			return videos[x].url;
		}
	}
	return videoUrl || videos[0].url;
}

/**
 * Searches through the given post for videos and returns all videos found.
 * The return value is an array of objects. Each object has properties "url" and "mimeType".
 */
function ng_GetVideos(post){
	function isVideo(url, mimeType, medium){
		return(
//#pragma NoCompStart  -- Tell the runtime compressor to not screw up our regexes
			(mimeType && /video\//i.test(mimeType)) ||
			(url && /(mpe?g)|(flv)|(swf)|(mov)|(mp4)|(m4v)|(wmv)|(asf)|(avi)|(mpe)|(mpv2)/i.test(url)) ||
			(medium == "video")
//#pragma NoCompEnd
		);
	};

	var videos = [];

	//note we could maybe do something with MediaRSS.Player stuff as well, but I'm not sure what.
	if(post.MediaRSS && post.MediaRSS.Contents){
		for(var i = 0; i < post.MediaRSS.Contents.length; i++){
			var c = post.MediaRSS.Contents[i];
			if(isVideo(c.Url, c.Type, c.Medium)){
				videos.push({url:c.Url, mimeType:c.Type});
			}
		}
	}
	
	if(post.EnclosureUrl && isVideo(post.EnclosureUrl, post.EnclosureType)){
		videos.push({url:post.EnclosureUrl, mimeType:post.EnclosureType});
	}
	
	return videos;
}

function GetAudioUrl(post){
	var audioTracks = ng_GetAudioTracks(post);
	
	var audioUrl = "";

	if (audioTracks.length == 0)
	{
		return null;
	}	
	audioUrl = audioTracks[0].url;
	
	for (var x = 0; x < audioTracks.length; x++)
	{
		if (audioTracks[x].isDefault && audioTracks[x].isDefault == true)
		{
			return audioTracks[x].url;
		}
	}
	return audioUrl;
}

function ng_GetAudioTracks(post)
{
	var isAudio = function(url, mimeType)
	{
		return(
		//#pragma NoCompStart  -- Tell the runtime compressor to not screw up our regexes
			(mimeType && (/audio\//i.test(mimeType))) ||
			(url && (/(mp3)|(m4a)|(m4b)/i.test(url)))
		//#pragma NoCompEnd
		);
	};
	
	var audioTracks = [];
	
	//note we could maybe do something with MediaRSS.Player stuff as well, but I'm not sure what.
	if(post.MediaRSS && post.MediaRSS.Contents){
		for(var i = 0; i < post.MediaRSS.Contents.length; i++){
			var c = post.MediaRSS.Contents[i];
			if(isAudio(c.Url, c.Type)){
				audioTracks.push({url:c.Url, mimeType:c.Type});
			}
		}
	}
	
	if(post.EnclosureUrl && isAudio(post.EnclosureUrl, post.EnclosureType)){
		audioTracks.push({url:post.EnclosureUrl, mimeType:post.EnclosureType});
	}
	
	return audioTracks;
}






/* ============================================================================================
 *  Legacy Widget player stuff
 * ============================================================================================ */

/**
 * Renders out the container elements for the popup video player
 */
function returnVideoPlayer(buzzObj, ctx){
	//no need to have one per buzz box on the page
	if(document.getElementById("ngbuzz_videodiv")){
		return "";
	}

	var baseUrl = (buzzObj && buzzObj.ngBaseUrl ? buzzObj.ngBaseUrl : "http://nmp.newsgator.com");
	var html = '<div id="ngbuzz_videodiv" style="width:100%; height: 100%; display:none; margin: 2px; border:1px solid #333;">';
	html +='<table width="100%" height="100%" style="background-color:white;">';
		html += '<tr height="17px">';
			html += '<td>&nbsp;</td>';
			html += '<td align="right">';
				html += '<a href="javascript:void(0)" onclick="closePlayer()"><img src="' + baseUrl + '/NGBuzz/Images/close.gif" style="padding:2px" align="right" border="0" alt="Close" /></a>';		
			html += '</td>';
		html += '</tr>';
		html += '<tr valign="top">';
			html += '<td colspan="2" align="center">';
				html += '<div id="ngbuzz_videoHolder" style="width: 100%; height: 100%"></div>';
			html += '</td>';
		html += '</tr>';
	html +='</table>';
	html += '</div>';
	return html;
}

var ng_videoPlayerStartElem;
function closePlayer(callback) {
	ng_hideWidgetPopup(ng_videoPlayerStartElem, "ngbuzz_videodiv", callback);
	ng_videoPlayerStartElem = null;
	
	var player = document.getElementById('ngbuzz_videoHolder');
	player.innerHTML = "";
}

/**
 * Show a popup video player over the widget
 */
function populateVideoHolder(url, notUsed, postUrl, evt, buzzObj, postId) {
	if(ng_videoPlayerStartElem){
		closePlayer(function(){
			populateVideoHolder(url, notUsed, postUrl, evt, buzzObj, postId);
		});
		return;
	}
	
	var videodiv = document.getElementById("ngbuzz_videodiv");
	if(!videodiv){
		var div = document.createElement("DIV");
		div.innerHTML = returnVideoPlayer(buzzObj);
		document.body.appendChild(div);
		videodiv = document.getElementById("ngbuzz_videodiv");
	}
	
	videodiv.style.height = "300px";
	videodiv.style.width = "340px";
	
	
	ng_videoPlayerStartElem = ng_getEventTarget(evt) || document.getElementById(buzzObj._targetId);
	var maxSize = ng_showWidgetPopup(evt, ng_videoPlayerStartElem, "ngbuzz_videodiv");
	
	//Calculate height and width
	var maxOuterWidth = (maxSize[0] ? maxSize[0] : 340);
	var maxOuterHeight = (maxSize[1] ? maxSize[1] : 300);
	
	//Calculate how much we have to scale
	var sf = Math.min(maxOuterWidth / 340, maxOuterHeight / 300);
	
	var player = document.getElementById('ngbuzz_videoHolder');
	if(sf > 1.0){
		player.innerHTML = create_player_markup(url, "", 320, 240, false, true, {wmode:"transparent"});
	} else {
		var xPadding = 10;
		var yPadding = 10;
		var outerWidth = parseInt(340 * sf);
		var outerHeight = parseInt(300 * sf);
		
		var videoSF = Math.min((outerWidth - xPadding) / 320, (outerHeight - yPadding) / 240);
		var videoWidth = 320 * videoSF;
		var videoHeight = 240 * videoSF;
		
		videodiv.style.width = outerWidth + "px";
		videodiv.style.height = outerHeight + "px";
		player.innerHTML = create_player_markup(url, "", videoWidth, videoHeight, false, true, {wmode:"transparent"});
		
	}

	player.style.display = "block";
	
	if(buzzObj && buzzObj.analytics){
		buzzObj.analytics.logPostEvent('viewVideo', postId, 0, postUrl, evt, null);
	}
}

/**
 * Show a video within the specified container node.
 * Accepts both video URLs and raw posts, will attempt to autosize if necessary or width/height isn't defined.
 * @param containerNode {string or Element} The node to insert the video player into
 * @param videoArgs {Object} arguments for the video. Can include...
 *		url {String} The URL of the video. Optional but if this is not set then "post" must be
 *		mimeType {String} The MIME type of the video. Optional.
 *		post {Object} The Post object to be searched for videos. Optional, but if this is not set then "url" must be
 *		autofit {Boolean} indicates whether to autosize the video to the container. Defaults to true.
 *		aspectWidth {Number} The width part of the aspect ration for the video, default is 4
 *		aspectHeight {Number} The height part of the aspect ration for the video, default is 3
 *		minWidth {Number} The smallest height allowed for the video, default is 120
 *		minHeight {Number} The smallest height allowed for the video, default is 90
 *		autoplay {Boolean} Indicates whether to automatically start the video, default is true
 * @param playerArgs {Objects} arguments to be passed to the create_player_html() function
 */
function ng_ShowVideo(containerNode, videoArgs, playerArgs){
	if(!videoArgs.url && videoArgs.post){
		var videos = ng_GetVideos(videoArgs.post);
		if(videos && videos.length > 0){
			videoArgs.url = videos[0].url;
			videoArgs.mimeType = videos[0].mimeType;
			
			videoUrl = videos[0].url;
	
			for (var x = 0; x < videos.length; x++)
			{
				if (videos[x].isDefault && videos[x].isDefault == true)
				{
					videoArgs.url = videos[x].url;
					videoArgs.mimeType = videos[x].mimeType;
					break;
				}
			}
		}
	}

	if(!containerNode.nodeType){
		containerNode = document.getElementById(containerNode);
	}

	var innerContainer = document.createElement("DIV");
	innerContainer.id = "ng_videoContainer_" + (new Date()).getTime();
	innerContainer.style.border = "0px";
	innerContainer.style.padding = "0px";
	innerContainer.style.margin = "0px";
	containerNode.innerHTML = "";
	containerNode.appendChild(innerContainer);
	
	var height = videoArgs.height;
	var width = videoArgs.width;
	if(videoArgs.autofit != false){
		var aspectWidth = videoArgs.aspectHeight || 4;
		var aspectHeight = videoArgs.aspectHeight || 3;
		var minWidth = (videoArgs.minWidth || 120) / aspectWidth;
		var minHeight = (videoArgs.minHeight || 90) / aspectHeight;
		var maxWidth = (videoArgs.maxWidth || 480) / aspectWidth;
		var maxHeight = (videoArgs.maxHeight || 360) / aspectHeight;

		//offsetWidth/Height work fairly well across browsers IFF the element has no border/padding/margins. Hence why we created our own element to check sizing on.
		//clientWidth/Height does not work well on some browsers (I think IE6, but I forget)
		var scaledWidth = (innerContainer.offsetWidth || 0) / aspectWidth;
		var scaledHeight = ((innerContainer.offsetHeight || 0) / aspectHeight) || scaledWidth;
		
		var smallestDimension = Math.max(Math.min(Math.min(scaledWidth, scaledHeight), maxWidth, maxHeight), minWidth, minHeight);
		height = smallestDimension * aspectHeight;
		width = smallestDimension * aspectWidth;
	}	
	
	var html = create_player_markup(videoArgs.url, videoArgs.mimeType, width, height, !!videoArgs.autoplay, true, playerArgs);
	innerContainer.innerHTML = html;
}

/* ============================================================================================
 *  End Legacy Widget player stuff
 * ============================================================================================ */


var NGFlashVideo = function(post, videoConfig) {
    var self = this;
    this.config = videoConfig || new NGFlashVideoConfig();
    this.buzzAppUrl = "http://nmp.newsgator.com/ngbuzz/";

    //see if it's an actual config object
    if (!(self.config.isConfig)) {
        //pass the object along and create a full NGFlashVideoConfig
        self.config = new NGFlashVideoConfig(videoConfig);
    }

    this.setPost = function(postValue) {
        var video = self.getFlvVideo(postValue);

        if (video != null) {
            self.config.url = video.url;
            if (video.Type) {
                self.config.mimeType = video.Type;
            }
        }
        else {
            self.config.url = "";

            debug("no flv videos found");
        }
        return self;
    };

    this.setVideoUrl = function(urlValue) {
        self.config.url = urlValue;
        return self;
    };

    this.setBuzzAppUrl = function(value) {
        self.buzzAppUrl = value;
        return self;
    };

    this.getMarkup = function() {
        if (self.config.url == "") {
            debug("no url specified or no flv video found on post");
            return "";
        }
        var playerUrl = self.buzzAppUrl + "scripts/ngvideo.swf";

        var getFlashVar = function(key) {
            if (self.config[key] != "") {
                return "&" + key.toLowerCase() + "=" + encodeURIComponent(self.config[key]);
            }
            return "";
        };

        var flashVars = ""
			+ "file=" + encodeURIComponent(self.config.url)
			+ "&height=" + self.config.height
			+ "&width=" + self.config.width
			+ "&autostart=" + self.config.autostart
			+ getFlashVar("thumbnail")
			+ getFlashVar("enableNGThumbnail")
			+ getFlashVar("disableControls")
			+ getFlashVar("acudeoCampaignId")
			+ getFlashVar("acudeoProgId")
			+ getFlashVar("acudeoCustId")
			+ getFlashVar("acudeoAdTarget")
			+ getFlashVar("loadCallback")
			+ getFlashVar("acudeoNoAdFound")
			+ getFlashVar("acudeoDomainNotAllowed");

        if (self.config.additionalFlashVars != "") {
            var additional = "";
            if (self.config.additionalFlashVars.indexOf("&") != 0) {
                additional += "&";
            }
            additional += self.config.additionalFlashVars;

            flashVars += additional;
        }

        var out = '<object id="' + self.config.id + '" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" \
			width="' + self.config.width + '" height="' + self.config.height + '" codebase="http://www.adobe.com/go/getflashplayer"> \
				<param name="movie" value="' + playerUrl + '?' + flashVars + '" /> \
				<param name="quality" value="high" /> \
				<param name="wmode" value="' + self.config.wmode + '" /> \
				<param name="name" value="' + self.config.id + '" /> \
				<param name="allowScriptAccess" value="always" /> \
				<param name="allowFullScreen" value="true" /> \
				<embed id="' + self.config.id + '" name="' + self.config.id + '" AllowScriptAccess="always" allowfullscreen="true" wmode="' + self.config.wmode + '" type="application/x-shockwave-flash" quality="high" \
					src="' + playerUrl + '?' + flashVars + '" style="width: ' + self.config.width + 'px; height: ' + self.config.height + 'px;"/> \
			</object>';

        return out;
    };

    this.getFlvVideo = function(postValue) {
        var videos = self.getFlvVideos(postValue);

        if (videos.length == 0) {
            return null;
        }

        var video = null;
        for (var x = 0; x < videos.length; x++) {
            if (videos[x].isDefault && videos[x].isDefault == true) {
                return videos[x];
            }
        }
        return video || videos[0];
    };

    this.getFlvVideos = function(postValue){
		function isFlvVideo(url, mimeType, medium){
		return(
		//#pragma NoCompStart  -- Tell the runtime compressor to not screw up our regexes
					(mimeType && /(video\/x-shockwave-flash)|(video\/flv)|(video\/x-flv)/i.test(mimeType)) ||
					(url && /(flv)|(swf)/i.test(url))
		//#pragma NoCompEnd
				);
			};
		
		var videos = [];
	
		//note we could maybe do something with MediaRSS.Player stuff as well, but I'm not sure what.
		if(postValue.MediaRSS && postValue.MediaRSS.Contents){
			for(var i = 0; i < postValue.MediaRSS.Contents.length; i++){
				var c = postValue.MediaRSS.Contents[i];
				if(isFlvVideo(c.Url, c.Type, c.Medium)){
					videos.push({url:c.Url, mimeType:c.Type});
				}
			}
		}
			
		if(postValue.EnclosureUrl && isFlvVideo(postValue.EnclosureUrl, postValue.EnclosureType)){
			videos.push({url:postValue.EnclosureUrl, mimeType:postValue.EnclosureType});
		}
		
		return videos;
	};

    this.getPlayerHandle = function(optionalId) {
        var id = optionalId || self.config.id;

        if (navigator.appName.indexOf("Microsoft") != -1) {
            return window[id];
        }
        else {
            return document[id];
        }
    };

    this.play = function() {
        executePlayerFunction("playVideo");
    };

    this.pause = function() {
        executePlayerFunction("pauseVideo");
    };

    this.stop = function() {
        executePlayerFunction("stopVideo");
    };

    var executePlayerFunction = function(funcName) {
        if (self.getPlayerHandle()[funcName]) {
            self.getPlayerHandle()[funcName]();
        }
        else {
            debug("unable to find media player within the page.  element name/id: " + self.config.id);
        }
    };

    var debug = (function() {
        if (window.ng_debug) {
            return ng_debug;
        } else if (typeof console != "undefined") {
            return console.debug || console.log || console.warn || function() { };
        }
        return function() { };
    })();


    //set this value at the end
    if (post != null) {
        self.setPost(post);
    }
};

var NGFlashVideoConfig = function(videoConfigObj) {
    var config = videoConfigObj || {};

    var getTrueFalseValue = function(obj, defaultValue) {
        var s = new String(obj);
        if (s == "undefined") {
            return defaultValue;
        }
        else {
            return s;
        }
    };

    this.mimeType = config.mimeType || "video/flv";
    this.url = config.url || "";

    this.id = config.id || "ngvideo" + Math.random().toString().substring(2);

    this.height = config.height || 300;
    this.width = config.width || 400;
    this.wmode = config.wmode || "transparent";

    this.autostart = getTrueFalseValue(config.autostart, "false");
    this.enableNGThumbnail = getTrueFalseValue(config.enableNGThumbnail, "true");
    this.disableControls = getTrueFalseValue(config.disableControls, "false");

    this.thumbnail = config.thumbnail || "";

	this.acudeoCampaignId = config.acudeoCampaignId || ""; // only use this if you need a dynamically loaded progId!
    this.acudeoProgId = config.acudeoProgId || "";
    this.acudeoCustId = config.acudeoCustId || "";
    this.acudeoAdTarget = config.acudeoAdTarget || "";
	this.acudeoNoAdFound	= config.acudeoNoAdFound || "";
	this.acudeoDomainNotAllowed = config.acudeoDomainNotAllowed || "";

    this.loadCallback = config.loadCallback || "";

    this.additionalFlashVars = config.additionalFlashVars || "";

    //method for easy check to see if it's a config object
    this.isConfig = function() { return true; };
};


var NGAudio = function(post, audioConfig){
	var self = this;
	this.config = audioConfig || new NGAudioConfig();
	this.buzzAppUrl = "http://nmp.newsgator.com/ngbuzz/";
	
	//see if it's an actual config object
	if (!(self.config.isConfig)){
		//pass the object along and create a full NGAudioConfig
		self.config = new NGAudioConfig(audioConfig);
	}

	this.setPost = function(postValue){
		var audio = self.getAudioTrack(postValue);
		
		if (audio != null){
			self.config.url = audio.url;
			if (audio.Type){
				self.config.mimeType = audio.Type;
			}
		}
		else{
			self.config.url = "";
			debug("no audio files found");
		}
		return self;
	};
	
	this.setAudioUrl = function(urlValue){
		self.config.url = urlValue;
		return self;
	};
	
	this.setBuzzAppUrl = function(value){
		self.buzzAppUrl = value;
		return self;
	};
	
	this.getMarkup = function(){
		if (self.config.url == ""){
			debug("no url specified or no audio found on post");
			return "";
		}
		var playerUrl = self.buzzAppUrl + "scripts/ngaudio.swf";
		
		var getFlashVar = function(key){
			if (self.config[key] != ""){
				return "&" + key.toLowerCase() + "=" + encodeURIComponent(self.config[key]);
			}
			return "";
		}
		
		var flashVars = ""
			+ "file=" + encodeURIComponent(self.config.url)
			+ "&height=" + self.config.height
			+ "&width=" + self.config.width
			+ "&autostart=" + self.config.autostart
			+ getFlashVar("loadCallback")
			+ getFlashVar("color")
			+ getFlashVar("transparent");
		
		if (self.config.additionalFlashVars != ""){
			var additional = "";
			if (self.config.additionalFlashVars.indexOf("&") != 0){
				additional += "&";
			}
			additional += self.config.additionalFlashVars;
			
			flashVars += additional;
		}		
		
		var out = '<object id="'+ self.config.id + '" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" \
			width="' + self.config.width + '" height="' + self.config.height + '" codebase="http://www.adobe.com/go/getflashplayer"> \
				<param name="movie" value="' + playerUrl + '?' + flashVars + '" /> \
				<param name="quality" value="high" /> \
				<param name="wmode" value="' + self.config.wmode + '" /> \
				<param name="name" value="'+ self.config.id + '" /> \
				<param name="AllowScriptAccess" value="always" /> \
				<embed id="'+ self.config.id + '" name="'+ self.config.id + '" AllowScriptAccess="always" wmode="' + self.config.wmode + '" type="application/x-shockwave-flash" quality="high" \
					src="' + playerUrl + '?' + flashVars + '" style="width: ' + self.config.width + 'px; height: ' + self.config.height + 'px;"/> \
			</object>';
		
		return out;
	};
	
	this.getAudioTrack = function(postValue){
		var tracks = ng_GetAudioTracks(postValue);
		
		if (tracks.length == 0){
			return null;
		}
		
		var track = null;
		for (var x = 0; x < tracks.length; x++)
		{
			if (tracks[x].isDefault && tracks[x].isDefault == true){
				return tracks[x];
			}
		}
		return track || tracks[0];
	};

	this.getPlayerHandle = function(optionalId){
		var id = optionalId || self.config.id;
		
		if (navigator.appName.indexOf("Microsoft") != -1){
			return window[id];
		}
		else{
			return document[id];
		}
	};
	
	this.play = function(){
		executePlayerFunction("playAudio");
	};
	
	this.pause = function(){
		executePlayerFunction("pauseAudio");
	};
	
	this.stop = function(){
		executePlayerFunction("stopAudio");
	};
	
	var executePlayerFunction = function(funcName){
		if (self.getPlayerHandle()[funcName]){
			self.getPlayerHandle()[funcName]();
		}
		else{
			debug("unable to find audio player within the page.  element name/id: " + self.config.id);
		}
	};
	
	var debug = function(){
		if(window.ng_debug){
			return ng_debug;
		} else if(typeof console != "undefined"){
			return console.debug || console.log || console.warn || function(){};
		}
		return function(){};
	}();
	
	
	//set this value at the end
	if (post != null){
		self.setPost(post);
	}
};

var NGAudioConfig = function(audioConfigObj){
	
	var config = audioConfigObj || {};
	
	var getTrueFalseValue = function(obj, defaultValue){
		var s = new String(obj);
		if (s == "undefined")
		{
			return defaultValue;
		}
		else{
			return s;
		}
	}
	
	this.mimeType 			= config.mimeType || "audio/mpeg";
	this.url 				= config.url || "";
	
	this.id					= config.id || "ngaudio" + Math.random().toString().substring(2);
	
	this.height				= config.height || 40;
	this.width				= config.width || 250;
	this.wmode				= config.wmode || "transparent";

	this.autostart			= getTrueFalseValue(config.autostart, "false");
	
	this.color				= config.color || ""; //current options are empty (blue), or 'gray'
	this.transparent		= getTrueFalseValue(config.transparent, "false");
	
	this.loadCallback		= config.loadCallback || "";
	
	this.additionalFlashVars = config.additionalFlashVars || "";
	
	//method for easy check to see if it's a config object
	this.isConfig = function() { return true; }
};


//handles sizing our Google Ads for widgets
//I'm anticipating that we may want to change this frequently, which is why this is in it's own file
//But the functions get mixed in to the ng.buzz.Buzzlet prototype, so they're properly called on a BuzzObj

(function() { //closure function
    //sizes to use for max width/height if autosizing/size detection can't figure something out
    var defaultMaxHeight = 150;
    var defaultMaxWidth = 234;

    //Array of ad sizes, in order of preference
    //This should probably match up to the ones in WidgetAd.aspx, but I don't think it's actually necessary
    var googleAdSizes = [
			{ width: 728, height: 90 }, //leaderboard image ad -- yeah, sure, we'll sell some of these
			{width: 336, height: 280 }, //Large rectangle image or text
			{width: 300, height: 250 }, //Medium Rectangle image or text 
			{width: 468, height: 60 }, //full banner text
			{width: 234, height: 60 }, //half banner text
			{width: 250, height: 250 }, //Square image or text
			{width: 200, height: 200 }, //Small square image or text
			{width: 180, height: 150 }, //small rectangle text
			{width: 125, height: 125 }, //button text
			{width: 200, height: 90 }, //200x90 link unit
			{width: 180, height: 90 }, //180x90 link unit
			{width: 160, height: 90 }, //160x90 link unit
			{width: 120, height: 90 }, //120x90 link unit
			{width: 728, height: 15 }, //728x15 link unit
			{width: 468, height: 15} //468x15 link unit
	];

    var defaultAd = { width: 234, height: 60 };

    function measureDimensions(elem) {
        var w, h;
        if (typeof jQuery != "undefined" && jQuery.fn.width) {
            var c = jQuery(elem);
            w = c.width();
            h = c.height();
        } else {
            w = elem.clientWidth;
            h = elem.clientHeight;
        }
        return { width: w, height: h };
    }

    function getAdFrameSrc(buzzObj, width, height) {
        return buzzObj.buzzAppUrl + "Ads/WidgetAd.aspx?buzzId=" + buzzObj.buzzId + "&apiToken=" + buzzObj.apiToken + "&height=" + height + "&width=" + width;
    }

    var mixinFunctions = {
        //The actual finder function
        //A "false-y" (ie, null or zero or false) value indicates any value is acceptable
        getAdSize: function(width, height) {
            width = parseInt(width, 10) || false;
            height = parseInt(height, 10) || false;

            if (!width && !height) {
                //if they gave us no constraints, we'll return the default size
                return defaultAd;
            }

            for (var i = 0; i < googleAdSizes.length; i++) {
                var ad = googleAdSizes[i];
                if ((!width || width >= ad.width) && (!height || height >= ad.height)) {
                    return ad;
                }
            }

            //we couldn't find an appropriately sized ad, so don't show one
            //Prevents blowing out widgets in spaces that are too small for an ad
            return null;
        },

        insertAd: function(containerElem, maxWidth, maxHeight, force, replaceContainerElem) {
            ///<summary>
            /// Given a container DOM node, attempt to find it's size and then place an ad inside it
            ///</summary>

            //we only deal with nodes here, so check
            if (!containerElem || !containerElem.nodeType) {
                return null;
            }

            var dimensions = measureDimensions(containerElem);

            var w, h;
            if (typeof jQuery != "undefined" && jQuery.fn.width) {
                w = maxWidth ? Math.min(maxWidth, (dimensions.width || 9999)) : (dimensions.width || defaultMaxWidth);
                h = maxHeight ? Math.min(maxHeight, (dimensions.height || 9999)) : (dimensions.height || defaultMaxHeight);
            } else {
                w = maxWidth ? Math.min(maxWidth, (dimensions.width || 9999)) : (dimensions.width || defaultMaxWidth);
                h = maxHeight ? Math.min(maxHeight, (dimensions.height || 9999)) : (dimensions.height || defaultMaxHeight);
            }

            //alert("ad constraints: " + w + "  " + h);

            var adSize = this.getAdSize(w, h);

            if (!adSize && force) {
                //alert("couldn't find ad inside constraints, trying again with height: " + defaultMaxHeight);

                adSize = this.getAdSize(w, defaultMaxHeight);

                if (!adSize) {
                    //alert("still couldn't find ad, using default size ad");
                    adSize = defaultAd;
                }
            }

            if (!adSize) {
                //alert("Couldn't determine appropriate ad size to fit within " + w + "x" + h);
                return null;
            }
            //alert("Final ad size: " + adSize.width + " x " + adSize.height);

            var adWidth = adSize.width;
            var adHeight = adSize.height;

            var iframe = document.createElement("IFRAME");
            iframe.scrolling = "no";
            iframe.frameborder = 0;
            iframe.style.width = iframe.width = adSize.width;
            iframe.style.height = iframe.height = adSize.height;
            iframe.style.overflowVisible = true;
            iframe.style.background = "transparent";

            //Note we're using this.BuzzAppUrl -- works because this gets mixed into the Buzzlet prototype
            //iframe.src = this.buzzAppUrl + "Ads/WidgetAd.aspx?buzzId=" + this.buzzId + "&apiToken=" + this.apiToken + "&height=" + adHeight + "&width=" + adWidth;
            iframe.src = getAdFrameSrc(this, adWidth, adHeight);

            if (replaceContainerElem) {
                containerElem.parentNode.replaceChild(iframe, containerElem);
                containerElem = iframe;
            }

            //Restyle the ad container to be the right size and the be centered
            containerElem.style.height = adHeight + "px";
            containerElem.style.width = adWidth + "px";

            if (ng_isIE) {
                //IE doesn't like the padding/margin centering method, so we'll use floats and calculated margins instead
                containerElem.style.marginLeft = ((w - adWidth) / 2) + "px";
                containerElem.style.position = "relative"; //fix problems in IE with parent elements relatively position. IH8IE6
                containerElem.style["float"] = "left";

            } else {
                containerElem.style.paddingLeft = "50%";
                containerElem.style.marginLeft = (-adWidth / 2) + "px";
            }

            return adSize;
        },

        renderGoogleAd: function(width, height) {
            var frameSrc = getAdFrameSrc(this, width, height);
            return '<iframe src="' + frameSrc + '" frameborder="0" scrolling="no" width="' + width + '" height="' + height + '" background="transparent"></iframe>';
        },

        /**
        * Renders out the placeholder for an autosized ad, and sets up the callbacks to populate it later
        **/
        renderAutosizeAd: function(maxWidth, maxHeight) {
            var containerId = "ngAdPlaceholder_" + this.uniqueId;

            //Add the callback to find the placeholder element and insert an ad into it
            this.addPostRenderCallback(function(a, b, buzzObj) {
                var ph = document.getElementById(containerId);
                if (!ph) return;

                var siblings = ph.parentNode.childNodes;

                var onlyChild = true;
                for (var i = 0; i < siblings.length; i++) {
                    var n = siblings[i];
                    if (n.nodeType == 1 && n != ph) {
                        onlyChild = false;
                        break;
                    }
                }

                if (onlyChild) {
                    var dimensions = measureDimensions(ph.parentNode);
                    //var h = (ng_isIE && dimensions.height && dimensions.height > 18 ? dimensions.height : defaultMaxHeight);
                    //alert("not only child height: " + dimensions.height + "  width: " + dimensions.width);
                    buzzObj.insertAd(ph, dimensions.width, dimensions.height, true, true);
                } else {
                    //alert("not only child height: " + maxHeight + "  width: " + maxWidth);
                    buzzObj.insertAd(ph, maxWidth, maxHeight, true, true);
                }
            });

            //return the placeholder so it can go into the output HTML
            return '<div id="' + containerId + '" style="height:0px;line-height:0px;width:100%;"></div>';
        }
    };


    /**
    * Mixin the ads functions to the regular BuzzObj and RenderContext prototypes
    */
    ng_mixin(ng.buzz.Buzzlet.prototype, mixinFunctions);
    
    ng.buzz.RenderContext.prototype.RenderAutosizeAd = function() {
        return this.BuzzObj.renderAutosizeAd.apply(this.BuzzObj, arguments);
    }
    
    ng.buzz.RenderContext.prototype.RenderGoogleAd = function() {
        return this.BuzzObj.renderGoogleAd.apply(this.BuzzObj, arguments);
    }
})();

(function(){
	
	var adMeld_Class = function(buzzObj, adConfig){
		var self = this;
		
		var publisher = adConfig.publisher || "";
		var site = adConfig.site || "";
		var width = adConfig.width || "";
		var height = adConfig.height || "";
		var placement = adConfig.placement || "";
		
		this.buzzAppUrl = buzzObj.buzzAppUrl;
		this.adMeldPageUrl = "ads/admeld.aspx";
		this.iframeId = "admeld" + Math.floor(Math.random() * 10000);
	
		this.renderAd = function(){
			if (self.adMeldPageUrl.indexOf("?") == -1){
				self.adMeldPageUrl += "?";
			}
			
			var size = width + "x" + height;
			
			var url = self.buzzAppUrl + self.adMeldPageUrl
				+ "&publisher=" + encodeURIComponent(publisher)
				+ "&site=" + encodeURIComponent(site)
				+ "&size=" + encodeURIComponent(size)
				+ "&placement=" + encodeURIComponent(placement);
				
			var html = "<iframe id='" + self.iframeId + "' name='" + self.iframeId + "' src='" + url + "' framespacing='0' frameborder='no' scrolling='no'  marginwidth='0' marginheight='0' width='" + width + "' height='" + height + "'></iframe>";
			
			return html;
		};
	};
	
	var technoratiAd_Class = function(buzzObj, adConfig)
	{
		var self = this;
		
		var width = adConfig.width || "";
		var height = adConfig.height || "";
		var section = adConfig.section || "";
		
		this.technoratiPageUrl = "http://ad.yieldmanager.com/st?ad_type=iframe";
		this.iframeId = "technorati" + Math.floor(Math.random() * 10000);
		
		this.renderAd = function(){
			var size = width + "x" + height;
			
			var url = self.technoratiPageUrl
				+ "&ad_size=" + size
				+ "&section=" + section;

			var html = "<iframe id='" + self.iframeId + "' name='" + self.iframeId + "' src='" + url + "' framespacing='0' frameborder='no' scrolling='no' marginwidth='0' marginheight='0' width='" + width + "' height='" + height + "'></iframe>";
			
			return html;
		};
	};
	
	var htmlAd_Class = function(adConfig){
		var self = this;
		var html = adConfig.html || "";
		
		this.renderAd = function(){
			return html;
		};
	};
	
	function getAd(buzzObj, indexOrName){
		var adConfig = null;
		if (typeof indexOrName == "number"){
			adConfig = getAdConfigByIndex(buzzObj, indexOrName);
		}
		else{
			try{
				var parsedVal = parseInt(indexOrName, 10);
				if (parsedVal + "" == indexOrName){
					adConfig = getAdConfigByIndex(buzzObj, indexOrName);
				}
				else{
					adConfig = getAdConfigByName(buzzObj, indexOrName);
				}
			}
			catch (e){
				adConfig = getAdConfigByName(buzzObj, indexOrName);
			}
		}
		
		if (adConfig == null){
			return null;
		}
		
		var ad = {}; //return obj
		
		switch (adConfig.type.toLowerCase()){
			case "admeld":
				ad = new adMeld_Class(buzzObj, adConfig);
				//ad.buzzAppUrl = "http://localhost/ngbuzz/"; //dev purposes
				break;
			case "technorati":
				ad = new technoratiAd_Class(buzzObj, adConfig);
				break;
			case "generic":
			default:
				ad = new htmlAd_Class(adConfig);
				break;
		}
		return ad;
	}
	
	function getAdConfigByName(buzzObj, name){
		var adConfigs = buildAdConfigs(buzzObj);
		for (var x = 0; x < adConfigs.length; x++){
			if (adConfigs[x].name && adConfigs[x].name.toLowerCase() == name.toLowerCase()){
				return adConfigs[x];
			}
		}
		return null;
	}
	
	function getAdConfigByIndex(buzzObj, index){
		var adConfig = buildAdConfigs(buzzObj);
		if (adConfig == undefined || adConfig == null){
			return null;
		}
		return adConfig;
	}
	
	function buildAdConfigs(buzzObj)
	{
		var ads = [];
		
		//pull data from config and populate ads
		for (var key in buzzObj.WidgetConfiguration)
		{
			if (key.match("^admanager_")=="admanager_")
			{
				//determine the index
				var secondHyphenIndex = key.indexOf("_", 10);
				var indexString = key.substring(10, secondHyphenIndex);
				var index = parseInt(indexString, 10);
				
				//if the index doesn't have an object, create one for it
				if (ads[index] == undefined){
					ads[index] = {};
				}
				
				//get the key for the placement and set it's value to the ads array
				var configKey = key.substring(secondHyphenIndex + 1);
				ads[index][configKey] = buzzObj.WidgetConfiguration[key];
			}
		}
		
		return ads;
	}

	var mixinFunctions = {
		renderAd : function(indexOrName){
			if (indexOrName == undefined || indexOrName == null){
				indexOrName = 0;
			}
			
			var ad = getAd(this, indexOrName);

			if (ad == null) { 
				ng_debug(this, ["No advertisement configured for: " + indexOrName]);
				return "<" + "!-- No Advertisement Configured for: " + indexOrName + " -->";
			}
			
			return ad.renderAd();
		}
	};
	
	ng_mixin(ng.buzz.Buzzlet.prototype, mixinFunctions);
	
	ng.buzz.RenderContext.prototype.RenderAd = function(){
		return this.BuzzObj.renderAd.apply(this.BuzzObj, arguments);
	}

})();


var ng_buzzFooterCss = '.buzzFooter{width:100%;padding:0;background-color:#eee}.buzzFooter .embed{color:#2d2d2d;font-family:tahoma;font-size:9px}.buzzFooter .footerText{color:#000;font-family:arial;font-size:10px}.ng_getThis{position:absolute;background-color:#fff;z-index:10000;padding:0;font-family:arial;font-size:11px;overflow:auto;border:solid 1px #000}.ng_getThisTOS{font-style:italic}.ng_getThis a,.ng_getThis a:visited{border:0;text-decoration:none;color:#000}.ng_getThis a:hover{color:#999}.ng_getIcon{text-align:left;padding:0 3px 0 0;margin-bottom:5px;float:left;white-space:nowrap}.ng_getThis input{font-family:tahoma;font-style:normal;font-variant:normal;font-weight:normal;font-size:9px;line-height:normal;width:50%;font-size-adjust:none;font-stretch:normal}.ng_getThis .ng_createNew a,.ng_getThis .ng_createNew a:visited,.ng_getThis .ng_createNew a:hover,.ng_getThis .ng_getThisTOS a,.ng_getThis .ng_getThisTOS a:visited,.ng_getThis .ng_getThisTOS a:hover{color:#00f;font-weight:bold}.ng_getThis .ng_createNew a:hover,.ng_getThis .ng_getThisTOS a:hover{text-decoration:underline}#ngbuzz_signUpDiv,#ngbuzz_signUpDiv div,#ngbuzz_signUpDiv table,#ngbuzz_signUpDiv td,#ngbuzz_signUpDiv div{margin:0;padding:0;line-height:1.1em;margin:0;padding:0;border:0;outline:0;font-weight:normal;font-style:normal;font-size:100%;font-family:times new romain,serif;vertical-align:baseline;background-color:transparent;color:#000}#ngbuzz_signUpDiv{border:1px solid #000;position:absolute;z-index:10001}#ngbuzz_signUpDiv table{background-color:#fff;border:3px solid #fff;width:600px}.ng_ShowFormClass{position:absolute;display:block;background-color:#fff;border:solid 1px #000;padding:5px;font-family:Arial,Verdana;font-size:11pt;width:auto;height:auto;overflow-y:auto;z-index:10001}.ng_EmailForm,.ng_CommentForm{width:100%}.ng_ShowFormClass .ng_FormExample{color:#777;font-size:7pt}.ng_ShowFormClass INPUT,.ng_ShowFormClass TEXTAREA{width:95%}.ng_FormContainer{text-align:left}.ng_ShowFormClass INPUT.ng_FormSubmitButton{background-color:#f4f4f4;border:1px solid;border-color:#ccc #666 #666 #ccc;color:#555;font-size:10px;cursor:pointer;width:auto}.ng_ShowFormClass .ng_Feedback{padding-right:4px}';

var defaultBuzzFooter = ' <div class="buzzFooter" style="width:100%; padding:0px; background-color: #f3f3f3; overflow:hidden; zoom:1;">\r\n\t <div style="float:left; width:66px; background-color:#eee;"><nobr>\r\n\t <a title="Get This" style="cursor: pointer;" href="javascript:void(0)" onclick="${ShowGetThisJS};${AttentionJS("custom3")}"><img border="0" style="margin: 3px;" alt="Get This" src="${BuzzAppUrl}/images/getThis_grey.png" /></a>\r\n\t </nobr></div>\r\n\t <div style="float:right; text-align:right; padding:7px 4px 0px 3px !important; padding:4px 4px 0px 3px; width:55px;"><img border="0" style="cursor: pointer;" onclick="window.open(\'http://widgets.newsgator.com\');${AttentionJS("click")}" src="${NGBaseUrl}/NGBuzz/Images/PoweredbyNG.gif" alt="Powered by NewsGator" /> </div>\r\n\t<br clear="all" />\r\n </div>';

var defaultBuzzGetThis = '<div id="${GetThisBoxId}" class="ng_getThis" style="display:none; background-image: url(${BuzzAppUrl}Images/bgtile.gif); background-repeat:repeat-x; border:1px solid #ccc; font-family:"Lucida Grande",Geneva,arial,sans-serif; color:#222;">\r\n\t<img alt="close" onclick="${HideGetThisJS}" src="${BuzzAppUrl}Images/x.png" style="float:right; cursor:pointer; margin:5px 5px 0 5px;" />\r\n\t{if UseGigyaSharing}\r\n\r\n\t\t${% new NGGetThis(BuzzObj, { width: ContainerWidth }).getMarkup() %}\r\n\r\n\t{else}\r\n\t\t{var showEmail = /email/i.test(SharingOptions)}\r\n\t\t{var showScript = /script/i.test(SharingOptions)}\r\n\t\t{var showCreate = /create/i.test(SharingOptions)}\r\n\t\t<div style="padding:7px;color:black;">\r\n\t\tAdd this widget to your blog or personal homepage or email it to a friend.\r\n\t\t<div>\r\n\t\t\t{if /typepad/i.test(SharingOptions)}<div class="ng_getIcon" style="background-color: #fff; border:1px solid #ccc; padding:4px; margin:3px 3px 0 0;"><a target="_blank" href="${TypepadLink}" title="TypePad"><img border="0" align="absmiddle" alt="TypePad" src="${BuzzAppUrl}Images/typepad.png"/></a></div>{/if}\r\n\t\t\t{if /blogger/i.test(SharingOptions)}<div class="ng_getIcon" style="background-color: #fff; border:1px solid #ccc; padding:4px; margin:3px 3px 0 0;"><a target="_blank" href="${BloggerLink}" title="Blogger"><img border="0" align="absmiddle" alt="Blogger" src="${BuzzAppUrl}Images/blogger.png"/></a></div>{/if}\r\n\t\t\t{if /netvibes/i.test(SharingOptions)}<div class="ng_getIcon" style="background-color: #fff; border:1px solid #ccc; padding:4px; margin:3px 3px 0 0;"><a target="_blank" href="${NetvibesLink}" title="Netvibes"><img border="0" align="absmiddle" alt="Netvibes" src="${BuzzAppUrl}Images/netvibes.png"/></a></div>{/if}\r\n\t\t\t{if /google/i.test(SharingOptions)}<div class="ng_getIcon" style="background-color: #fff; border:1px solid #ccc; padding:4px; margin:3px 3px 0 0;"><a target="_blank" href="${GoogleLink}" title="iGoogle"><img border="0" align="absmiddle" alt="iGoogle" src="${BuzzAppUrl}Images/igoogle.png"/></a></div>{/if}\r\n\t\t\t{if /pageflakes/i.test(SharingOptions)}<div class="ng_getIcon" style="background-color: #fff; border:1px solid #ccc; padding:4px; margin:3px 3px 0 0;"><a target="_blank" href="${PageflakesLink}" title="Pageflakes"><img border="0" align="absmiddle" alt="Pageflakes" src="${BuzzAppUrl}Images/pageflakes.png"/></a></div>{/if}\r\n\t\t\t{if /LiveDotCom/i.test(SharingOptions)}<div class="ng_getIcon" style="background-color: #fff; border:1px solid #ccc; padding:4px; margin:3px 3px 0 0;"><a target="_blank" href="${LiveDotComLink}" title="Live.com"><img border="0" align="absmiddle" alt="Live.com" src="${BuzzAppUrl}Images/windows.png"/></a></div>{/if}\r\n\t\t\t{if /LiveSpaces/i.test(SharingOptions)}<div class="ng_getIcon" style="background-color: #fff; border:1px solid #ccc; padding:4px; margin:3px 3px 0 0;"><a target="_blank" href="${LiveSpacesLink}" title="Live Spaces"><img border="0" align="absmiddle" alt="Live Spaces" src="${BuzzAppUrl}Images/livespaces.png"/></a></div>{/if}\r\n\t\t\t{if /Facebook/i.test(SharingOptions)}<div class="ng_getIcon" style="background-color: #fff; border:1px solid #ccc; padding:4px; margin:3px 3px 0 0;"><a target="_blank" href="${FacebookLink}" title="Facebook"><img border="0" align="absmiddle" alt="Facebook" src="${BuzzAppUrl}Images/facebook.png"/></a></div>{/if}\r\n\t\t\t{if /Vista/i.test(SharingOptions)}<div class="ng_getIcon" style="background-color: #fff; border:1px solid #ccc; padding:4px; margin:3px 3px 0 0;"><a target="_blank" href="${VistaLink}" title="Vista Sidebar"><img border="0" align="absmiddle" alt="Vista Sidebar" src="${BuzzAppUrl}Images/vista.png"/></a></div>{/if}\r\n\t\t\t{if showEmail}<div class="ng_getIcon" style="background-color: #fff; border:1px solid #ccc; padding:4px; margin:3px 3px 0 0; cursor:pointer"><img onclick="${ShowEmailWidgetJS}" border="0" align="absmiddle" alt="Email to a friend" title="Email to a friend" src="${BuzzAppUrl}Images/email.png"/></div>{/if}\r\n\t\t</div>\r\n\t\t<br clear="all"/>\r\n\t\t{if showScript}<div style="padding:7px 0 0;"><strong>Embed code:</strong> <input type="text" value="<script src=&quot;${EmbedScriptSrc}&quot; type=&quot;text/javascript&quot;></script>" onclick="this.focus();this.select();" /></div>{/if}\r\n\t\t\t<a href="http://www.newsgatorwidgets.com" target="_blank"><img src="${BuzzAppUrl}Images/NGfooterlogo.png" alt="NewsGator" border="0" style="float:right; margin:4px 0 0;" /></a>\r\n\t\t\t{if showCreate}\r\n\t\t\t\t<div style="padding:7px 0 0 0;">\r\n\t\t\t\t\t<a href="javascript:void(0)" onclick="${ShowSignupJS}">\r\n\t\t\t\t\t\t<img border="0" align="absmiddle" src="${BuzzAppUrl}Images/widget.png"/>\r\n\t\t\t\t\t\t&nbsp;<nobr>Create your</nobr> <nobr>own widget</nobr>\r\n\t\t\t\t\t</a>\r\n\t\t\t\t</div>\t\t\t\r\n\t\t\t{/if}\r\n\t\t<div style="padding:15px 0 0;text-align:left;font-size:smaller;blue:#555">If you copy and use this Widget code for use on your website(s), you agree to the following <a target="_blank" href="${NGBaseUrl}admin/pages/legal.html" style="color:blue">terms and conditions</a>.</div>\r\n\t{/if}\r\n</div>';

var defaultEmailWidget = '<div id="ngEmailFormHtmlDiv_${BuzzId|html}" class="ng_ShowFormClass ng_EmailForm" style="display:none">\r\n\t<div class="ng_FormContainer" align="left">\r\n\t\t<div class="ng_FormLabel">\r\n\t\t\tTo Email: <br /> \r\n\t\t\t<span class="ng_FormExample">(someone@example.com)</span>\r\n\t\t</div>\r\n\t\t<input type="text" id="ngEmailToTxt_${BuzzId|html}" value="" class="ng_FormInput" />\r\n\t\t\r\n\t\t<div class="ng_FormLabel">\r\n\t\t\tFrom Email: <br />\r\n\t\t\t<span class="ng_FormExample">(me@example.net)</span>\r\n\t\t</div>\r\n\t\t<input type="text" id="ngEmailFromTxt_${BuzzId|html}" value="" class="ng_FormInput" />\r\n\t\t\r\n\t\t<div class="ng_FormLabel">Subject:</div>\r\n\t\t<input id="ngEmailTitleTxt_${BuzzId|html}" type="text" value="Check out this widget!" class="ng_FormInput" />\r\n\t\t\r\n\t\t<div class="ng_FormLabel">Comment:</div>\r\n\t\t<textarea id="ngEmailMsgTxt_${BuzzId|html}" class="ng_FormInput" cols="15"></textarea>\r\n\t\t\r\n\t\t<div nowrap align="right" style="padding-top:8px">\r\n\t\t\t<input type="button" class="ng_FormSubmitButton" value="Submit" onclick="ng_emailWidget(event, this, \'${BuzzId|js}\', ${BuzzObjRef});${AttentionJS("custom3")}">\r\n\t\t\t<input type="button" class="ng_FormSubmitButton" onclick="ng_closeEmailWidgetForm(event, this, \'ngEmailFormHtmlDiv_${BuzzId|js}\');" value="Close" />\r\n\t\t\t<input type="hidden" id="ngEmailMsgTemplate_${BuzzId|html}" value="${EmailSharingTemplate}" />\r\n\t\t\t<input type="hidden" id="ngEmailPreserveArgs_${BuzzId|html}" value="${PreserveExtraArgs}" />\r\n\t\t\t{if FbApiKey}<input type="hidden" id="fbApiKey_${BuzzId|html}" value="${FbApiKey|html}" />{/if}\r\n\t\t\t<div id="ngEmailFeedback_${BuzzId|html}" class="ng_Feedback" style="text-align:right; width:100%; color:white">Placeholder text</div>\r\n\t\t</div>\r\n\t</div>\r\n</div>';

var defaultBuzzSignup = '<div id="ngbuzz_signUpDiv" style="display:none;">\r\n\t<div style="border:10px solid #eaebee;">\r\n\t<table cellpadding="3" cellspacing="0" border="0">\r\n\t\t<tr valign="top">\r\n\t\t\t<td align="center" style="background-color:#efefe5; width:180px;"><img src="http://nmp.newsgator.com/ngbuzz/Images/NGLogo_Viral.gif" border="0" width="141" height="32"></td>\r\n\t\t\t<td align="right" style="vertical-align:top"><a href="javascript:void(0)" onclick="ng_closeSignUpDiv()"><img src="http://nmp.newsgator.com/ngbuzz/Images/close.gif" vspace="2" border="0" alt="Close" /></a></td>\r\n\t\t</tr>\r\n\t\t<tr>\r\n\t\t\t<td align="left" valign="top" style="background-color:#efefe5; width:180px; padding:4px 10px 0px 10px; font-size:12px; vertical-align:top">\r\n\t\t\t\t<strong>Get NewsGator Widgets on your site in just a few steps!</strong> Give us your name and contact information, we\'ll give you a script to put on your site.<br /><br />\r\n\t\t\t\t<strong>Want to customize it?</strong> When you sign up we\'ll give you a login into the "NewsGator Editor\'s Desk" where you can change content, change the look and feel, or manage the content that is shown.\r\n\t\t\t</td>\r\n\t\t\t<td valign="top" style="padding-left:30px;">\r\n\t\t\t\t<iframe marginheight="0" marginwidth="0" scrolling="auto" align="left" width="100%" height="340" frameborder="No">&nbsp;</iframe>\r\n\t\t\t</td>\r\n\t\t</tr>\r\n\t</table>\r\n\t</div>\r\n</div>';

try{window['ng_buzz'] = true;}catch(e){}
