	function REST(action, data, callback, ajaxArgs)
	{
		if (data == null)
			data = {};

		if (typeof(data) == "object")
		{
			data["_s"] = randStr(10);
			data = objectToString(data);
		}

		ajaxArgs = ajaxArgs || {};
		ajaxArgs.async = ajaxArgs.async !== false;
		if (/^http/i.test(action))
			ajaxArgs.url = action;
		else
			ajaxArgs.url = ((/^\//.test(action)) ? "" : (globals.basePath || "/admin/") + "api/") + action;
		ajaxArgs.data = data;
		ajaxArgs.type = (((ajaxArgs.type || "post").toLowerCase() == "post") ? "POST" : "GET");
		ajaxArgs.dataType = ajaxArgs.dataType || "json";
		ajaxArgs.success = callback || null;

		$.ajax(ajaxArgs);
	}

	function showMessage(message, isError, kwargs)
	{
		var kwargs = kwargs || {};

		var classes = kwargs.classes || "";
		if (isError && !classes)
			classes = "error";

		var e = $(".messages:eq(0)");
		if (e.length > 0)
		{
			e.fadeOut(function(){
				e.html("<p class=\""+ classes +"\">"+ message +"</p>");
				e.fadeIn();
			});
		}
		else
			alert(message);
	}

	/**
	 * e.g. var qsObject = getDataRowObject(getQS()));
	 **/
	function getDataRowObject(data, kwargs)
	{
		var retObject = {};
		if (testNullString(data)) // only process if the 'data' isn't empty
		{
			kwargs = kwargs || {};
			var itemDelimiter = kwargs.itemDelimiter || "&";
			var valueDelimiter = kwargs.valueDelimiter || "=";

			var arrData = String(testNullString(data, "")).split(itemDelimiter);

			for (var i = 0; i < arrData.length; i ++)
			{
				var kv = arrData[i].split(valueDelimiter);
				retObject[kv[0]] = kv[1];
			}
		}
		return retObject;
	}

	function objectToString(data, kwargs)
	{
		kwargs = kwargs || {};
		var itemDelimiter = kwargs.itemDelimiter || "&";
		var valueDelimiter = kwargs.valueDelimiter || "=";
		var includeEmpty = kwargs.includeEmpty === true; // default to false

		var output = [];
		for (key in data)
			if (includeEmpty || testNullString(data[key]))
				output.push(key + valueDelimiter + testNullString(data[key], ""));

		return output.join(itemDelimiter);
	}

	function getArray(mixedInput, returnIfNull)
	{
		var arrRet = ((typeof(returnIfNull) != "undefined") ? returnIfNull : []);

		if (typeof(mixedInput) == "string" && mixedInput)
			arrRet = mixedInput.split(/\s*,\s*/g);
		else if (mixedInput != null && typeof(mixedInput) == "object")
			arrRet = mixedInput;
		
		return arrRet;
	}

	function typify(value, boolEscape) { return ((value) ? ((/^-?\d*\.?\d+$/.test(value)) ? Number(value) : ((boolEscape) ? dbEscape(value) : value)) : value); }
	function dbEscape(value) { return String((value || "")).replace(/[']/g,"''"); }
	function conditionalQuote(value) { return ((value) ? ((/^-?\d*\.?\d+$/.test(value)) ? value : "'"+ dbEscape(value) +"'") : value); }

	// loc can either be an integer offset to scroll to or an element id that will be scrolled to.
	function scrollTo(loc, speed, scrollWrapper, callback)
	{
		var offset = 0;
		speed = ((typeof(speed) != "undefined") ? speed : 300);
		scrollWrapper = ((typeof(scrollWrapper) != "undefined") ? poundFirst(scrollWrapper) : "html,body");
		callback = ((typeof(callback) == "function") ? callback : null);

		if (/^\d+$/.test(String(loc)))
			offset = loc;
		else
		{
			try {
				var e = poundFirst(loc);
				offset = $(e).offset().top;
			} catch(e) { return; } // If we couldn't find anywhere to scroll to, don't scroll.
		}

		$(scrollWrapper).animate({ scrollTop: offset }, { duration: speed, complete: callback });
	}

