/* * * * * * * * * * * * * * * * * * * * * * * * * *

    PURE Unobtrusive Rendering Engine for HTML

    Licensed under the MIT licenses.
    More information at: http://www.opensource.org

    Copyright (c) 2008 Michael Cvilic - BeeBole.com

    revision: 1.28

* * * * * * * * * * * * * * * * * * * * * * * * * */
var $p, pure;
$p = pure = {
	find: function(selector, context){
		try{
			return (context||document).querySelector( selector );}
		catch(e){
			this.msg('library_needed');};},
				
	getRuntime: function(){
		//build the runtime to be exported as a JS file
		var src = ['var $p, pure;$p = pure = {', '$outAtt:', this.$outAtt.toString(), ',', '$c:', this.$c.toString(), ',', 'render:', this.render.toString(), ',', 'compiledFunctions:[], msg:'+this.msg.toString()+'};'];
		for (var fName in this.compiledFunctions){
			if(this.compiledFunctions.hasOwnProperty(fName)){
			var htmlFunction = '$p.compiledFunctions[\'' + fName + '\']';
			src.push(htmlFunction+'={};'+htmlFunction+'.compiled=');
			src.push(this.compiledFunctions[fName].compiled.toString()+';');
			for (var fi in this.compiledFunctions[fName]){
				if(fi != 'compiled'){
					src.push('$p.compiledFunctions[\''+fName+'\'].'+fi+'='+this.compiledFunctions[fName][fi].toString()+';');}}}}
	var elm = document.getElementById('pureMsg');
	if (elm) {
		elm.value = src.join('');
		elm.select();}
	else{
		this.msg('place_runtime_container');}},

	$f:{cnt:0},

	$c:function(context, path, nullMode){
		if(path == 'context'){return context;}
		if(typeof context == 'object'){
			//context is a JSON
			var aPath = path.split(/\./);
			var value = context[aPath[0]];

			for (var i=1; i<aPath.length; i++){
				if (!value){ break;}
				value = value[aPath[i]];}}
			if (!value && value!=0) {value = nullMode ? null :'';}
		return value;},

	render: function(/*html, context, directives || context, compiledName, directives*/){
		var fn, html, context, directives = arguments[2];
		if (typeof arguments[1] === 'string'){//a compiled HTML is passed
			html = arguments[1];
			context = arguments[0];}
		else{
			html = arguments[0];
			context = arguments[1];}
		if (typeof html != 'string'){
			var mapped = directives ? this.map(directives, html):html.cloneNode(true);
			fn = this.compiledFunctions.length || 0;
			this.compile(mapped, fn, context, false);}
		else{ // call to an already compiled f()
			fn = html;}
		if (this.compiledFunctions[fn]){
			return this.compiledFunctions[fn].compiled(context);} //transform and return an html string
		else{
			this.msg('HTML_does_not_exist', fn);}},

	autoRender:function(html, context, directives){
		if (typeof html != 'string') {
			if (!html) { this.msg('wrong_html_source'); return false;}
			html.setAttribute(this.utils.AUTO, 'true');}
		return this.render(html, context, directives);},

	compiledFunctions:{},

	$outAtt:function(content){
			var att = content.join('');
			return (/\=\"\"/.test(att)) ? '' : att;},
 	utils:{
 		CLASSNAME:/MSIE\s+(6|7)/.test(navigator.userAgent)? 'className':'class',
		NS:/MSIE/.test(navigator.userAgent) ? 'pure_':'pure:',
		PURECLASS:/MSIE/.test(navigator.userAgent) ? 'pure_class':'pure:class',
		AUTO:/MSIE/.test(navigator.userAgent) ? 'pure_autoRender':'pure:autoRender',
		REPEAT:/MSIE/.test(navigator.userAgent) ? 'pure_repeat':'pure:repeat',
		NODEVALUE:/MSIE/.test(navigator.userAgent) ? 'pure_nodeValue':'pure:nodeValue',
		nodeValues:[],
		repeats:[],
		autoRenderAtts:[],
		isTypeOfArray:function(obj){
            return typeof obj.length === 'number' && !(obj.propertyIsEnumerable('length')) && typeof obj.splice === 'function';},
		autoMap: function(n, autoRender, context, openArray){
			var toMap, k, j, att, repeatPrefix, prop, attValue, ap;
			if (autoRender == 'true') {
				attValue = n.getAttribute(this.CLASSNAME);
				if (attValue) {
					toMap = attValue.replace(/^\d|\s\d/g,'').split(/\s+/);//remove numeric classes as they mess up the array reference
					for (j = 0; j < toMap.length; j++) {
						repeatPrefix = '';
						ap = this.ap_check(toMap[j]);
						att = ap.clean.split(/@/);
						if(openArray.length > 0) {
							for (k = openArray.length-1; k>=0; k--) {
								prop = openArray[k] == 'context' ? context[0][att[0]] : $p.$c(context[openArray[k]][0], att[0], true);
								if (prop || prop == 0) {//found a repetition field, break, specific case when 0 is returned as a value
									repeatPrefix = openArray[k];
									break;}}}

						if(!prop && prop != 0){
							prop = att[0] != 'context' ? $p.$c(context, att[0], true) : !(/context/).test(openArray.join('')) ? context: true;}
							
						if (prop || prop==0) {
							if (typeof prop.length === 'number' && !(prop.propertyIsEnumerable('length')) && typeof prop.splice === 'function') { //Douglas Crockford check if array
								openArray.push(att[0]);
								n.setAttribute(this.REPEAT, att[0] + '<-' + att[0]);}
							else {
								if(repeatPrefix !== ''){ 
									att[0] = repeatPrefix + '[\'' + att[0] + '\']';}
								if(!att[1]){ //not an attribute
									att.push('nodeValue');}
								if(ap.type){ //append or prepend ?
									att[0] = this.ap_format(att[0], att[1], n, ap.type);}
								if (att[1]!='nodeValue'){ // remove the existing attribute if any
									this.removeAtt(n, att[1]);}
								if (!n.getAttribute(this.NS + att[1])) { //don't overwrite a directive if any
									n.setAttribute(this.NS + att[1], att[0]);}}}}
					if (n.getAttribute(this.PURECLASS) && n.getAttribute(this.CLASSNAME)){
						n.removeAttribute(this.CLASSNAME);}}}

			//flag the nodeValue and repeat attributes
			var isNodeValue = n.getAttribute(this.NODEVALUE);
			if (isNodeValue) {this.nodeValues.push(n);}
			var isRepeat = n.getAttribute(this.REPEAT);
			if (isRepeat) {this.repeats.push(n);}},

		nodeWalk:function(node, context){
			var auto = this.AUTO;
			this.repeats = []; this.nodeValues = [];
			var autoRender = node.getAttribute(auto);
			node.removeAttribute(auto);
			var openArray=[];
			//memory safe non-recursive tree traverse
			var c = node, n = null;
			do {
				if (c.nodeType == 1) {
					this.autoMap(c, autoRender, context, openArray);}
				n = c.firstChild;
				if (n === null) {
					n = c.nextSibling;}
				var tmp = c;
				if (n === null) {
					tmp = c;
					do {
						n = tmp.parentNode ? tmp.parentNode:node;
						if (n == node) {break;}
						tmp = n;
						n = n.nextSibling;}
					while (n === null);}
				c = n;}
			while (c != node);
			//post process the repeat and nodeValue for easier compiling
			var replaced, replacer, replacedSrc, nodeValueSrc, str = false;
			for (var j = this.nodeValues.length-1; j >= 0; j--) {
				try {
					n = this.nodeValues[j];
					nodeValueSrc = n.getAttribute(this.NODEVALUE); // put the node value in place
					if (nodeValueSrc) {
						var ap = nodeValueSrc.match(/\|(a|p)\|/);
						if (ap) {
							if (ap[1] == 'a'){
								n.innerHTML += this.NODEVALUE + '="' + nodeValueSrc.substring(ap.index+3) + '"';}
							else{
								n.innerHTML = this.NODEVALUE + '="' + nodeValueSrc.substring(ap.index+3) + '"' + n.innerHTML;}}
						else{
							n.innerHTML = this.NODEVALUE + '="' + nodeValueSrc + '"';}
						
						n.removeAttribute(this.NODEVALUE);}} 
				catch (e) {}}
			for(var i=this.repeats.length-1; i>=0;i--){
				n = this.repeats[i];//go inside out of the tree
				try {
					replacedSrc = n.getAttribute(this.REPEAT); //wrap in tags for easy string find
					if (replacedSrc) {
						replaced = n.cloneNode(true);
						replaced.removeAttribute(this.REPEAT);
						replacer = document.createElement(this.REPEAT);
						replacer.appendChild(replaced);
						replacer.setAttribute('source', "" + replacedSrc);
						if(node == n){
							str = this.outerHTML(replacer);}
						else{
							n.parentNode.replaceChild(replacer, n);}}}
				catch (e2) {}}
				return (str) ? str : false;},
				
		ap_format: function(attValue, attName, node, ap){
			if (ap){
				if (!attName) {attName = 'nodeValue';}
				var fixAtt = attName == 'class' ? this.CLASSNAME : attName;
				var original = node.getAttribute(fixAtt) || ('nodeValue' == attName ? 'nodeValue' : null);
				if (original){
					return original + '|' + ap + '|' + attValue;}}
				return attValue;},
							
		ap_check: function(str){
			var prepend, append;
			str = (prepend = /^\+/.test(str)) ? str.slice(1) : (append = /\+$/.test(str)) ? str.slice(0, -1) : str;
			return {type:(append) ? 'a' : (prepend) ? 'p' : false, clean:str};},

		removeAtt:function(node, att){
			if (att == 'class') {att = this.CLASSNAME;}
			try{ 
				node[att] = ''; 
				node.removeAttribute(att);
			}catch(e){}},

		out:function(content){ return ['output.push(', content, ');'].join('');},
		strOut:function (content){ return ['output.push(', "'", content, "');"].join('');},
		outputFn:function (attValue, currentLoop){
			if (currentLoop){
				return attValue + '({context:context, items:' + currentLoop + ',pos:'+currentLoop+'Index==\'0\'?0:parseInt(' + currentLoop + 'Index)||'+currentLoop+'Index, item:' + currentLoop + '['+currentLoop+'Index==\'0\'?0:parseInt(' + currentLoop + 'Index)||'+currentLoop+'Index]})';}
			else{
				return attValue + '({context:context})';}},
		contextOut:function(path){ return '$p.$c(context, ' + path + ')';},

		isArray:function (attValue, openArrays){ //check if it is an array reference either [] or an open loop
			var arrIndex = /\[[^\]]*\]/.test(attValue);
			var objProp  = attValue.replace(/(")|(')/g,'').split(/\./);
			return arrIndex || openArrays[objProp[0]] ? true: false;},

		arrayName:function(pName){
			var name=pName.match(/\w*/)[0] || ''; 
			var subIndex= pName.substring(name.length).replace(/\[\s*\]/,''); // take the tail and replace [ ] by ''
			if(/\./.test(subIndex)){
				subIndex = subIndex.replace(/^\./, '[\'').replace(/\./g,'\'][\'') + '\']';}
			return name + '[' + name + 'Index]' + subIndex.replace(/\\\'/g,"'");},
		domCleaningRules:[
			{what:window ? new RegExp(window.location.toString().substring(0, window.location.toString().indexOf(window.location.pathname)), 'g'):'', by:''},//put all absolute links( img.src ) of window.location relative to the root
			{what:/\>\s+</g, by:'> <'}, //remove multiple spaces between >..< (IE 6) 
			{what:/\r|\n/g, by:''},//may be too strong check with pre, textarea,...
			{what:/\\\'|\'/g, by:'\\\''}, //escape apostrophe
			{what:/\s+[^\=]+\=\"\"(?=[^\>]|\>)/ig, by:''}, //IE does not remove some attr, ticket #20
			{what:/^\s+/, by:''}],//clean leading white spaces in the html
		outerHTML:function(elm){
			return elm.outerHTML || (function(elm){
				var div = document.createElement('div');
				div.appendChild(elm);
				return div.innerHTML;})(elm);},
		html2str:function(html, context){
			var clone = html[0] && !html.nodeType ? html[0].cloneNode(true) : html.cloneNode(true);
			//node manipulation before conversion to string
			var str = this.nodeWalk(clone, context);
			//convert the HTML to a string
			if(!str) {str = this.outerHTML( clone );}
			//avoid shifting lines remove the > and </ around pure:repeat tags
			str = str.replace(new RegExp('<\/?:?'+this.REPEAT, 'gi'), this.REPEAT);// :? -> from bug in IE
			//clean the dom string, based on rules in $p.domCleaningRules
			var rules = this.domCleaningRules;
			for(var i=0;i<rules.length;i++){
				str = str.replace(rules[i].what||'' ,rules[i].by);}
			return str.split(this.NS);}},

	autoCompile:function(html, fName, context, noEval){
		html.setAttribute(this.utils.AUTO, 'true');
		return this.compile(html, fName, context, noEval);},

	compile: function(html, fName, context, noEval){
		var aStr = this.utils.html2str(html, context);
				
		if(!fName && typeof fName != 'number'){
			this.msg( 'no_HTML_name_set_for_parsing', aStr.join(''), html);
			return false;}

		//start the js generation
		var js, wrkStr, rTag = false, rSrc, openArrays=[], cnt=1, subSrc='', fnId, attOut, spc, suffix, currentLoop, isNodeValue, max, curr, key, offset, attName = '', attValue = '', attValues=[], arrSrc, fullAtt;

		this.compiledFunctions[fName]={}; //clean the fct place if any
		var aJS = ['{var output = [];'];

		if(aStr[0]!=="") {aJS.push(this.utils.strOut(aStr[0]));}
		for(var j = 1;j < aStr.length; j++){
			wrkStr = aStr[j];
			if (/^repeat[^\>]*\>/i.test(wrkStr)){
				rTag = wrkStr.match(/^repeat[^\>]*>/i);
				rSrc = rTag[0].match(/"[^"]*"/);
				if (rSrc){ //start a loop
					rSrc = rSrc[0].replace(/&lt;/,'<').replace(/"/g,'').replace(/\s/g,'');
					subSrc = rSrc.split(/<-/);
					currentLoop = subSrc[0];
					arrSrc = subSrc[1] || '';
					if ( this.utils.isArray(arrSrc, openArrays) ){
						//reference to an open array
						aJS.push('var ' + currentLoop + '=' + this.utils.arrayName(arrSrc) + ';');}
					else{
						if (/context/i.test(arrSrc) || arrSrc.length == 0) {
							if (!(/context/i).test(currentLoop)){ // avoid var context = context 
								aJS.push('var ' + currentLoop + '= context;');}}
						else{ 
							aJS.push('var ' + currentLoop + '= $p.$c(context, "' + arrSrc + '");');}}
					aJS.push('for(var '+currentLoop+'Index in '+currentLoop+'){if ('+currentLoop+'.hasOwnProperty('+currentLoop+'Index)){'); 		
					aJS.push(this.utils.strOut(wrkStr.substring(rTag[0].length)));
					openArrays[currentLoop] = cnt++;}
			
				else{ //end of loop;
					aJS.push('}}');
					delete openArrays[currentLoop];
					max = 0;
					for (key in openArrays){
						if(openArrays.hasOwnProperty(key)){
						curr = openArrays[key];
						if( curr > max){
						max = curr;
						currentLoop = key;}}}
					aJS.push(this.utils.strOut(wrkStr.substring(rTag[0].length, wrkStr.length)));}

				rTag = false;
				continue;}
			else{
				attName = wrkStr.substring(0, wrkStr.indexOf('='));
				attValue = wrkStr.match(/\=""?[^"]*""?/)[0].substr(2).replace(/"$/,'');
				offset = attName.length + attValue.length + 3;
				if (/&quot;/.test(attValue)) {
					attValue = attValue.replace(/&quot;/g, '"');
					wrkStr = wrkStr.replace(/&quot;/, '"').replace(/&quot;/, '"');}

				isNodeValue = /^nodeValue/i.test(wrkStr);	
				fullAtt = isNodeValue ? []: ['\''+attName+'="\''];

				attOut = attValue.match(/\|(a|p)\|/);
				suffix = ''; 
				spc = attName !== 'class'  ? '':' '; //at some point we should use 'tag[class]+':' #{prop}' instead and deprecate the auto space for class
				if (attOut) {
					if(attOut[1] =='a'){
						fullAtt.push('\''+attValue.substring(0, attOut.index)+spc+'\'');}
					else{ // |p|
						suffix = attValue.substring(0, attOut.index);}
					attValue = attValue.substring(attOut.index + 3);}

				if(/\$f\[(f[0-9]+)\]/.test(attValue)){ //function reference
					fnId = attValue.match(/\[(f[0-9]+)/)[1];
					this.compiledFunctions[fName]['$'+fnId]=this.$f[fnId];
					delete this.$f[fnId];this.$f.cnt--;
					fullAtt.push(this.utils.outputFn('this.$'+fnId, currentLoop));
					if(suffix !== '') {fullAtt.push('\''+spc+suffix+'\'');}}
				else if(/^\\\'|&quot;/.test(attValue)){ //a string, strip the quotes
					fullAtt.push('\''+ attValue.replace(/^\\\'|\\\'$/g,'')+'\'');
					if(suffix !== '') {fullAtt.push('\''+spc+suffix+'\'');}}
				else{
					if (!(/MSIE/).test(navigator.userAgent)) {
						attValues = attValue.split(/(#\{[^\}]*\})/g);}
					else { //IE:(
						var ie = attValue.match(/#\{[^\}]*\}/);
						attValues = ie ? [] : [attValue];
						while (ie) {
							if (ie.index > 0) {attValues.push(attValue.substring(0, ie.index));}
							attValues.push(ie[0]);
							attValue = attValue.substring(ie.lastIndex);
							ie = attValue.match(/#\{[^\}]*\}/);
							if (!ie && attValue !== '') {attValues.push(attValue);}}}

					for(var atts = 0; atts<attValues.length; atts++){
						attValue = attValues[atts];
						if(/\#\{/.test(attValue) || attValues.length == 1){
							attValue = attValue.replace(/^\#\{/, '').replace(/\}$/,'');
							if(this.utils.isArray(attValue, openArrays)){ //iteration reference
								fullAtt.push(this.utils.arrayName(attValue));}
							else{ //context data
								fullAtt.push(this.utils.contextOut("'"+attValue+"'"));}}
						else if(attValue !== ''){
							fullAtt.push('\''+attValue+'\'');}
	
						if(suffix !== ''){ fullAtt.push('\''+spc+suffix+'\'');}}}

				if (!isNodeValue) { //close the attribute string
					fullAtt.push('\'"\'');}}
				aJS.push(this.utils.out(fullAtt.length > 1 ? '$p.$outAtt(['+fullAtt.join(',')+'])':fullAtt[0]));
				
			//output the remaining if any	
			wrkStr = wrkStr.substr(offset);
			if(wrkStr !== '') {aJS.push(this.utils.strOut(wrkStr));}}
		aJS.push( 'return output.join("");}' );
		js = aJS.join('');
		if(!noEval){
			try{
				this.compiledFunctions[fName].compiled = new Function('context', js);} 
			catch (e){
				this.msg('parsing_error', [e.message, js]);
				return false;}}
		return js;},

	map:function(directives, html, noClone){
		// a directive is a tuple{ dom selector, value }
		// returns the html with the directives as pure:<attr>="..."
		if(!html[0] && html.length == 0){
			this.msg('no_HTML_selected');
			return false;}

		var fnId, multipleDir=[], currentDir, clone, ap,isAttr, target, attName, repetition, parentName, selector, i, autoRender, classToDelete=[];
		if (noClone){
			clone = html[0] && !html.nodeType ? html[0] : html;}
		else{
			clone = html[0] && !html.nodeType ? html[0].cloneNode(true) : html.cloneNode(true);}
			
		autoRender = clone.getAttribute(this.utils.AUTO)||false;
		for (selector in directives){ // for each directive set the corresponding pure:<attr>
			if(directives.hasOwnProperty(selector)){
				currentDir = directives[selector];
				if(this.utils.isTypeOfArray(currentDir)){//check if an array of directives is provided
					multipleDir = currentDir;}
				else{
					multipleDir = []; 
					multipleDir.push(currentDir);}
				for(i = 0; i<multipleDir.length;i++){
					currentDir = multipleDir[i];
					ap = this.utils.ap_check(selector);
					selector = ap.clean;
					isAttr = selector.match(/\[[^\]]*\]/); // match a [...]
					if(/^\[|^\.$/.test(selector)){ //attribute of the selected node or itself . (dot)
						target = clone;}
					else{
						target = this.find(selector, clone);
						if (!target && isAttr){
							//if the attribute does not exist yet, select its containing element
							target = this.find(selector.substr(0, isAttr.index), clone);}}

					if ( target ){  //target found
						if (typeof currentDir == 'function'){
							fnId = 'f'+this.$f.cnt++;
							this.$f[fnId] = currentDir;
							currentDir = '$f['+fnId+']';}

						attName = 'nodeValue'; //default
						repetition = -1;
						if (isAttr){
							//the directive points to an attribute
							attName = selector.substring(isAttr.index+1,isAttr[0].length+isAttr.index-1);
						if(attName.indexOf(this.utils.NS) > -1){
							attName = attName.substring(this.utils.NS.length);}}
						else{
							//check if the directive is a repetition
							repetition = currentDir.search(/w*<-w*/);
							if(repetition > -1) {attName = 'repeat';}}

						currentDir = currentDir.replace(/^"|"$|\'|\\\'/g, '\\\''); //escape any quotes by \'
						currentDir = this.utils.ap_format(currentDir, attName, target, ap.type);
						target.setAttribute( this.utils.NS + attName, currentDir);

						if(isAttr){
							if (attName != 'class'){ 
								this.utils.removeAtt(target, attName);}
							else if (autoRender != 'true'){ 
							  		classToDelete.push(target);}}}

					else{ // target not found
						parentName = [clone.nodeName];
						if(clone.id !== '') {parentName.push('#' + clone.id);}
						if(clone.className !== '') {parentName.push('#' + clone.className);}
						this.msg( 'element_to_map_not_found', [selector, parentName.join('')], clone);}}}}
		if (classToDelete.length>0){ //remove class attribute only at the end to allow .selector to work regardless of the order of directives
			for (i=0;i<classToDelete.length;i++){
				this.utils.removeAtt(classToDelete[i], 'class');}}
		return clone;},

	messages:{
		'wrong_html_source':'The source HTML provided to autoRender does not exist. Check your selector syntax.',
		'element_to_map_not_found':"PURE - Cannot find the element \"&\" in \"&\"",
		'place_runtime_container':'To collect the PURE runtime, place a <textarea id=\"pureMsg\"></textarea> somewhere in your document.',
		'no_HTML_selected':'The map function didn\'t receive a valid HTML element',
		'no_HTML_name_set_for_parsing':'A name is needed when parsing the HTML: &',
		'HTML_does_not_exist':'The HTML: & does not exist or is not yet compiled',
		'library_needed':'In order to run PURE, you need a JS library such as: dojo, domAssistant, jQuery, mootools, prototype,...',
		'parsing_error':'Parsing error: \"&\" in: &'},

	msg:function(msgId, msgParams, where){
		// find the msg in local labels repository or in this.messages
		var msg = this.messages[msgId] || msgId;
		var re = /&/, i;
		if(msg != msgId && msgParams){
			if (typeof msgParams == 'string'){
				msg = msg.replace(re, msgParams);}
			else{
				for(i=0; i<msgParams.length;i++ ){
					msg = msg.replace(re, msgParams[i]);}}}

		var elm = document.getElementById('pureMsg');
		if(elm){
			elm.innerHTML = [msg, '\n', elm.innerHTML].join('');}
			else{ alert(msg);}},
	libs:{
		mapDirective:function(elm, directives){
			return $p.map(directives, elm);},

		compile:function(elm, fName, directives, context){
			var html = elm;
			if(directives) {html = $p.map( directives, elm);}
			if(context) {html.setAttribute($p.utils.AUTO, 'true');}
			return $p.compile(html, fName, context||false, false);},//return the compiled JS

		render:function(elm, context, directives, html, auto){
			var source = elm;
			if(typeof html !== 'undefined'){
				source = typeof html !== 'string' && html[0] || html;} //either a lib object or a node or a template name
			else if(typeof directives !== 'undefined' && (directives.jquery || directives.cssSelect || directives.nodeType || typeof directives=== 'string')){
				//the directive is the template 
				source = (directives.jquery || directives.cssSelect) ? directives[0]:directives;
				directives = null;}
			return this.replaceWithAndReturnNew(elm, auto === true ? $p.autoRender(source, context, directives):$p.render(source, context, directives));},

		replaceWithAndReturnNew: function(elm, html){
			var div = document.createElement('div');
			var replaced = elm;
			var parent = replaced.parentNode;
			parent.insertBefore(div, replaced);//avoid IE mem leak, place it before filling
			div.innerHTML = html;
			var replacers = div.childNodes;
			var newThis = [];
			for (var i = replacers.length - 1; i >= 0; i--) {
				newThis.push(replaced.parentNode.insertBefore(replacers[i], replaced.nextSibling));}
			parent.removeChild(replaced);
			parent.removeChild(div);
			return newThis.length > 1 ? newThis:newThis[0];}}};

if(typeof jQuery !== 'undefined' && $ == jQuery){ 
	//patch jQuery to read namespaced attributes see Ticket #3023
	if(jQuery.parse) {jQuery.parse[0] = /^(\[) *@?([\w:\-]+) *([!*$\^~=]*) *('?"?)(.*?)\4 *\]/;}
	$p.utils.domCleaningRules.push({ what: /\s?jQuery[^\s]+\=\"null\"/gi, by: ''});
	if (typeof document.querySelector === 'undefined') {$p.find = function(selector, context){
		var found = jQuery.find(selector, context);
		return found[0] || false;};}
	// jQuery chaining functions
	jQuery.fn.mapDirective = function(directives){
		return jQuery($p.libs.mapDirective(this[0], directives));};
	jQuery.fn.compile = function(fName, directives, context){
		$p.libs.compile(this[0], fName, directives, context);
		return this;};
	jQuery.fn.render = function(context, directives, html){
		return jQuery($p.libs.render(this[0], context, directives, html));};
	jQuery.fn.autoRender = function(context, directives, html){
		return jQuery($p.libs.render(this[0], context, directives, html, true));};}

else if (typeof DOMAssistant !== 'undefined') { //Thanks to Lim Cheng Hong from DOMAssistant who did it
	if (typeof document.querySelector === 'undefined') {$p.find = function (selector, context) {
		var found = $(context).cssSelect(selector);
		return found[0] || false;};}	
	DOMAssistant.attach({
		publicMethods : [ 'mapDirective', 'compile', 'render', 'autoRender'],
		mapDirective : function (directives) {
			return $($p.libs.mapDirective(this, directives));},
		compile : function (fName, directives, context) {
			$p.libs.compile(this, fName, directives, context);
			return this;},
		render : function (context, directives, html) {
			return $($p.libs.render(this, context, directives, html));},
		autoRender : function (context, directives, html) {
			return $($p.libs.render(this, context, directives, html, true));}});}

else if (typeof MooTools !== 'undefined') {//Thanks to Carlos Saltos
	if (typeof document.querySelector === 'undefined'){$p.find = function (selector, context) {
		var found = $(context).getElement(selector);
		return found || false;};}

	Element.implement({
	mapDirective: function (directives) {
		return $($p.libs.mapDirective(this, directives));},
	
	compile: function (fName, directives, context) {
		$p.libs.compile(this, fName, directives, context);
		return this;},
	
	render: function (context, directives, html) {
		return $($p.libs.render(this, context, directives, html));},
	
	autoRender: function (context, directives, html) {
		return $($p.libs.render(this, context, directives, html, true));}});}
			
else if (typeof Prototype !== 'undefined'){ //Thanks to Carlos Saltos and Borja Vasquez
	// Implement the find function for pure using the prototype
	// select function
	if (typeof document.querySelector === 'undefined'){ $p.find = function (selector, context) {		
		var found = $(context).select(selector);
		// patch prototype when using selector with id's and cloned nodes in IE
		// maybe in next releases of prototype this is fixed
		if (!found || found === '') {
			var pos = selector.indexOf('#');
			if (pos > -1) { 				
				var id = selector.substr(pos+1);								
				var els = context.getElementsByTagName('*');
        		for (var i = 0, el; el = els[i]; i++) {
        			if (el.id == id) {
        				return el;}}}}
		return found[0] || false;
	};}
	// Add more methods to the prototype element's objects for
	// supporting pure calls
	// Add these extended methods using the prototype element object
	Element.addMethods({
		mapDirective: function (element, directives) {
			return $($p.libs.mapDirective(element, directives));},

		compile: function (element, fName, directives, context) {
			$p.libs.compile(element, fName, directives, context);
			return this;},

		render: function (element, context, directives, html) {
			return $($p.libs.render(element, context, directives, html));},

		autoRender: function (element, context, directives, html) {
			return $($p.libs.render(element, context, directives, html, true));}});}
			
else if (typeof Sizzle !== 'undefined') {
		if (typeof document.querySelector === 'undefined'){ $p.find = function(selector, context){
		var found = Sizzle(selector, context);
		return found[0] || false;};}
		
	$p.sizzle = function(selector, context){
		selector = selector || document;
		var ret  = selector.nodeType ? [selector]:Sizzle(selector, context);
		var sizzle = ret;
		sizzle.mapDirective = function(directives){
			sizzle[0] = $p.libs.mapDirective(sizzle[0], directives);
			return sizzle;};

		sizzle.compile = function(fName, directives, context){
			$p.libs.compile(sizzle[0], fName, directives, context);
			return sizzle;};

		sizzle.render = function(context, directives, html){
			sizzle[0] = $p.libs.render(sizzle[0], context, directives, html);
			return sizzle;};

		sizzle.autoRender = function(context, directives, html){
			sizzle[0] = $p.libs.render(sizzle[0], context, directives, html, true);
			return sizzle;};
		return sizzle;};}
		
/*
 * jQuery Form Plugin
 * version: 2.21 (08-FEB-2009)
 * @requires jQuery v1.2.2 or later
 *
 * Examples and documentation at: http://malsup.com/jquery/form/
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 */
;(function($) {

/*
    Usage Note:  
    -----------
    Do not use both ajaxSubmit and ajaxForm on the same form.  These
    functions are intended to be exclusive.  Use ajaxSubmit if you want
    to bind your own submit handler to the form.  For example,

    $(document).ready(function() {
        $('#myForm').bind('submit', function() {
            $(this).ajaxSubmit({
                target: '#output'
            });
            return false; // <-- important!
        });
    });

    Use ajaxForm when you want the plugin to manage all the event binding
    for you.  For example,

    $(document).ready(function() {
        $('#myForm').ajaxForm({
            target: '#output'
        });
    });
        
    When using ajaxForm, the ajaxSubmit function will be invoked for you
    at the appropriate time.  
*/

/**
 * ajaxSubmit() provides a mechanism for immediately submitting 
 * an HTML form using AJAX.
 */
$.fn.ajaxSubmit = function(options) {
    // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
    if (!this.length) {
        log('ajaxSubmit: skipping submit process - no element selected');
        return this;
    }

    if (typeof options == 'function')
        options = { success: options };

    options = $.extend({
        url:  this.attr('action') || window.location.toString(),
        type: this.attr('method') || 'GET'
    }, options || {});

    // hook for manipulating the form data before it is extracted;
    // convenient for use with rich editors like tinyMCE or FCKEditor
    var veto = {};
    this.trigger('form-pre-serialize', [this, options, veto]);
    if (veto.veto) {
        log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
        return this;
    }

    // provide opportunity to alter form data before it is serialized
    if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
        log('ajaxSubmit: submit aborted via beforeSerialize callback');
        return this;
    }    
   
    var a = this.formToArray(options.semantic);
    if (options.data) {
        options.extraData = options.data;
        for (var n in options.data) {
          if(options.data[n] instanceof Array) {
            for (var k in options.data[n])
              a.push( { name: n, value: options.data[n][k] } )
          }  
          else
             a.push( { name: n, value: options.data[n] } );
        }
    }

    // give pre-submit callback an opportunity to abort the submit
    if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
        log('ajaxSubmit: submit aborted via beforeSubmit callback');
        return this;
    }    

    // fire vetoable 'validate' event
    this.trigger('form-submit-validate', [a, this, options, veto]);
    if (veto.veto) {
        log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
        return this;
    }    

    var q = $.param(a);

    if (options.type.toUpperCase() == 'GET') {
        options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
        options.data = null;  // data is null for 'get'
    }
    else
        options.data = q; // data is the query string for 'post'

    var $form = this, callbacks = [];
    if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
    if (options.clearForm) callbacks.push(function() { $form.clearForm(); });

    // perform a load on the target only if dataType is not provided
    if (!options.dataType && options.target) {
        var oldSuccess = options.success || function(){};
        callbacks.push(function(data) {
            $(options.target).html(data).each(oldSuccess, arguments);
        });
    }
    else if (options.success)
        callbacks.push(options.success);

    options.success = function(data, status) {
        for (var i=0, max=callbacks.length; i < max; i++)
            callbacks[i].apply(options, [data, status, $form]);
    };

    // are there files to upload?
    var files = $('input:file', this).fieldValue();
    var found = false;
    for (var j=0; j < files.length; j++)
        if (files[j])
            found = true;

    // options.iframe allows user to force iframe mode
   if (options.iframe || found) { 
       // hack to fix Safari hang (thanks to Tim Molendijk for this)
       // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
       if (options.closeKeepAlive)
           $.get(options.closeKeepAlive, fileUpload);
       else
           fileUpload();
       }
   else
       $.ajax(options);

    // fire 'notify' event
    this.trigger('form-submit-notify', [this, options]);
    return this;


    // private function for handling file uploads (hat tip to YAHOO!)
    function fileUpload() {
        var form = $form[0];
        
        if ($(':input[name=submit]', form).length) {
            alert('Error: Form elements must not be named "submit".');
            return;
        }
        
        var opts = $.extend({}, $.ajaxSettings, options);
		var s = jQuery.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);

        var id = 'jqFormIO' + (new Date().getTime());
        var $io = $('<iframe id="' + id + '" name="' + id + '" src="about:blank" />');
        var io = $io[0];

        $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });

        var xhr = { // mock object
            aborted: 0,
            responseText: null,
            responseXML: null,
            status: 0,
            statusText: 'n/a',
            getAllResponseHeaders: function() {},
            getResponseHeader: function() {},
            setRequestHeader: function() {},
            abort: function() { 
                this.aborted = 1; 
                $io.attr('src','about:blank'); // abort op in progress
            }
        };

        var g = opts.global;
        // trigger ajax global events so that activity/block indicators work like normal
        if (g && ! $.active++) $.event.trigger("ajaxStart");
        if (g) $.event.trigger("ajaxSend", [xhr, opts]);

		if (s.beforeSend && s.beforeSend(xhr, s) === false) {
			s.global && jQuery.active--;
			return;
        }
        if (xhr.aborted)
            return;
        
        var cbInvoked = 0;
        var timedOut = 0;

        // add submitting element to data if we know it
        var sub = form.clk;
        if (sub) {
            var n = sub.name;
            if (n && !sub.disabled) {
                options.extraData = options.extraData || {};
                options.extraData[n] = sub.value;
                if (sub.type == "image") {
                    options.extraData[name+'.x'] = form.clk_x;
                    options.extraData[name+'.y'] = form.clk_y;
                }
            }
        }

        // take a breath so that pending repaints get some cpu time before the upload starts
        setTimeout(function() {
            // make sure form attrs are set
            var t = $form.attr('target'), a = $form.attr('action');

			// update form attrs in IE friendly way
			form.setAttribute('target',id);
			if (form.getAttribute('method') != 'POST')
				form.setAttribute('method', 'POST');
			if (form.getAttribute('action') != opts.url)
				form.setAttribute('action', opts.url);
							
            // ie borks in some cases when setting encoding
            if (! options.skipEncodingOverride) {
                $form.attr({
                    encoding: 'multipart/form-data',
                    enctype:  'multipart/form-data'
                });
            }

            // support timout
            if (opts.timeout)
                setTimeout(function() { timedOut = true; cb(); }, opts.timeout);

            // add "extra" data to form if provided in options
            var extraInputs = [];
            try {
                if (options.extraData)
                    for (var n in options.extraData)
                        extraInputs.push(
                            $('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />')
                                .appendTo(form)[0]);
            
                // add iframe to doc and submit the form
                $io.appendTo('body');
                io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
                form.submit();
            }
            finally {
                // reset attrs and remove "extra" input elements
				form.setAttribute('action',a);
                t ? form.setAttribute('target', t) : $form.removeAttr('target');
                $(extraInputs).remove();
            }
        }, 10);

        var nullCheckFlag = 0;
		
        function cb() {
            if (cbInvoked++) return;
            
            io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);

            var ok = true;
            try {
                if (timedOut) throw 'timeout';
                // extract the server response from the iframe
                var data, doc;

                doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
                
                if ((doc.body == null || doc.body.innerHTML == '') && !nullCheckFlag) {
                    // in some browsers (cough, Opera 9.2.x) the iframe DOM is not always traversable when
                    // the onload callback fires, so we give them a 2nd chance
                    nullCheckFlag = 1;
                    cbInvoked--;
                    setTimeout(cb, 100);
                    return;
                }
                
                xhr.responseText = doc.body ? doc.body.innerHTML : null;
                xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
                xhr.getResponseHeader = function(header){
                    var headers = {'content-type': opts.dataType};
                    return headers[header];
                };

                if (opts.dataType == 'json' || opts.dataType == 'script') {
                    var ta = doc.getElementsByTagName('textarea')[0];
                    xhr.responseText = ta ? ta.value : xhr.responseText;
                }
                else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
                    xhr.responseXML = toXml(xhr.responseText);
                }
                data = $.httpData(xhr, opts.dataType);
            }
            catch(e){
                ok = false;
                $.handleError(opts, xhr, 'error', e);
            }

            // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
            if (ok) {
                opts.success(data, 'success');
                if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
            }
            if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
            if (g && ! --$.active) $.event.trigger("ajaxStop");
            if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');

            // clean up
            setTimeout(function() {
                $io.remove();
                xhr.responseXML = null;
            }, 100);
        };

        function toXml(s, doc) {
            if (window.ActiveXObject) {
                doc = new ActiveXObject('Microsoft.XMLDOM');
                doc.async = 'false';
                doc.loadXML(s);
            }
            else
                doc = (new DOMParser()).parseFromString(s, 'text/xml');
            return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
        };
    };
};

/**
 * ajaxForm() provides a mechanism for fully automating form submission.
 *
 * The advantages of using this method instead of ajaxSubmit() are:
 *
 * 1: This method will include coordinates for <input type="image" /> elements (if the element
 *    is used to submit the form).
 * 2. This method will include the submit element's name/value data (for the element that was
 *    used to submit the form).
 * 3. This method binds the submit() method to the form for you.
 *
 * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
 * passes the options argument along after properly binding events for submit elements and
 * the form itself.
 */ 
$.fn.ajaxForm = function(options) {
    return this.ajaxFormUnbind().bind('submit.form-plugin',function() {
        $(this).ajaxSubmit(options);
        return false;
    }).each(function() {
        // store options in hash
        $(":submit,input:image", this).bind('click.form-plugin',function(e) {
            var form = this.form;
            form.clk = this;
            if (this.type == 'image') {
                if (e.offsetX != undefined) {
                    form.clk_x = e.offsetX;
                    form.clk_y = e.offsetY;
                } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
                    var offset = $(this).offset();
                    form.clk_x = e.pageX - offset.left;
                    form.clk_y = e.pageY - offset.top;
                } else {
                    form.clk_x = e.pageX - this.offsetLeft;
                    form.clk_y = e.pageY - this.offsetTop;
                }
            }
            // clear form vars
            setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 10);
        });
    });
};

// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
$.fn.ajaxFormUnbind = function() {
    this.unbind('submit.form-plugin');
    return this.each(function() {
        $(":submit,input:image", this).unbind('click.form-plugin');
    });

};

/**
 * formToArray() gathers form element data into an array of objects that can
 * be passed to any of the following ajax functions: $.get, $.post, or load.
 * Each object in the array has both a 'name' and 'value' property.  An example of
 * an array for a simple login form might be:
 *
 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
 *
 * It is this array that is passed to pre-submit callback functions provided to the
 * ajaxSubmit() and ajaxForm() methods.
 */
$.fn.formToArray = function(semantic) {
    var a = [];
    if (this.length == 0) return a;

    var form = this[0];
    var els = semantic ? form.getElementsByTagName('*') : form.elements;
    if (!els) return a;
    for(var i=0, max=els.length; i < max; i++) {
        var el = els[i];
        var n = el.name;
        if (!n) continue;

        if (semantic && form.clk && el.type == "image") {
            // handle image inputs on the fly when semantic == true
            if(!el.disabled && form.clk == el)
                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
            continue;
        }

        var v = $.fieldValue(el, true);
        if (v && v.constructor == Array) {
            for(var j=0, jmax=v.length; j < jmax; j++)
                a.push({name: n, value: v[j]});
        }
        else if (v !== null && typeof v != 'undefined')
            a.push({name: n, value: v});
    }

    if (!semantic && form.clk) {
        // input type=='image' are not found in elements array! handle them here
        var inputs = form.getElementsByTagName("input");
        for(var i=0, max=inputs.length; i < max; i++) {
            var input = inputs[i];
            var n = input.name;
            if(n && !input.disabled && input.type == "image" && form.clk == input)
                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
        }
    }
    return a;
};

/**
 * Serializes form data into a 'submittable' string. This method will return a string
 * in the format: name1=value1&amp;name2=value2
 */
$.fn.formSerialize = function(semantic) {
    //hand off to jQuery.param for proper encoding
    return $.param(this.formToArray(semantic));
};

/**
 * Serializes all field elements in the jQuery object into a query string.
 * This method will return a string in the format: name1=value1&amp;name2=value2
 */
$.fn.fieldSerialize = function(successful) {
    var a = [];
    this.each(function() {
        var n = this.name;
        if (!n) return;
        var v = $.fieldValue(this, successful);
        if (v && v.constructor == Array) {
            for (var i=0,max=v.length; i < max; i++)
                a.push({name: n, value: v[i]});
        }
        else if (v !== null && typeof v != 'undefined')
            a.push({name: this.name, value: v});
    });
    //hand off to jQuery.param for proper encoding
    return $.param(a);
};

/**
 * Returns the value(s) of the element in the matched set.  For example, consider the following form:
 *
 *  <form><fieldset>
 *      <input name="A" type="text" />
 *      <input name="A" type="text" />
 *      <input name="B" type="checkbox" value="B1" />
 *      <input name="B" type="checkbox" value="B2"/>
 *      <input name="C" type="radio" value="C1" />
 *      <input name="C" type="radio" value="C2" />
 *  </fieldset></form>
 *
 *  var v = $(':text').fieldValue();
 *  // if no values are entered into the text inputs
 *  v == ['','']
 *  // if values entered into the text inputs are 'foo' and 'bar'
 *  v == ['foo','bar']
 *
 *  var v = $(':checkbox').fieldValue();
 *  // if neither checkbox is checked
 *  v === undefined
 *  // if both checkboxes are checked
 *  v == ['B1', 'B2']
 *
 *  var v = $(':radio').fieldValue();
 *  // if neither radio is checked
 *  v === undefined
 *  // if first radio is checked
 *  v == ['C1']
 *
 * The successful argument controls whether or not the field element must be 'successful'
 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
 * The default value of the successful argument is true.  If this value is false the value(s)
 * for each element is returned.
 *
 * Note: This method *always* returns an array.  If no valid value can be determined the
 *       array will be empty, otherwise it will contain one or more values.
 */
$.fn.fieldValue = function(successful) {
    for (var val=[], i=0, max=this.length; i < max; i++) {
        var el = this[i];
        var v = $.fieldValue(el, successful);
        if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
            continue;
        v.constructor == Array ? $.merge(val, v) : val.push(v);
    }
    return val;
};

/**
 * Returns the value of the field element.
 */
$.fieldValue = function(el, successful) {
    var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
    if (typeof successful == 'undefined') successful = true;

    if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
        (t == 'checkbox' || t == 'radio') && !el.checked ||
        (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
        tag == 'select' && el.selectedIndex == -1))
            return null;

    if (tag == 'select') {
        var index = el.selectedIndex;
        if (index < 0) return null;
        var a = [], ops = el.options;
        var one = (t == 'select-one');
        var max = (one ? index+1 : ops.length);
        for(var i=(one ? index : 0); i < max; i++) {
            var op = ops[i];
            if (op.selected) {
				var v = op.value;
				if (!v) // extra pain for IE...
                	v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
                if (one) return v;
                a.push(v);
            }
        }
        return a;
    }
    return el.value;
};

/**
 * Clears the form data.  Takes the following actions on the form's input fields:
 *  - input text fields will have their 'value' property set to the empty string
 *  - select elements will have their 'selectedIndex' property set to -1
 *  - checkbox and radio inputs will have their 'checked' property set to false
 *  - inputs of type submit, button, reset, and hidden will *not* be effected
 *  - button elements will *not* be effected
 */
$.fn.clearForm = function() {
    return this.each(function() {
        $('input,select,textarea', this).clearFields();
    });
};

/**
 * Clears the selected form elements.
 */
$.fn.clearFields = $.fn.clearInputs = function() {
    return this.each(function() {
        var t = this.type, tag = this.tagName.toLowerCase();
        if (t == 'text' || t == 'password' || tag == 'textarea')
            this.value = '';
        else if (t == 'checkbox' || t == 'radio')
            this.checked = false;
        else if (tag == 'select')
            this.selectedIndex = -1;
    });
};

/**
 * Resets the form data.  Causes all form elements to be reset to their original value.
 */
$.fn.resetForm = function() {
    return this.each(function() {
        // guard against an input with the name of 'reset'
        // note that IE reports the reset function as an 'object'
        if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
            this.reset();
    });
};

/**
 * Enables or disables any matching elements.
 */
$.fn.enable = function(b) { 
    if (b == undefined) b = true;
    return this.each(function() { 
        this.disabled = !b 
    });
};

/**
 * Checks/unchecks any matching checkboxes or radio buttons and
 * selects/deselects and matching option elements.
 */
$.fn.selected = function(select) {
    if (select == undefined) select = true;
    return this.each(function() { 
        var t = this.type;
        if (t == 'checkbox' || t == 'radio')
            this.checked = select;
        else if (this.tagName.toLowerCase() == 'option') {
            var $sel = $(this).parent('select');
            if (select && $sel[0] && $sel[0].type == 'select-one') {
                // deselect all other options
                $sel.find('option').selected(false);
            }
            this.selected = select;
        }
    });
};

// helper fn for console logging
// set $.fn.ajaxSubmit.debug to true to enable debug logging
function log() {
    if ($.fn.ajaxSubmit.debug && window.console && window.console.log)
        window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,''));
};

})(jQuery);



String.prototype.startsWith = function(str) { 
	return this.match("^"+str) == str;
}
Array.prototype.find = function(searchStr) {
  for (i=0; i<this.length; i++) {
    if (typeof(searchStr) != 'function') {
      if (this[i] == searchStr) {
      	return true;
      }
    }
  }
  return false;
}
Array.prototype.findDict = function(key, value) {
	for (i=0; i<this.length; i++) {
	    if (this[i][key] == value) {
	    	return this[i];
	    }
	}
	return false;
}
Array.prototype.findDictIndex = function(key, value) {
	for (i=0; i<this.length; i++) {
	    if (this[i][key] == value) {
	    	return i;
	    }
	}
	return -1;
}


function trim( str, charlist ) {
    var whitespace;
    if (!charlist) {
        whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000';
    } else {
        whitespace = charlist.replace(/([\[\]\(\)\.\?\/\*\{\}\+\$\^\:])/g, '\$1');
    }
    for (var i = 0, l=str.length; i < l; i++) {
        if (whitespace.indexOf(str.charAt(i)) === -1) {
            str = str.substring(i);
            break;
        }
    }
    for (i = str.length - 1; i >= 0; i--) {
        if (whitespace.indexOf(str.charAt(i)) === -1) {
            str = str.substring(0, i + 1);
            break;
        }
    }
    return whitespace.indexOf(str.charAt(0)) === -1 ? str : '';
}

function fixIE() {
}

function getUrlParam(name, url) {
	name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
	var regexS = "[\\?&]"+name+"=([^&#]*)";
	var regex = new RegExp( regexS );
	var results = regex.exec(url);
	if ( results == null )
		return "";
	else
		return results[1];
}

function goTo(uri) {
	window.location.href = uri;
}

function onFbLogin() {
	window.location.reload();
}

// Depricated
function transformToVideo() {
	var provider = $(this).attr('class');
    var w = $(this).width();
	//alert(w);
	//alert($(this).css('width'));
    var h = $(this).height();
    var id = getUrlParam('id', $(this).attr('src'));
	var video = '';

	if (provider == 'youtube') {
	    video = '<object width="'+w+'" height="'+h+'"><param name="movie" value="http://www.youtube.com/v/'+id+'&hl=en&fs=1&showinfo=0"></param>'
	    		  +'<param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param>'
	    		  +'<embed src="http://www.youtube.com/v/'+id+'&hl=en&fs=1&showinfo=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="'+w+'" height="'+h+'"></embed></object>';
	} else if (provider == 'vimeo') {
		video = '<object width="'+w+'" height="'+h+'"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" />'+
				'<param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id='+id+'&amp;server=vimeo.com&amp;show_title=0&amp;show_byline=0&amp;show_portrait=0&amp;color=00ADEF&amp;fullscreen=1" />'+
				'<embed src="http://vimeo.com/moogaloop.swf?clip_id='+id+'&amp;server=vimeo.com&amp;show_title=0&amp;show_byline=0&amp;show_portrait=0&amp;color=00ADEF&amp;fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="'+w+'" height="'+h+'">'+
				'</embed></object>';    
	}
   	var elem = $('<div class="embeded"></div>');
	$(this).replaceWith(elem);
    elem.get(0).innerHTML = video;
    //alert(elem.innerHTML);
}

function transformToScribdDocument() {
    var id = getUrlParam('id', $(this).attr('src'));
    var access_key = getUrlParam('access_key', $(this).attr('src'));
    var w = $(this).width();
    var h = $(this).height();
    
    var html =  '<object codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" id="doc_242382659961825" name="doc_242382659961825" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" align="middle"	height="'+h+'" width="'+w+'">'+
				'<param name="movie" value="http://documents.scribd.com/ScribdViewer.swf?document_id='+id+'&access_key='+access_key+'&page=1&version=1&viewMode=">'+ 		
				'<param name="quality" value="high">'+ 		
				'<param name="play" value="true">'+		
				'<param name="loop" value="true">'+ 		
				'<param name="scale" value="showall">'+		
				'<param name="wmode" value="opaque">'+ 		
				'<param name="devicefont" value="false">'+		
				'<param name="bgcolor" value="#ffffff">'+ 		
				'<param name="menu" value="true">'+		
				'<param name="allowFullScreen" value="true">'+ 		
				'<param name="allowScriptAccess" value="always">'+ 		
				'<param name="salign" value="">'+    		
				'<embed src="http://documents.scribd.com/ScribdViewer.swf?document_id='+id+'&access_key='+access_key+'&page=1&version=1&viewMode=" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer" play="true" loop="true" scale="showall" wmode="opaque" devicefont="false" bgcolor="#ffffff" name="doc_242382659961825_object" menu="true" allowfullscreen="true" allowscriptaccess="always" salign="" type="application/x-shockwave-flash" align="middle"  height="'+h+'" width="'+w+'"></embed>'+
				'</object>';
   	var elem = $('<div class="embeded">document here</div>');
	$(this).replaceWith(elem);
    elem.get(0).innerHTML = html;
}

function transformToSlideShareDocument() {
    var doc = getUrlParam('doc', $(this).attr('src'));
    //var w = $(this).width();
    //var h = $(this).height();
    var w = 425; // SlideShare toolbar get's messed up, if you change the default dimensions
    var h = 355;
    var html =  '<object style="margin:0px" width="' +w+ '" height="' +h+ '">'+
				'<param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=' +doc+ '&rel=0" />'+
				'<param name="allowFullScreen" value="true"/>'+
				'<param name="allowScriptAccess" value="always"/>'+
				'<embed src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=' +doc+ '&rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="' +w+ '" height="' +h+ '"></embed>'+
				'</object>';
   	var elem = $('<div class="embeded">document here</div>');
	$(this).replaceWith(elem);
    elem.get(0).innerHTML = html;
}



function getEmbedCode(url, w, h, alt_image) {
    var code = '<object type="application/x-shockwave-flash" data="'+url+'" width="'+w+'" height="'+h+'">'+
			   '<param name="movie" value="'+url+'" /><img src="'+static_site+'/images/video-icon.jpg" width="'+w+'" height="'+h+'" alt="" /></object>';
	return code;
}

function showDialog(message) {
	alert(message);
}

jQuery.cookie = function(name, value, options) {
    if (typeof value != 'undefined') { // name and value given, set cookie
        options = options || {};
        if (value === null) {
            value = '';
            options.expires = -1;
        }
        var expires = '';
        if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
            var date;
            if (typeof options.expires == 'number') {
                date = new Date();
                date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
            } else {
                date = options.expires;
            }
            expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
        }
        // CAUTION: Needed to parenthesize options.path and options.domain
        // in the following expressions, otherwise they evaluate to undefined
        // in the packed version for some reason...
        var path = options.path ? '; path=' + (options.path) : '';
        var domain = options.domain ? '; domain=' + (options.domain) : '';
        var secure = options.secure ? '; secure' : '';
        document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
    } else { // only name given, get cookie
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
};

jQuery.fn.validateForm = function() {
	var valid = true;
	$(this).find('input:text:not(.optional)').each(function(){
		if (!$(this).val()) {
			if (!$(this).next().is('span.required')){
				$(this).addClass('invalid').after('<span class="required tiny">Required</span>');
			}
			valid = false;
		} else {
			$(this).removeClass('invalid').next('span.required').remove();
		}
	});
	$(this).find('input:checkbox:.required').each(function(){
		if ($(this).is(':checked')) {
			$(this).removeClass('invalid').next('span.required').remove();
		} else {
			if (!$(this).next().is('span.required')){
				$(this).addClass('invalid').after('<span class="required tiny">Required</span>');
			}
			valid = false;
		}
	});
	return valid;
}
jQuery.fn.labelOverlay = function(options) {
	$(this).each(function(){
		if (options == 'hide-labels') {
			if ($(this).val() == $(this).attr('label')){
				$(this).val('');
			}
			$(this).removeClass('label-overlay-on');

		} else {
			if ($(this).data('label')) return; // Check for initialization

			// Initialize	
			var value = $(this).val();
			var label = $(this).attr('label');
			if (!label) {
				$(this).attr('label', $(this).attr('title'));
				label = $(this).attr('label');
			}
			if (value == '' || value == label) {
				$(this).addClass('label-overlay-on');
				$(this).val($(this).attr('label'));
			} else {
				$(this).removeClass('label-overlay-on');
			}

			// Bind events
			$(this).focus(function(){
				if ($(this).val() == $(this).attr('label')){
					$(this).val('');
				}
				$(this).removeClass('label-overlay-on');
			});
			$(this).change(function(){
				var value = $(this).val();
				if (value == '' || value == $(this).attr('label')) {
					$(this).addClass('label-overlay-on');
					$(this).val($(this).attr('label'));
				} else {
					$(this).removeClass('label-overlay-on');
				}
			});
			$(this).blur(function(){
				var label = $(this).attr('label');
				if ($(this).val() == ''){
					$(this).addClass('label-overlay-on');
					$(this).val(label);
				}
			});
			$(this).data('label', true); // Initialized
		}
	});
	return $(this);
}
jQuery.fn.loading = function(message, options) {
    try {
		options = $.extend({
			spinner: true,
			button: null
		}, options);
		var button = $(this).is('form') ? $(this).find('button:last') : $(this);
		if (message == 'done') {
			button.next('span.loading, span.loading-message').remove();
			button.attr('disabled','');
			options.button == 'hide' ? button.hide() : button.show();
		} else {
			var cls = options.spinner ? 'loading' : 'loading-message';
			var loading = button.next('span.loading, span.loading-message');
			if (loading.length == 0)	{
				loading = $('<span class="'+cls+' tiny"></span>');
				loading.html(message);
				loading.css('float', button.css('float'));
				button.after(loading);
			} else {
				loading.attr('class', cls);
				loading.html(message);
			}
			button.attr('disabled', (options.disabled ? 'disabled' : '') );
			options.button == 'show' ? button.show() : button.hide();
		}
    } catch(error) {
        $.log(error);
    }
}
jQuery.fn.loadingButton = function(options) {
	$(this).each(function(){
	    try {
			options = $.extend({
				text: 'Loading...',
				spinner: true,
				done: false
			}, options);
			var button = $(this);
			var originalText = button.data('original-text');
	
			if (options.done){
				// Stop
				button.next('span.loading').remove();
				button.text(originalText);
				button.attr('disabled', '');
			} else {
				// Start 
				if (options.text) {
					if (!originalText){
						button.data('original-text', button.text());
					}
					button.text(options.text);
				}
				if (options.spinner){
					button.after('<span class="loading tiny"/>')
				}
				button.attr('disabled', 'disabled');
			}
	    } catch(error) {
	        $.log(error);
	    }
	});
}
jQuery.fn.toggler = function() {
	$(this).each(function(){
		if (!$(this).data('toggler')) {
			if (!$(this).prev().hasClass('.toggler-icon')) {
				$(this).before('<span class="toggler-icon">&nbsp;</span>');
			}
			$(this).click(function(){
				//$.log(this);
				var target = $(this).attr('target');
				target = target ? $(target) : $(this).next();
				//$.log(target);
				if (target.is(':hidden')) {
					target.parents(':hidden').show();
					$(this).prev().addClass('toggler-visible');
					target.slideDown(200);
				} else {
					$(this).prev().removeClass('toggler-visible');
					target.slideUp(200);
				}
				return false;
			});
			$(this).data('toggler', true);
		}
	});
	return $(this);
}
jQuery.fn.valOf = function(selector) {
	return $(this).find(selector).val();
}
jQuery.message = function(message, options) {
	if (!options) options = {};
	var container = $('#top-message-box');
	var box = container.children('span');
	if (message == 'done') {
		box.animate({ top: 0 }, { duration: 300, queue: false });
		box.removeClass('loading');
	} else {
		if (options.spinner) box.addClass('loading');
		else box.removeClass('loading');
		box.text(message);
		box.animate({ top: '45px' }, { duration: 300, queue: false });
		setTimeout('$.message("done")', 7000);
	}
}

jQuery.log = function(message, debug) {
	if (debug && $.browser.msie) return;
	if (!$.browser.msie && typeof(console) != "undefined") {
		console.log(message);
	}
}
jQuery.logArray = function(a) {
	for (i=0; i<a.length; i++) {
	    $.log(a[i]);
	}
}
jQuery.logDict = function(d) {
	for (i in d) {
	    $.log(i);
	}
}

jQuery.loadCSS = function(css_url) {
	$("head").append("<link>");
    $("head").children("link:last").attr({rel: "stylesheet", type: "text/css", href: css_url});
}

jQuery.applyCSS = function(css_string, id) {
	if (css_string){
		if (id) {
			$('#'+id).remove();
			$("head").append('<style id="' +id+ '" type="text/css">' + css_string + '</style>');
		} else {
			$("head").append('<style type="text/css">' + css_string + '</style>');
		}
	}
}




// ZipiBuilder javascript component. This needs some rethinking.
jQuery.fn.component = function(action, value) {
	$(this).each(function(){
		//$.log($(this) + ' ' + action);
		if (action == 'get') {
			return $(this).data('component');
		} else if (action == 'set') {
			$(this).data('component', value);
		} else {
			return $(this).data('component');
		}
		return value;
	});
	return $(this).data('component');
}


jQuery.fn.user = function(action) {
	var component = $(this).component('get');
	if (!component) {
		component = new User($(this));
		$(this).component('set', component);
		return component;
	} else if (!action){
		alert('Element already has a component. Failed to create User.');
	} else if (action == 'get-profile-image'){
		return component.v.root.find('span.profile-image-url').text();
	}
}

function User(root) {
	this.type = 'user';
	this.site_key = root.find('.site-key').text();
	this.v = {};
	this.v.root = root;
	this.v.login = $('#login-dialog');
	this.v.signup = $('#signup-dialog');
	this.v.name = root.find('a.user_name');
	this.v.logout = root.find('a.logout');
	this.v.login_link = root.find('a.login');
	this.v.signup_link = root.find('a.signup');
	this.v.login_message = this.v.login.find('span.message');
	this.v.signup_message = this.v.signup.find('span.message');
	this.v.save = $('form#user-save');
	this.data = {};
	this.init();
}
User.prototype.init = function() {
	var self = this;
	this.v.signup.dialog({
		bgiframe: true,
		autoOpen: false,
		modal: true,
		resizable: false 
	}).show().find("form").submit(function(){
		self.signup($(this).find('input.name').val(), $(this).find('input.email').val(), $(this).find('input.password').val());
		return false;
	});
	
	this.v.login.dialog({
		bgiframe: true,
		autoOpen: false,
		modal: true,
		resizable: false 
	}).show().find("form").submit(function(){
		self.login($(this).find('input.email').val(), $(this).find('input.password').val());
		return false;
	}).find('button.new-password').click(function(){
		self.sendNewPassword($(this).parents('form').find('input.password-email').val());
		return false;
	});
	
	self.v.login_link.click(function(){
		self.showLogin();
		return false;
	});
	self.v.signup_link.click(function(){
		self.showSignup();
		return false;
	});
	
	self.v.save.submit(function(){
		if (!$(this).validateForm()) return false;
		self.saveSettings(this);
		return false;
	});

    // Bind events
    this.v.root.bind('login', self.onLogin);
    //this.v.root.bind('new-user-data', function(){});
}
User.prototype.showLoginMessage = function(message) {
	this.v.login_message.text(message).slideDown();
}
User.prototype.showSignupMessage = function(message) {
	this.v.signup_message.text(message).slideDown();
}
User.prototype.showLogin = function() {
	this.v.login_message.hide();
	this.v.login.dialog('open');
	this.v.login.find('input.email').focus();
}
User.prototype.showSignup = function() {
	this.v.signup_message.hide();
	this.v.signup.dialog('open');
	this.v.signup.find('input.name').focus();
}
User.prototype.login = function(email, password) {
	var self = this;
	self.v.login.find('button.login').loading('&nbsp;', {spinner:true});
	$.post('/user/login/', {
		email: email,
		password: password
	}, function(response){
		if (response.status == 1) {
			self.data = response.user;
			var forward = self.v.login.find('input.forward').val();
			if (forward.length > 0) goTo(forward);
			$('.component').trigger('login', {reload:true} );
		} else {
			self.v.login.find('button.login').loading(response.message, {spinner:false, button:'show'});
			//$.message(response.message);
		}
    }, 'json');
}
User.prototype.saveSettings = function(form) {
	var self = this;
	form = $(form);
	form.loading('&nbsp;');
	form.ajaxSubmit({ 
        dataType:  'json',
        success: function(response){
			if (response.status == 1) {
				self.data.name = name;
			}
			form.loading('done');
			form.find('span.save-results').text(response.message);
		}
    });
}
User.prototype.sendNewPassword = function(email) {
	var self = this;
	$.post('/user/send-password/', {
		email: email
	}, function(response){
		self.showLoginMessage(response.message);
    }, 'json');
}
User.prototype.signup = function(name, email, password) {
	var self = this;
	self.v.signup.find('button.signup').loading('&nbsp;', {spinner:true});
	$.post('/user/signup/', {
		site_key: self.site_key,
		name: name,
		email: email,
		password: password,
		redirect_url: location.href
	}, function(response){
		if (response.status == 1) {
			self.data = response.user;
			self.v.signup.find('button.signup').loading('done');
			var redirect_url = response.redirect_url ? response.redirect_url : '/settings' 
			$('.component').trigger('login', {redirect:redirect_url} );
		} else {
			self.v.signup.find('button.signup').loading(response.message);
		}
    }, 'json');
}
User.prototype.onLogin = function(event, data) {
	if (data && data.redirect) {
		goTo(data.redirect);
		return;
	}
	if (data && data.reload) {
		window.location.reload();
		return;
	}
	var self = $(this).component();
	self.v.login.dialog('close');
	self.v.signup.dialog('close');
	if (self.data.name) {
		self.v.name.text(self.data.name);
	} else {
		self.v.name.text('Asetukset');
	}
	self.v.logout.attr('href', self.data.logout_url);
	self.v.root.show();
	self.v.login_link.parent().hide();
	self.v.signup_link.parent().hide();
	self.v.name.parent().show();
	self.v.logout.parent().show();
}



$(document).ready(function() {
	//$.log('ready: main.js');
	//var start = new Date().getMilliseconds();
	$('#user').user();
	
	var ie = $.browser.msie;
	if (!ie) {
		if ($('ul.images').length == 0 && typeof Shadowbox != 'undefined') {
			$('a.shadowbox').attr('rel', 'shadowbox');
			Shadowbox.init();
		}
	}
	
	$('a.toggler').toggler();
	$('input.label-overlay').labelOverlay();

	$('body.view-post').each(function(){
		$('img.youtube').each(transformToVideo); // Depricated
		$('img.vimeo').each(transformToVideo);	// Depricated
		$('img.scribd').each(transformToScribdDocument);	
		$('img.slideshare').each(transformToSlideShareDocument);	
	});
	
	$('ul.menu li').hover(
		function() { $(this).addClass('opacity'); }, 
		function() { $(this).removeClass('opacity'); }
	);
	
	$('span.ui-state-disabled').hover(
		function() { $(this).removeClass('ui-state-disabled'); }, 
		function() { $(this).addClass('ui-state-disabled'); }
	);
	$('button.ui-state-default').hover(
		function() { $(this).addClass('ui-state-hover'); }, 
		function() { $(this).removeClass('ui-state-hover'); }
	);
	//console.log('main.js document.ready: ' + (new Date().getMilliseconds() - start));
});



