	/*
	 * Projekt Orange - web - 
	 * Donovan's jQuery HTML Snippet Engine
	 * Version 2.1.0
	 *
	 * MiT Licensed.
	 * You may use this code and modify it freely so long as you maintain original credit at the top.
	 * thanks!
	 *
	 * METHOD REFERENCE // methods have been tested on only 1 element. 
	 *
	 * $("#elementID").snippet(templateName, data) <- (name of template stored in jQuery, properly formatted data object)
	 * $("#elementID").snippetString(templateString, data) <- (the actual template, properly formatted data object)
	 * $("#elementID").snippetURL(inURL, data) <- NOT YET IMPLEMENTED (will grab a template from a url, then fill it in with data)
	 * 
	 * FUNCTION REFERENCE 
	 *										
	 * $.setTemplateLib(inTemplateHash) <- (a js Object where each named attribute is a template)
	 *										sets the templates for this library.  Good if you have a lot of templates and want to keep them handy
	 * $.snippetString(templateString, data) <- (the actual template, properly formatted data object)
	 *										returns the filled template
	 * $.loadURLTemplates(inURLHash) <- (a js Object where each named attribute is the url to a corresponding template)
	 *										so this function will retrieve templates from a server and store them in the orange template library.
	 *										they can then be used by calling snippet with the name the url was associated with in the original js object
	 *										js Object. like so:
	 *															$.loadURLTemplates({"my_tpl":"http://supernoggies.com/js_templates/my_template.html", 
	 *																					"your_tpl":"/js_templates/u_template.html"})
	 *															$("#target").snippet("my_tpl", data);				
	 * $.snippetURL(inURL, data) <- NOT YET IMPLEMENTED (will grab a template from a url, then fill it in with data and return the filled template
	 * 
	 * $.objFromDom(inElementIDArray, inPrefix) <- Creates a Javascript Object from html form elements with the 
	 * $.fillForm(inObj, inPrefix)
	 * $.inspect(inObj, inConfig) 
	 *
	 */	
	
	
	//jQuery POSITION Methods
	jQuery.fn.centerElement = function(inID) {
		var width = parseInt(this.css("width"));
		var height = parseInt(this.css("height"));
		var winHeight = jQuery(document).height();
		var winWidth = jQuery(document).width();
		this.css("top", Math.floor((winHeight - height) /2) + "px"); 
		this.css("left", Math.floor((winWidth - width) /2) + "px"); 
		return(this);
	}
	
	//jQuery KEYLISTENER Methods	
	jQuery.fn.listen = function(inConfig) {
		if(this.selector.indexOf("#") == 0 && typeof(inConfig.htmlID) == "undefined") {
			inConfig.htmlID = this.selector.substring(1);
		}
		var keyListener = new KeyListener(inConfig);
		
		switch(inConfig.keystroke) {
			default :
				this.keyup( function(e) {keyListener.processKey(e); } );
		}		
	}
	
	//jQuery TEMPLATE Methods
	jQuery.fn.snippet = function(inTPName, inElementHash) {
		var snippet = jQuery.snippet(inTPName, inElementHash);
		this.html(snippet);
		return(this);	
	};
	
	
	jQuery.fn.snippetAfter= function(inTPName, inElementHash) {
		var snippet = jQuery.snippet(inTPName, inElementHash);
		this.after(snippet);
		return(this);
	}
	
	
	jQuery.fn.snippetAppend = function(inTPName, inElementHash) {
		var snippet = jQuery.snippet(inTPName, inElementHash);
		this.append(snippet);
		return(this);	
	};
	
	jQuery.fn.snippetBefore = function(inTPName, inElementHash) {
		var snippet = jQuery.snippet(inTPName, inElementHash);
		this.before(snippet);
		return(this);
	}
	
	jQuery.fn.snippetPrepend = function(inTPName, inElementHash) {
		var snippet = jQuery.snippet(inTPName, inElementHash);
		this.prepend(snippet);
		return(this);	
	};
	
	jQuery.fn.snippetString = function(inTemplateString, inElementHash) {
		var snippet = new Snippet(inTemplateString);
		this.html(snippet.fill(inElementHash));
		delete(snippet);
		return(this);
	};
	
	//END jQuery TEMPLATE methods
	
	//jQuery TEMPLATE Functions
	
		
	jQuery.snippet = function(inTPName, inElementHash) {
		jQuery.makeOrangeVars();
		if(!jQuery.variables.orange.templates.hasOwnProperty(inTPName)) {
			var msg = "template:'" + inTPName + "' not set";
			if(!jQuery.log(msg)) alert(msg);
			return("invalid snippet: "  + inTPName);
		}
		return(jQuery.variables.orange.templates[inTPName].fill(inElementHash));
	};
	
	jQuery.snippetString = function(inTemplateString, inElementHash) {
		jQuery.makeOrangeVars();
		var snippet = new Snippet(inTemplateString);
		var retString = snippet.fill(inElementHash);
		delete(snippet);
		return(retString);		
	};
	
	
	jQuery.makeOrangeVars = function() {
		if(!jQuery.hasOwnProperty("variables")) {
			jQuery.variables = new Object();
		}
		if(!jQuery.variables.hasOwnProperty("orange")) {
			jQuery.variables.orange = new Object();
		}
		if (!jQuery.variables.orange.hasOwnProperty("templates")) {
			jQuery.variables.orange.templates = new Object();
		}
		if (!jQuery.variables.orange.hasOwnProperty("left")) {
			jQuery.variables.orange.left = "{";
		}
		if (!jQuery.variables.orange.hasOwnProperty("right")) {
			jQuery.variables.orange.right = "}";
		}
	}
	
	jQuery.hasSnippet = function(inTemplateName) {
		jQuery.makeOrangeVars();
		if(jQuery.variables.orange.templates.hasOwnProperty(inTemplateName)) {
			return (jQuery.variables.orange.templates[inTemplateName]);
		}
		return(false);
	}
	
	
	jQuery.setSnippetLib = function(inTemplates) {
		jQuery.makeOrangeVars();
		for(i in inTemplates) {
			if(inTemplates.hasOwnProperty(i)) 
				jQuery.variables.orange.templates[i] = new Snippet(inTemplates[i]);
		}
		//jQuery.variables.orange.templates = inTemplates;	
	};
	
	
	jQuery.addSnippet = function(inTPLName, inTplStr) {
		jQuery.makeOrangeVars();
		jQuery.variables.orange.templates[inTPLName] = new Snippet(inTplStr);
	};
	
	
	jQuery.getSnippets = function(inTemplateURLs) {
		jQuery.makeOrangeVars();
		for(i in inTemplateURLs) {
			if(inTemplateURLs.hasOwnProperty(i)) {
				eval("jQuery.ajax({type:'GET', url:inTemplateURLs[i], success: function(template) {	jQuery.variables.orange.templates." + i + " = new Snippet(template);}, error: function(XMLHttpRequest, textStatus, errorThrown){ alert('error' + textStatus + ' ' + errorThrown);}});");
			}
		}
	};
	
	
	//END jQuery TEMPLATE Functions
	
	
	jQuery.attrList = function(inObj) {
		var retList = new Array();
		for(i in inObj) {
			if(inObj.hasOwnProperty(i)) {
				retList.push(i);
			}
		}
		return(retList);
	}
	
	
	//jQuery FORM Functions
	jQuery.fillForm = function(inObj, inPrefix) {
		var prefix = "";
		if(typeof(inPrefix) != "undefined") { prefix = inPrefix };
		for(i in inObj) {
			if(inObj.hasOwnProperty(i)) {
				jQuery("#" +prefix + i).val(inObj[i]);
			}
		}
		delete(prefix);
	}
	
	jQuery.objFromDom = function(inFormIDList, inDOMPrefix) {
		if(!inDOMPrefix) inDOMPrefix = "";
		var newObject = {};
		for(i in inFormIDList) {
			if(inFormIDList.hasOwnProperty(i)) {
				newObject[inFormIDList[i]] = jQuery("#" + inDOMPrefix + inFormIDList[i]).val();
			}
		}
		return(newObject);
	}
	
	
	jQuery.ofd = function(inFormIDList, inDOMPrefix) {
		return(jQuery.objFromDom(inFormIDList, inDOMPrefix));
	}
	
	//jQuery UTIL functions
	jQuery.log = function(e, title, inspect) {
		if(typeof(console) != "undefined") {
			if(typeof(console.log) == "function") {
				if(typeof(title) == "undefined") title = "";
				else title = title + "\n";
				
				if(typeof(inspect) == "boolean" && inspect) console.log(title + jQuery.inspect(e));
				else console.log(title + e );
				
				return(true);
			}
		}
		return(false);
	}
	//WARNING: RECURSIVE OBJECTS WILL RECURSE INFINITELY!!!!
	jQuery.clone = function(inObj, root) {
		if(typeof(root) == "undefined") {
			var cleanCloneList = true;
			jQuery.variables.orange.cloneList = new Array();
		}
		if(typeof (inObj) == "undefined") {
			return(inObj);
		}
		if(inObj.constructor == Array) {
			var retObj = [];
			for (var i = 0; i < inObj.length; i++) {
	    	    if (typeof inObj[i] == 'object') {
		            retObj[i] = new jQuery.clone(inObj[i], true);
		        }
		        else {
		            retObj[i] = inObj[i];
		        }
		    }
		}
		else {
			var retObj = {};
		    for (var i in inObj) {
		    	if(inObj.hasOwnProperty(i)) {
			        if (typeof inObj[i] == 'object') {
			            retObj[i] = new jQuery.clone(inObj[i], true);
			        }
			        else {
			            retObj[i] = inObj[i];
			        }
			    }
		    }
		}
	    return(retObj);
	}
	
	/**
	* inConfig - optional - options: indent, maxRecurse (circular objects could recurse infinitely), allProps (defaults to has own property), toJSON (converts the object to JSON string)
	*  
	*/
jQuery.inspect = function(inObject, inConfig) {
		
		var outString = "";
		var indent = "";
		if(typeof(inConfig) == "undefined") {
			var inConfig = {};
		}
		if(!inConfig.hasOwnProperty("recurse"))  {
			inConfig.recurse = 0;
		}
		if(!inConfig.hasOwnProperty("maxRecurse"))  {
			inConfig.maxRecurse = 5;
		}
		if(!inConfig.hasOwnProperty("allProps"))  {
			inConfig.allProps = false;
		}		
		if(!inConfig.hasOwnProperty("toJSON"))  {
			inConfig.toJSON = false;
		}		
		
		if(!inConfig.hasOwnProperty("indent")) {
			inConfig.indent = "";
		} else {
			indent = inConfig.indent; 
			inConfig.indent += "**";
		}	
		var inObjType = typeof(inObject);
		if(!inConfig.toJSON) {
			switch(inObjType) {
				case "string":
					outString += "\"" + inObject + "\"";
					break;
				case "boolean":
					outString += "bool(" + inObject + ")";
					break;
				case "number": 
					outString += inObject;
					break;
				case "undefined":
					outString += "undefined";
					break;
				case "object":
					if(inObject.constructor == Array) outString += "[\n";
					else outString +="{\n";
					for(var key in inObject) {
						if(key != "parent") {
						try {
							if(inObject.hasOwnProperty(key) || inConfig.allProps) {
						  		var propType = typeof(inObject[key]); 
						  		outString += inConfig.indent + " " + key;
								if(propType == "object") {							
									if(inConfig.recurse < inConfig.maxRecurse) {
										inConfig.recurse++;
										outString += "= " + jQuery.inspect(inObject[key], inConfig);
										inConfig.recurse--;
									} else {
										if(inObject[key] == null) outString += "= null";
										else if(inObject[key].constructor == "Array") outString += "= [Array] (at max recurse:" + inConfig.recurse + ")";
										else outString += "= {object} (at max recurse:" + inConfig.recurse + ")";
									}
								} else { 
									outString += "= " + jQuery.inspect(inObject[key], inConfig);
								}
								outString += "\n";
							}
						} catch (e) {
							return (outString + " ERROR inspecting!: " + e.message);
						}
						}
					}
					if(inObject.constructor == Array) outString += inConfig.indent + "]\n";
					else outString += inConfig.indent + "}\n";
					break;
				default :
					outString += inObject.toString();
			}
			inConfig.indent = indent;
		} else { //if inConfig.toJSON
			switch(inObjType) {
			case "string":
				var regEx = /("|\\)/
				var transStr = "";
				while(inObject.indexOf("\\") > -1) {
					transStr += inObject.substring(0, inObject.indexOf("\"")) + "\\\\";
					inObject = inObject.substring(inObject.indexOf("\\") + 1);
				}
				transStr += inObject;
				inObject = transStr;
				transStr = "";
				while(inObject.indexOf("\"") > -1) {
					transStr += inObject.substring(0, inObject.indexOf("\"")) + "\\\"";
					inObject = inObject.substring(inObject.indexOf("\"") + 1);
				}
				transStr += inObject;
				outString += "\"" + transStr + "\"";
				break;
			case "boolean":
				outString += inObject;
				break;
			case "number": 
				outString += inObject;
				break;
			case "undefined":
				outString += "undefined";
				break;
			case "object":
				if(inObject.constructor == Array) outString += "[";
				else outString +="{";
				for(var key in inObject) {
					if(key != "parent") {
						try {
							if(inObject.hasOwnProperty(key) || inConfig.allProps) {
						  		var propType = typeof(inObject[key]); 
						  		if(inObject.constructor != Array)
						  			outString += key + ":";
								if(propType == "object") {							
									if(inConfig.recurse < inConfig.maxRecurse) {
										inConfig.recurse++;
										outString += jQuery.inspect(inObject[key], inConfig);
										inConfig.recurse--;
									} else {
										if(inObject[key] == null) outString += "null";
										else if(inObject[key].constructor == "Array") outString += "[]";
										else outString += "{}";
									}
								} else { 
									outString += jQuery.inspect(inObject[key], inConfig);
								}
								outString += ",";
							}
						} catch (e) {
							return (outString + " ERROR inspecting!: " + e.message);
						}
					}
				}
				if(inObject.constructor == Array) outString += "]";
				else outString += "}";
				break;
			default :
				outString += inObject.toString();
		}
		}
		
		return(outString);
	}
	
	/****
	* Serializes a javscript object with named attributes. Attribute names will be utilized in the url.  NOTE strings and numbers supported only!
	* @param object inObject			- required -
	* @param string inAddPrefix 		- optional - prefix the names in the returned url with this
	* @param object inFilter 			- optional - filter on this object, only the attribute names are important. values are ignored
	* @param boolean inFilterPositive 	- optional - is the filter positive (allowing only those attributes that exist in the filter to be included)
	*													or negative (any attribute that is in the filter will be excluded)
	*/
	jQuery.serializeObj = function(inObject, inAddPrefix, inFilter, inFilterPositive) {
		var urlString = "";
		var prefix = "";
		if(typeof(inAddPrefix) != "undefined") {
			prefix = inAddPrefix;
		}
		if(typeof(inFilter) == "undefined") {
			for(i in inObject) {
				if(inObject.hasOwnProperty(i))
					urlString += "&" + prefix + i + "=" + inObject[i];
			}
		} else if (typeof(inFilterPositive) == "boolean" && !inFilterPositive) {
			for(i in inObject) {
				if(inObject.hasOwnProperty(i))
					if(!inFilter.hasOwnProperty(i))
						urlString += "&" + prefix + i + "=" + inObject[i];
			}
		} else {
			for(i in inObject) {
				if(inObject.hasOwnProperty(i))
					if(inFilter.hasOwnProperty(i))
						urlString += "&" + prefix + i + "=" + inObject[i];
			}
		}
		return(urlString);
	}
	
	
	/****
	* Serialize a javascript object into a url list representation
	* @param object inList			- required - the object/list to be serialized
	* @param string inListName 		- required - the name to be used for the serialized list
	*/
	jQuery.serializeList = function(inList, inListName) {
		var urlString = "";
		var prefix = "";
		for(i in inList) {
			if(inList.hasOwnProperty(i))
				urlString += "&" + inListName + "[]=" + inList.i;
		}
		return(urlString);
	}
	
	
	///the snippet class!  required for templating functionality
	function Snippet(inString, inParent) {
		//defaults
		this.key = null;
		this.elements = [];
		this.tag = null;
		this.type = "";
		this.isSnippet = true;
		this.cycleInc = 0;
		this.cycleName = false;
		this.cycleValues = [];
		this.defaultVal = "";
		this.inputString = inString;
		this.parent = false;

		this.snippets 	= new Array();
		if(typeof(inParent) == "object" && inParent.isSnippet) 
			this.parent = inParent;

		//find my tag (always at the beginning)		
		var tagOpen = this.tagOpen; //regular expression
		var match = tagOpen.exec(inString);
		$.log(inString, "passed in");
		if(!this.parent) { //if I have no parent, I'm the root snippet and don't need to find my tag (because it's implied)
			this.tag = "root{}";
			this.type = this.tagType(this.tag);
		} else {
			this.tag = match[0].substring(1, match[0].length - 1); //get the clean tag with no whitespace or opening {
			this.type = this.tagType(this.tag);
			inString = inString.substring(match[0].length-1)
			$.log(inString, this.tag + "inString after removing match - 1 ");
			switch(this.type) {
			case "function" :
				this.parseConfig(inString.substring(1, inString.indexOf("}}")));
				inString = inString.substring(inString.indexOf("}}") + 2);
				$.log(inString.indexOf("}}"));
				$.log(inString, this.tag + " content (for parsing and the like) ")
				break;
			default :
				this.parseConfig(inString.substring(1, inString.indexOf("}")));
				inString = inString.substring(inString.indexOf("}") + 1);
			}
		}
		//$.log(inString, this.tag);
		
		//get the data element key  (to fill the template) 
		
		
		//if this is an object, array, or root snippet (an object) look through the 'inString' for child-snippets
		if(this.type == "object" || this.type == "array" || this.type == "function") {
			this.key =  this.tag.substring(0, this.tag.length - 2); //trim off the [] or {} 
			var tag = null;
			var tagSuffix = null;
			var tagType = null;
			var matchString = null;
			var working = inString;
			var key = null;
			var closeTag = null;
			var z = 0;
			
			while(true) {
				$.log("searching for new tags");
				match = tagOpen.exec(working);
				if(match == null) { break; }//this will break us out of the loop when there are no more matches				
				//$.log(match)
				this.elements.push(working.substring(0, match.index));
				working = working.substring(match.index);

				matchString = match[0];
				tag = matchString.substring(1, matchString.length -1);
				$.log("match found:" + matchString)
				//if this is an object or array snippet, we need to find the end tag and give it all the characters in-between
				tagType = this.tagType(tag);
				switch(tagType) {
				case "object" :
				case "array" :	
				case "function" :
					tagSuffix = tag.substring(tag.length - 2);
					key 	= tag.substring(0, tag.length - 2);
					
					closeTag = "{" + tagSuffix + key + "}";
					
					//var newMatchIndex = working.indexOf(matchString, match.index + matchString.length);
					var newMatchIndex = working.indexOf(matchString, matchString.length);
					var matchCloseIndex = working.indexOf(closeTag);
					if(matchCloseIndex == -1) alert("error, no close for " + this.tagType(tag) + " " + tag);
					//$.log(working, "open index:" + newMatchIndex + " close index:" + matchCloseIndex);
					//because there may be nested tags
					var i = 0; //for debugging the templates
					while(newMatchIndex < matchCloseIndex && newMatchIndex > -1) {
						newMatchIndex = working.indexOf(matchString, newMatchIndex + matchString.length);
						matchCloseIndex = working.indexOf(closeTag, matchCloseIndex + closeTag.length);
						if(matchCloseIndex == -1 && newMatchIndex == -1) alert("error, no close for " + this.tagType(tag) + " " + tag + " iteration:" + i);
						//$.log(working, "open index:" + newMatchIndex + " close index:" + matchCloseIndex);
						i++;
					}
					//we've found the closing tag for our new object or array Snippet now create it 
					$.log("", "new tag:" + key);
					var snippet = new Snippet(working.substring(0, matchCloseIndex), this);
					//snippet.pre = working.substring(0, match.index);
					this.elements.push(snippet);
					working = working.substring(matchCloseIndex + closeTag.length);
					break;
				default : //if tag is a value
					matchCloseIndex = working.indexOf("}");					
					var snippetString = working.substring(0, matchCloseIndex + 1);
					var snippet = new Snippet(snippetString, this);
					//snippet.pre = working.substring(0, match.index);
					this.elements.push(snippet);
					working = working.substring(matchCloseIndex + 1);
				}				
				z++;
			}
			this.elements.push(working);
		} else {
			this.key = this.tag;
		}
		return(this);
	}
	
	//PROTOTYPE VARIABLES
	Snippet.prototype.tagOpen = new RegExp("\{([a-z]|[A-Z]|_)+(\.([a-z]|[A-Z])+)*((\{\})|(\\[\\]|\\(\\)))*( |\})");
	Snippet.prototype.tagOpenCloseBrace = /(^|[^\\])}/;  //not yet used

	
	//PROTOTYPE FUNCTIONS
	Snippet.prototype.debug = function(inMessage) {
		if(typeof(console) != "undefined") {
			if(typeof(log) != "undefined") {
			}
		} else {
				alert(inMessage);
		}
			
		if(inMessage == null) {
			console.log("null");
		} else {
			if(typeof(inMessage) == "object") {
				if(typeof($) != "undefined" && typeof($.inspect) == "function") {
					console.log($.inspect(inMessage));
				} else {
					console.log(inMessage);
				}
			} else if (typeof(inMessage) == "function") {
				console.log(inMessage.toString());
			} else { 
				console.log(inMessage);
			}
		}
	}
	
	
	/**
	* assumes tag has tag delimiters and any attributes removed
	*/
	Snippet.prototype.tagType = function(inTag) {
		if(inTag.substring((inTag.length - 2)) == "[]") {
			return("array");
		}
		else if(inTag.substring((inTag.length - 2)) == "{}") {
			return("object");
		}
		else if(inTag.substring((inTag.length - 2)) == "()") {
			return("function");
		}
		return("value");
	}
	
	
	Snippet.prototype.fill = function(obj) {
		var out = "";
		var snippet = null;
		var count = 1;
		var item = null;
		var temp = "";
		this.obj = obj;
		if(typeof(obj) == "undefined" || obj == null) {
			obj = this.getDefaultValue();
		} 
		switch (this.type) {
		case "value" :
			out += obj;
			break;
		case "function" :
			var myVal = this.myFunction(obj);
			$.log();
			if(typeof(myVal) == "undefined" || !myVal) break;
			if(typeof(myVal) == "string") return(myVal);
			//else fill the sub-templates
		case "object" :
			out += this.fillSnippets(obj);
			break;
		case "array" :				
			if(typeof(obj.length) == "undefined") {
				if(typeof(obj) == "object") {
					this.cycleInc = 0;
					for(var j in obj) {
						if(obj.hasOwnProperty(j)) {
							if(this.cycleInc >= this.cycleValues.length) this.cycleInc = 0;
							if(typeof(obj[j]) == "string" || typeof(obj[j]) == "boolean" || typeof(obj[j]) == "number") {
								out += this.fillSnippets({"val":obj[j]});
							} else {
								out += this.fillSnippets(obj[j]);
							}
							this.cycleInc++;
						}
					}
				} else {
					return(out + this.inner);
				}
			} else {
				this.cycleInc = 0;
				for(var j = 0; j < obj.length; j++) {
					if(this.cycleInc >= this.cycleValues.length) this.cycleInc = 0;
					if(typeof(obj[j]) == "string" || typeof(obj[j]) == "boolean" || typeof(obj[j]) == "number") {
						out += this.fillSnippets({"val":obj[j]});
					} else {
						out += this.fillSnippets(obj[j]);
					}
					this.cycleInc++;
				}
			}
		}		
		//}
		this.obj = null;
		delete(this.obj);
		return(out);
	}
	
	
	Snippet.prototype.fillSnippets = function(obj) {
		var out = "";
		var snippet = null;
		for(var i = 0; i < this.elements.length; i++) {
			snippet = this.elements[i];
			if(typeof(snippet) == "string") {
				out += snippet;
			} else {
				if(this.cycleName && this.cycleName == snippet.tag) {
					out += snippet.fill(this.cycleValues[this.cycleInc]);
				} else {
					if(snippet.type == "function") out += snippet.fill(obj); //we do this because functions operate in the root namespace
					else out += snippet.fill(obj[snippet.key]);
				}
			}
		}
		return(out);
	}
	
	
	Snippet.prototype.getDefaultValue = function() {
		if(this.defaultVal.length == 0 && this.parent) {
			return(this.parent.getObjValue(this.key));
		} else {
			return(this.defaultVal);
		}
	}
	
	
	Snippet.prototype.getObjValue = function(inKey) {
		if(typeof(this.obj) != "undefined") {
			if(this.obj.hasOwnProperty(inKey)) {
				return(this.obj[inKey]);
			} else {
				if(this.parent) {
				 	return(this.parent.getObjValue(inKey));
				}
			}
		}
		return("");
	}
	
	
	Snippet.prototype.parseConfig = function(inString) {
		$.log("'" + inString + "'", this.tag + " is a " + this.type);
		if(this.type == "function") {
			inString = inString.substring(1);
			 eval("this.myFunction = function(obj) {" + inString + "}");
			$.log("'" + inString + "'", this.tag + " is a " + this.type);
		} else {
			if(inString.indexOf("default=\"") == 0) {
				inString = inString.substring(9);
				this.defaultVal = inString.substring(0, inString.indexOf("\""));
				inString = inString.substring(inString.indexOf("\"") + 1);
			}
			if(inString.indexOf("cycleName=") == 0) {
				inString = inString.substring(10);
				this.cycleName = inString.substring(0, inString.indexOf(" "));
				inString = inString.substring(inString.indexOf(" ") + 1);
				this.cycleValues = inString.split("|");
			}
		}
	}
	
	
	/**
	*	Key event evaluation object (perform javascript on a keystroke event)
	*
	*	NOTE: binding the processKey event using jQuery or in areas where scope/context(namespace) may be ambiguous binding needs to be wrapped in a function
	*	example: $("#groups_display_label").keyup( function(e) {keyWatcher.processKey(e); } ); 
	*
	* @param inConfig	-required-	attribues //all are optional 
	* 									- keycodes -
	*									.27, 
	*									.13, etc = (function) || (string) 
	*										//the integer number  of a Character keyCode.  Add as many as you want
	*										Each of these can be either a string to be evaluated - eval(string), or a function to be called.  
	*										functions will be passed 2 arguments:
	*											1. The event that triggered the call
	*											2. The listener object (where config options including htmlID will be available off of .config[optName]
	*												duplicating this for an eval string would be the string 'somefunc(e, this)'
	*										
	*									.defaultAction = (function)||(string) 
	*										//the action that is performed when any valid key is pressed (see keyCodes above) 
	*										(see .regEx for a definition of a 'valid key')
	*										
	* 									
	* 									.regEx = (/regEx/)
	* 										a regular expression that will match on a keystroke and will process the default action
	*										if this argument is not supplied KeyListener.isKeyDefaultValid() will be used to determine if the key is to be processed
	*
	*									.invalidAction = (function)||(string)
	* 										//the action that is performed when any INVALID key is pressed (see keyCodes above) 
	*										if absent the listener will attempte to swallow the keystroke.
	*
	*									.defaultActionDelay = (integer) 
	* 										//the amount of time to delay the default action. if another key code is processed 
	*										before the time expires, the time is reset
	*										
	*									.actionDelay = (integer) 
	* 										//the amount of time to delay all actions but the default action. if another key code is processed before the time expires, the time is reset
	*										
	*
	* 									.htmlID (string)
	*  										The unique DOM id of the INPUT element to be matched.  
	*  
	* Additional information
	* 									If you want 'no action' performed on some keystrokes when a default action is specified, simply assign and
	*									empty string to that keycode, the same for the .invalidAction argument
	*									inConfig = {'defaultAction':'myfunction()', 27:"$('#somefield').val('')", 9:''} 
	*									
	*/
	function KeyListener(inConfig){
		this.config = {"defaultActionDelay":0, "actionDelay":0, "defaultAction":""};
		this.keyActions = {};
		var regEx = /^[0-9]*$/;
		for(var i in inConfig) {
			if(inConfig.hasOwnProperty(i)) {
				if(regEx.test(i)) 
					this.keyActions[i] = inConfig[i];
				else 
					this.config[i] = inConfig[i];
			}
		}
		
		this.validationMode = "default";
		this.delayedAction = null;
		
		if(typeof(inConfig.regEx) != "undefined") {
			this.validationMode = "regEx";
		}
	}
	
	KeyListener.prototype.executeAction = function(e, inAction) {
		switch(typeof(inAction)) {
		case "string" :
			eval(inAction);
			break;
		case "function" :
			inAction(e, this); // this is supposed to be the key listener instance
			break;
		}
	}
	
	/**
	* Processes the keyboard input key value passed to it.
	*
	* @param	Key Event	e			Key event we are going to process
	**/
	KeyListener.prototype.processKey = function( e ){
		//$.log(e, "keyListener", true);
		var listener = this;
		var valid = false;	
		if (e.preventDefault) e.preventDefault();
		if (e.stopPropagation) e.stopPropagation();

		var actionDelay = 0;
		var keyAction   = "";

		clearTimeout(this.delayedAction);
		if(typeof(this.keyActions[e.keyCode]) != "undefined") {
			keyAction = this.keyActions[e.keyCode];
			actionDelay = this.config.actionDelay;
		} else {
			switch(this.validationMode) {
			case "regEx" :
				var inputString = document.getElementById(this.config.htmlID).value
				valid = this.config.regEx.test(inputString);
				break;	
			default :			
				valid = this.isKeyDefaultValid(e.which);
			}
			if( valid ){
				keyAction = this.config.defaultAction;
				actionDelay = this.config.defaultActionDelay;
			} else if(typeof(this.config.invalidAction) != "undefined") {
				if(this.config.invalidAction == "swallowKey") {
					keyAction = function() { listener.swallowKey();}				
				} else {
					keyAction = this.config.defaultAction;
				}
			}				
		}
		this.delayedAction = setTimeout(function() {listener.executeAction(e, keyAction);}, actionDelay);
	}
	
	KeyListener.prototype.isKeyDefaultValid = function(key) {
		$.log(key);
		if( key < 13 || key == 16 || key == 27 ||  
		    ( key > 45 && key < 91 ) || 
			( key > 94 && key < 106 ) || 
			( key > 107 && key < 111 ) || 
			( key > 188 && key < 191) 
			){
				return(true);
		}
		return(false);
	}
	
	KeyListener.prototype.swallowKey = function() {
		if(this.config.hasOwnProperty("htmlID")) {
			var str = document.getElementById(this.config.htmlID).value;
			document.getElementById(this.config.htmlID).value = str.substring(0, str.length-1);
			delete(str);
		}
	}
	
	
	
	
	
	
	
	
	
	
	
	
	
	