// JavaScript Document
// choquinlabs choquin.js v2.0, Mon Nov 26 2007 16:40:12 GMT-0300

// Copyright (c) 2007 Lisandro Puzzolo (http://www.puzzolo.com.ar, http://choquinlabs.com.ar)
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

try{Prototype}catch(e){alert("you must include Prototype library.")}


_global = window;


var Choquin = Choquin||{};
Object.extend(Choquin,{
	Enumerable: {},
	eval: function(what){return eval(what)}, //sirve para hacer bind de eval
	
	StyleFragment: '<style[^>]*>([\\S\\s]*?)<\/style>',
	CommentFragment: '<!--([\\S\\s]*?)-->',
	
	include: function(src,options){
		/** includes external js and css files ONLY ONCE
			options:
				base: a string from another included source, ie: choquin.js from where the new src will be lined
					ie: Choquin.include('choquin.css',{base:'choquin.js'}) will link choquin.css at the same path from choquin.js
				onload: a function that will trigger once the file gets included.
				type: type of file, javascript or css
		*/
		var options = Object.extend({},options||{});
		if(options.base){
			var src = (Choquin.getLinkedFilePath(options.base)||options.base)+src;
		}
		if(Choquin.getLinkedFilePath(src,'equals')) return;
		var head = $(document.getElementsByTagName('head')[0]);
		var node;
		(arguments.callee.id = arguments.callee.id||0);
		arguments.callee.id++;
		
		var id = arguments.callee.id;
		//var onload = "Choquin.include._onLoad(this,'"+src+"',"+id+")";
		
		var type = options.type;
		if(type == 'text/javascript' || type=='javascript' || src.ext()=='js'){
			var type = 'text/javascript';
			var language = 'javascript';
			node = new Element('script',{language:language, type:type, src:src});
		} else {
			type = 'text/css';
			node = new Element('link',{rel:'stylesheet', type:type, href:src});
		};
		
		node.onload = function(evt){
			if(options.onload) options.onload(src,id);
			//if(type=='text/javascript' && node && node.removeFromParent) node.removeFromParent(); //si remuevo un css, el estilo desaparece
		};
		
		node.onerror = function(evt){
			if(options.onerror) options.onerror(evt);
			//if(type=='text/javascript' && node && node.removeFromParent) node.removeFromParent(); //si remuevo un css, el estilo desaparece
		};
		
		/***** para ie ***/
		node.onreadystatechange= function () {
			//alert(this.readyState);
			if (this.readyState == 'complete' || this.readyState == 'loaded') { // 
				node.onreadystatechange = null;
				node.onload();
			};
		};
		/*****************/

		head.insert({top:node});
		//document.write('<script type="text/javascript" src="'+src+'"><\/script>');
	},
	
	getLinkedFilePath: function(fileName,compareFn){
		var compareFn = compareFn || 'include';
		if(fileName.ext() == 'js'){
			var script = $A(document.getElementsByTagName("script")).find(function(s) {
				return (s.src && s.src[compareFn](fileName));
			});
			return script && script.src.trimFromLast(fileName);
		};
		if(fileName.ext() == 'css'){
			var link = $A(document.getElementsByTagName("link")).find(function(s) {
				return (s.href && s.href[compareFn](fileName));
			});
			return link && link.href.trimFromLast(fileName);
		};
		return null;
	},
	
	createNamespace: function(namespace){
		var names = namespace.split(".");
		var objeto = null;
		while(names.size()>0){
			var name = names.shift();
			if(!objeto){
				if (eval ('typeof '+name)=='undefined') eval(name+'={}');
				eval('objeto='+name);
			}
			else {
				objeto [name] = objeto [name] || {};
				objeto = objeto[name];
			}
		}
	},
	
	Ajax:{}
});


/*Choquin.include._onLoad = function(node,src,id){
	if(node._onload) node._onload(src,id);
	
};*/

Choquin.Translations={y:'y'};

////////////////////////////////////////////////
/*
	Base.js, version 1.1
	Copyright 2006-2007, Dean Edwards
	License: http://www.opensource.org/licenses/mit-license.php
*/

var Base = function() {
	// dummy
};

Base.extend = function(_instance, _static) { // subclass
	var extend = Base.prototype.extend;
	
	// build the prototype
	Base._prototyping = true;
	var proto = new this;
	extend.call(proto, _instance);
	delete Base._prototyping;
	
	// create the wrapper for the constructor function
	//var constructor = proto.constructor.valueOf(); //-dean
	var constructor = proto.constructor;
	var klass = proto.constructor = function() {
		if (!Base._prototyping) {
			if (this._constructing || this.constructor == klass) { // instantiation
				this._constructing = true;
				constructor.apply(this, arguments);
				delete this._constructing;
			} else if (arguments[0] != null) { // casting
				return (arguments[0].extend || extend).call(arguments[0], proto);
			}
		}
	};
	
	// build the class interface
	klass.ancestor = this;
	klass.extend = this.extend;
	klass.forEach = this.forEach;
	klass.implement = this.implement;
	klass.prototype = proto;
	klass.toString = this.toString;
	klass.valueOf = function(type) {
		//return (type == "object") ? klass : constructor; //-dean
		return (type == "object") ? klass : constructor.valueOf();
	};
	extend.call(klass, _static);
	// class initialisation
	if (typeof klass.init == "function") klass.init();
	return klass;
};

Base.prototype = {	
	extend: function(source, value) {
		if (arguments.length > 1) { // extending with a name/value pair
			var ancestor = this[source];
			if (ancestor && (typeof value == "function") && // overriding a method?
				// the valueOf() comparison is to avoid circular references
				(!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) &&
				/\bbase\b/.test(value)) {
				// get the underlying method
				var method = value.valueOf();
				// override
				value = function() {
					var previous = this.base || Base.prototype.base;
					this.base = ancestor;
					var returnValue = method.apply(this, arguments);
					this.base = previous;
					return returnValue;
				};
				// point to the underlying method
				value.valueOf = function(type) {
					return (type == "object") ? value : method;
				};
				value.toString = Base.toString;
			}
			this[source] = value;
		} else if (source) { // extending with an object literal
			var extend = Base.prototype.extend;
			// if this object has a customised extend method then use it
			if (!Base._prototyping && typeof this != "function") {
				extend = this.extend || extend;
			}
			var proto = {toSource: null};
			// do the "toString" and other methods manually
			var hidden = ["constructor", "toString", "valueOf"];
			// if we are prototyping then include the constructor
			var i = Base._prototyping ? 0 : 1;
			while (key = hidden[i++]) {
				if (source[key] != proto[key]) {
					extend.call(this, key, source[key]);

				}
			}
			// copy each of the source object's properties to this object
			for (var key in source) {
				if (!proto[key]) extend.call(this, key, source[key]);
			}
		}
		return this;
	},

	base: function() {
		// call this method from any other method to invoke that method's ancestor
	}
};





// initialise
Base = Base.extend({
	constructor: function() {
		this.extend(arguments[0]);
	}
}, {
	ancestor: Object,
	version: "1.1",
	



	forEach: function(object, block, context) {
		for (var key in object) {
			if (this.prototype[key] === undefined) {
				block.call(context, object[key], key, object);
			}
		}
	},
		
	implement: function() {
		for (var i = 0; i < arguments.length; i++) {
			if (typeof arguments[i] == "function") {
				// if it's a function, call it
				arguments[i](this.prototype);
			} else {
				// add the interface using the extend method
				this.prototype.extend(arguments[i]);
			}
		}
		return this;
	},
	
	toString: function() {
		return String(this.valueOf());
	},
	applyTo:function(object){
		if(object.constructor==this) return;
		var ghost=new this();
		Object.extend(object,ghost);
		return object;
	}
});



///////////////// LICHO's extensions to base //////////////////////////

Base.implement({
	toString:function(){
		return this.inspect();
	},
	
	inspect: function(){
		return (this.className||"Base")+":{"+this.objectAttributes()+"}";
	},
	
	
	objectAttributes:function(){
		var ret = [];
		for (i in this) if(typeof this[i]!="function" && i!="className") ret.push(i+":"+this[i]);
		return ret.join(",");
	},
	
	clone:function(){
		//console.debug('cloning');
		var clone = new this.constructor();
		return Object.extend(clone,this);
	}

});


///////////////////////////////////////////////////////////////////

Number.prototype.describe = Object.toJSON.methodize();
String.prototype.describe = Object.toJSON.methodize();

Object.describe=function(object,depth,stack){
	if(depth==null) depth = 1;
	if(!stack) stack = {};
	if(depth>3 || stack[object]) return "...";
  try {
	if (typeof object == 'object' && object) stack[object] = true;
	if (typeof object == "undefined") return 'undefined';
    if (object == null) return 'null';
    if (typeof object == 'number') return object;
    if (typeof object == 'boolean') return object;
    if (typeof object == "date") return object+='';
		if (typeof object=="function") return "function("+String(object).functionParameters()+")";
	try{object.inspect}catch(e){WriteLn(typeof object+"\n"+e)};
	if(object.describe) return object.describe();
	//if(object.inspect) return object.inspect();
	var ret="{\n";
	var arr=[];
	for (var i in object) if(typeof object[i]!="function") arr.push ("\t".repeat(depth)+"'"+i+"':"+Object.describe(object[i],depth+1,stack));	
	ret+= arr.join(",\n")+"\n"+"\t".repeat(depth-1)+"}";
	return ret;
  } catch (e) {
    if (e instanceof RangeError) return '...';
    throw e;
  };
};



Object.extend(Object,{
	extendClone: function(destination,source){
		return Object.extend(Object.clone(destination),source);
	},

	null2Empty:function(val){
		if(val==null || val==undefined) return "";
		return String(val);
	},
	
	isNull:function(val,options){
		return val==null || val==undefined || String(val)=="" || String(val)=="null";
	},
	
	isEmpty:function(source){
		//devuelve true si [], "", null, {}, 0
		//false: 1, " ", "  ", [0]
		return (source instanceof Array && source.length==0) || (source==="") || (Object.keys(source).size()==0) || source===0;
	},

	isDate: function(val){
		return val instanceof Date;
	},
	
	isBoolean: function(val){
		return typeof val == "boolean";
	},


	isValidDate: function(val){
		var date = new Date(val);
		return !isNaN(date.getYear());
	},
	
	isValidNumber: function(val){
		return !isNaN(val);
	},
	
	toLowerCase:function(val){
		if(typeof val.toLowerCase=="function") return val.toLowerCase();
	},

	toUpperCase:function(val){
		if(typeof val.toUpperCase=="function") return val.toUpperCase();
	},
	
	toArray: function(val,options){
		//esta función transforma valoes únicos en array [valor], strings en [string], arrays y enumerables en sí mismos.
		options = options||{};
		if(!val) {
			if (val===undefined && (options.all || options['undefined'])) return [undefined]; 
			if (val===null && (options.all || options['undefined'])) return [null]; 
			return [];
		}
		if(typeof val == 'string') return [val];
		if(val.toArray) return val; //no pongo val.toArray() para que no me transforme los enumerables.
		return [val];
	},
	
	stringToDate:function(val){
		//intenta convertir el objeto a un Date, y si no es posible, lo deja como está. Sólo convierte cadenas, no múmeros
		var date = new Date(val);
		return isNaN(date.getYear()) ? val: date;
	},
	
	formatDate: function(val,format){
		if(val && val.format) return val.format(format);		
		return "";
	},

	
	archiveProperty: function(obj,propname,copy){
		if(!obj) return;
		var oldProperty = obj[propname];
		obj["__________"+propname] = oldProperty;
		if(!copy) try{delete obj[propname];}catch(e){};
	},
	
	backupProperty: function(obj,propname){
		this.archiveProperty(obj,propname,true);
	},

	
	restoreProperty: function(obj,propname){
		if(obj["__________"+propname]){
			obj[propname] = obj["__________"+propname];
			delete obj["__________"+propname];
		}
	},
	
	archivedProperty: function(obj,propname){
		if(!obj) return;
		return obj["__________"+propname];
	},

	
	removeProperties: function(obj,properties){
		if(!properties) return;
		if(typeof properties=="string") properties = [properties];
		if(arguments.length>2) {
			var arr = $A(arguments);
			arr.shift();arr.shift();
			properties = properties.concat(arr);
		}
		properties.each(function(property){
			delete obj[property];
		});
		return obj;
	},
	
	intersect: function(obj,properties){
		//devuelve un objeto que tiene las propiedades que son intersección con el array properties
		var ret = {};
		if(!properties) return;
		if(typeof properties=="string") properties = [properties];
		if(arguments.length>2) {
			var arr = $A(arguments);
			arr.shift();arr.shift();
			properties = properties.concat(arr);
		}
		properties.each(function(property){
			if(property in obj) ret[property] = obj[property];
		});
		return ret;
	},
	
	hasMember: function(obj,val){
		//devuelve si este objeto tiene la propiedad o método con nombre val en su prototype
		return obj.constructor.prototype[val]!=undefined;
	},
	
	hasProperty: function(obj,key){
		return (obj && key in obj);
	},	
	
	replaceWildcard: function (source,fields,wildcard) {
		var destination = {};
		fields.each(function(field){
			if (Object.hasProperty(source,field+wildcard)) destination [field] = source [field+wildcard];
		});
		return destination;
	},
	
	replaceIfNull: function(source, key, value){
		if(!source[key]) source[key] = value;
		return source;
	},
	
	withoutValues: function(source,values){
		var dest = {};
		values = Object.toArray(values,{all:true});
		for (var key in source) {
			//console.debug(key,source[key], values.includeExact(source[key]));
			if(!values.includeExact(source[key])) dest[key] = source[key];
		}
		return dest;
	},

	withoutKeys: function(source,keys){
		var dest = {};
		keys = Object.toArray(keys);
		for (key in source) {
			if(!keys.includeExact(key)) dest[key] = source[key];
		}
		return dest;
	},
	
	append: function(destination,source){
		//agrega nuevas propiedades al objeto destination (no reemplaza originales)
	  for (var property in source) {
		if(!Object.hasProperty(destination,property)) destination[property] = source[property];
	  }
	  return destination;
	},
	
	properties: function(obj){
		return Object.keys(obj).select(function(propertyName){
			return typeof obj[propertyName] != "function";
		});
	}

});






/*
//
//Written by Thierry Schellenbach
//http://www.mellowmorning.com/2007/10/25/introducing-a-cross-site-ajax-plugin-for-prototype/
//Version 0.8.0 for Prototype 1.5.0
//Developed for www.commenthub.com
//
//modeled after XmlHttpRequest http://en.wikipedia.org/wiki/XMLHttpRequest
//functions open, send (setRequestHeader) - variable readyState, status
//
//    * 0 = uninitialized - open() has not yet been called.
//    * 1 = open - send() has not yet been called.
//    * 2 = sent - send() has been called, headers and status are available.
//    * 3 = receiving - Downloading, responseText holds partial data.
//    * 4 = loaded - Finished.
//
//    for which prototype does this:
//    ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']
//    unfortunately the onreadystatechange only works for the last 3, because 
//    prototype 1.5.0 assigns it too late, for our usage and prevents status 1
//    Prototype uses a timer, which in tests lead onSuccess to occur before onLoading
//    We use respondToReadyState to make a direct instruction and bypass the filter
//

//TODO:
//Removal of <script> nodes?

//
//------------------------------ initialize, open and send ------------------------------------------------------
//

Choquin.Ajax.XHR = Class.create({
	initialize: function() {
	    this.readyState = 0;
	},


	open: function(method, url, asynchronous) {
		if (method != 'GET')
		alert('Method should be set to GET when using cross site ajax');
		this.readyState = 1;
		// little hack to get around the late assignment of onreadystatechange 
		this.respondToReadyState(1);
		this.onreadystatechange();
		this.url = url;
		this.userAgent = navigator.userAgent.toLowerCase();
		this.setBrowser();
	},


	send: function(body) {
		this.readyState = 2;
		this.onreadystatechange();
		this.getScriptXS(this.url);
	},


	setBrowser: function(body) {
		this.browser = {
			version: (this.userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
			safari: /webkit/.test(this.userAgent),
			opera: /opera/.test(this.userAgent),
			msie: /msie/.test(this.userAgent) && !/opera/.test(this.userAgent),
			mozilla: /mozilla/.test(this.userAgent) && !/(compatible|webkit)/.test(this.userAgent),
			konqueror: this.userAgent.match(/konqueror/i)
			};
	},


	callback: function() {
		try{
			this.status = _xsajax$transport_status;
		} catch(e) {
			return;
		//to prevent people from writing code, which is not cross browser compatible
		}
		this.readyState = 4;
		this.onreadystatechange();
		_xsajax$transport_status = null;
	},
	
	
	getScriptXS: function() {
		// determine arguments 
		var arg = {
		'url':      null
		}
		arg.url = arguments[0];
	
		// generate <script> node 
		this.node = document.createElement('SCRIPT');
		this.node.type = 'text/javascript';
		this.node.src = arg.url;
	
		this.fixNodeEvents(this.node);
		
		// inject <script> node into <head> of document 
		this.readyState = 3;
		this.onreadystatechange();
		var head = document.getElementsByTagName('HEAD')[0];
		head.appendChild(this.node);
	},
	

	fixNodeEvents: function(node){
		// FF and Opera properly support onload. MSIE has its own implementation. Safari and Konqueror need some polling 
		if (this.browser.msie) {
			
			function mybind(obj) {
				temp = function() {
					if (this.readyState == "complete" || this.readyState == "loaded") {
						return obj.callback.call(obj);
					}
				};
				return temp;
			}
			// MSIE doesn't support the "onload" event on
			//   <script> nodes, but it at least supports an
			//   "onreadystatechange" event instead. But notice:
			//   according to the MSDN documentation we would have
			//   to look for the state "complete", but in practice
			//   for <script> the state transitions from "loading"
			//   to "loaded". So, we check for both here... 
			this.node.onreadystatechange = mybind(this);
	
		} else if (this.browser.safari || this.browser.konqueror) {
			this.timepassed = 0;
			// Safari/WebKit and Konqueror/KHTML do not emit _any_ events at all, so we need to use some primitive polling 
			this.checkTimer = setInterval(function()
				{
				this.timepassed = this.timepassed+100;
				if(typeof(eval(_xsajax$transport_status)) != 'undefined' && eval(_xsajax$transport_status) != null)
				{
						this.callback();
						clearInterval(this.checkTimer);
				}
				if(this.timepassed > 20000)
					clearInterval(this.checkTimer);
			}.bind(this),100);
		} else {
			
			// Firefox, Opera and other reasonable browsers can
			// use the regular "onload" event... 
			this.node.onload = this.callback.bind(this);
		}

	},
	
	setRequestHeader: Prototype.emptyFunction,
	onreadystatechange: Prototype.emptyFunction,
	respondToReadyState: Prototype.emptyFunction

});

//
//------------------------------ Don't complain when these are called: setRequestHeader and onreadystatechange ----------
//

*/



Object.extend(Choquin.Enumerable,{
	sortBy: function(iterator,desc) {
    var val_1 = desc? 1:-1;
		var val_2 = desc? -1:1;
		return this.collect(function(value, index) {
      return {value: value, criteria: iterator(value, index)};
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? val_1 : a > b ? val_2 : 0;
    }).pluck('value');
  },

	intersect:function (otherEnumerable){
		return this.select(function(obj){
			return otherEnumerable.include(obj);
		});
	},

	
  /*pluck: function(property) {
		//esta extensión permite hacer pluck de métodos además de propiedades, ejemplo [obj,obj].puck("getName()")
    var results = [];
    this.each(function(value, index) {
      var result;
			try{result = eval("value."+property);}catch(e){}
			results.push(result);
    });
    return results;
  },*/

	
	grepPartition: function (pattern,iteratorTrue,iteratorFalse,property){
		//pattern puede ser un string, un regExp, o un iterator (funcion)
		var trues = [], falses = [];	
		
		if(typeof pattern == "function") {
			this.each(function(value,index){
				if (pattern (value,index))
					trues.push((iteratorTrue || Prototype.K)(value, index));
				else falses.push((iteratorFalse || Prototype.K)(value, index));
			});
			return [trues,falses];
		}
		
		if(! (pattern instanceof RegExp)) pattern = String(pattern);
		if(typeof pattern == "string") pattern = pattern.findToRegExp();

		this.each(function(value,index){
			var stringValue = String(value).toString();
			if(property) {
				try{stringValue = String(eval("value."+property));}catch(e){}
			}
			if (pattern.test(stringValue))
				trues.push((iteratorTrue || Prototype.K)(value, index));
			else falses.push((iteratorFalse || Prototype.K)(value, index));
		});
		return [trues,falses];
	},
	
	castTo: function(className){
		//cast every element to the className
		return this.collect(function(element){return new className(element)});	
	},
	
	includeExact: function(object) {
		var found = false;
		this.each(function(value) {
		  if (value === object) {
			found = true;
			throw $break;
		  }
		});
		return found;
	},
	
	tag: function(tag,options){
		//surrounds every element with the tag and returns a String
		return this.collect(function(element){return String(element).tag(tag,options)});
	},
	
	includeTimes: function(){
		/**
			receives groups of arguments, in the way: obj1,obj2,times1, obj3,times2
			where times are numbers, and any other obj are from any other types;
			It returns true if the object includes at least that times the argument objects.
			for example: ["A", "A", "A", "B", "C"].includeTimes("A",2,"B",1) = true
			for example: ["A", "A", "A", "B", "C"].includeTimes("A",4,"B",1) = false // "A" it's not 4 times in the object
		*/
		
		var values = $A(arguments);
		var ret = true;
		for (var i = 0; ret && i<values.length;i++){
			var item = [];
			do {item.push(values[i]);i++} while (i<values.length && typeof values[i]!="number");
			var times = values[i];
			var found = this.findAll(function(thisItem){
				return item.include(thisItem);
			});
			if (found.size()<times) ret=false;
		}
		return ret;
	},
	
	withoutTimes: function(){
		/**
			receives groups of arguments, in the way: obj1,obj2,times1, obj3,times2
			where times are numbers, and the other obj are any other types
			It returns an array without that times the argument objects;
			for example: ["A", "A", "A", "B", "C"].withoutTimes("A",2,"B",1) = ["A","C"]
		*/
		var values = $A(arguments);
		var ret = [];

		var items = [];
		for (var i=0; i<values.length ; i++){
			var item = [];
			do {item.push(values[i]);i++} while (i<values.length && typeof values[i]!="number");
			item.times = values[i];
			items.push(item);
		}
		
		this.each (function(item){
			itemGroup = items.find(function(argumentItem){
				return argumentItem.include(item);
			});
			
			if (itemGroup && itemGroup.times>0) itemGroup.times--;
			else ret.push(item);
		});
		
		return ret;
	},
	
	withoutItems: function(){
		// devuelve un array sin los items recibidos por parámetro (quita sólo una vez cada item de la lista original)
		var values = $A(arguments);
		var ret = [];
		var clone = this.clone();
		clone.each(function(item,index){
			if (!values.include(item)) ret.push(item);
			else values = values.withoutTimes(item,1);
		});
		return ret;
	},
	
	findTimes: function(times,iterator){
		// encuentra hasta times ocurrencias buscadas
		return this.select(this.timesIterator(times,iterator));
	},
	
	////////////////////////////////////////////////////////////////

	
	timesIterator: function(times,iterator){
		//devuelve un closure con un iterator que a partir de encontrar n veces el iterator, devuelve siempre false;
		return function(item){
			var ret = (times>0) && (iterator||Prototype.K)(item);
			if (ret) times--;
			//if(times==0) throw $break;
			return ret;
		}
	},
	
	atLeast: function(times,iterator){
		//se cumple el iterador al menos n veces? es como all , o any pero con un número
		return this.select(Enumerable.timesIterator(times,iterator)).size()>=times;
	},
	
	sum: function(){
		//sums every number element from the array.
		return this.inject(0,function(accum,item){return accum+item})
	},
	
	groupBy: function(iterator){
		var ret = [];
		this.each(function(element){
			var groupName = iterator(element);
			var group = ret[groupName];
			if(!group){
				group = [];
				ret.push(group);
				ret[groupName] = group;
			}
			group.push(element);
		});
		return ret;
	},
	
	shuffle: function(sortingKey,unshuffle){
		//si se pasa sortingKey:String, el array será mezclado de acuerdo a esa cadena
		if(unshuffle){
			return this.shuffle($R(0,this.size()-1).toArray().shuffle(sortingKey).join(""));
		};
		
		return this.toArray().sortBy(function(i,index){
			return sortingKey ? sortingKey.charAt(index%sortingKey.length) : Math.random();
		});
	},
	
	toEnumeratedString:function(joiner){
		var array=this.toArray();
		if(array.size()==1) return array.join();
		var last=array.pop();
		return[array.join(", "),last].join(" "+(joiner||Choquin.Translations.y)+" ");
	},
	
	////////////////////////////////
	find: Enumerable.find.wrap(function(proceed,iterator,context){
		//upgrade a Enumerable.find para que pueda utilizar objetos en reemplazo de funciones iterator.
		// ej: [{nombre:'juan',edad:32},{nombre:'pirulo',edad:24},{nombre:'ana',edad:72}].find({nombre:'pirulo'}) => {nombre:'pirulo',edad:24}
		if(typeof iterator == "function") return proceed (iterator,context);
		else {
			var options = iterator;
			return proceed(function(element){
				var ret = true;
				for (var i in options){
					ret = ret && element[i]==options[i];
				}
				return ret;
			},context);
		}
	})

});

Object.extend(Enumerable,Choquin.Enumerable);
Object.extend(Array.prototype,Choquin.Enumerable);


Object.extend(Array.prototype,{
	pushFirst:function(what){
		var oldArray = [].concat(this);
		var newArray = [what].concat(this);
		this.length = 0;
		Object.extend(this,newArray);
		return this;
	},
	
	pushArray:function(array){
		if(!array) return;
		this.push.apply(this,array);
	},
	
	wrap: function(first,last){
		//add first as the first object, and last as the last object (or first on both if last is not passed)
		if($A(arguments).size()==1) last=first;
		this.unshift(first);
		this.push(last);
		return this;
	}


});


////////////////

Object.extend(Number.prototype,{
	parseInt: function(radix){
		return Number(parseInt(this,radix));

	},
	
	toDecimalsPartString: function(){
		//devuelve un string con los decimales de este número
		var executed = /\.(\d+)/.exec(this);
		if(executed && executed.length>1) return executed[1];
		else return "";
	},
	
	decimals:function(val,separator){
		//tiene en cuenta el redondeo
		/*este nombre está mal... debería ser algo así como toDecimalString
		*/
		var separator = separator || Number.decimalSeparator;
		var val2 = Math.pow(10,val);
		var ret = String(Math.round(this*val2)/val2);
		var posDot = String(ret).indexOf(".");
		var integer = String(parseInt(ret));
		var decimals = "";
		if (posDot==-1) decimals = "0".repeat(val);
		else decimals = ret.substr(posDot+1,ret.length);
		decimals+="0".repeat(val-decimals.length);		
		return [integer,decimals].without("").join(separator);
	},
	
	trimDecimals:function(val,separator){
		/* debería llamarse toTrimmedDecimalsString
		deja el número con menor cantidad de decimales
		*/
		var separator = separator || Number.decimalSeparator;
		var val2 = Math.pow(10,val);
		var ret = String(Math.round(this*val2)/val2);
		var posDot = String(ret).indexOf(".");
		var integer = String(parseInt(ret));
		var decimals = posDot!=-1?ret.substr(posDot+1,ret.length):null;
		return [integer,decimals].compact().join(separator);
	},

	
	currency:	function (symbol,decimals) {
		var options = {};
		if(typeof(symbol)=='object'){ //symbol es un objeto con propiedades donde están las opciones para aplicar a currency
			Object.extend(options,symbol);
		} else {
			options.symbol = symbol;
			options.decimals = decimals;
		}
		
		if(!options.symbol) options.symbol="$";
		if(!options.decimals) options.decimals = Number.currencyDecimals;
		options.thousandsSeparator = options.thousandsSeparator || Number.thousandsSeparator;
		options.decimalSeparator = options.decimalSeparator || Number.decimalSeparator;
		
		var trimmedNumber = this.decimals(options.decimals,options.decimalSeparator);
		
		var splittedTrimmedNumber = trimmedNumber.split(options.decimalSeparator); //separo entero de decimal
		var integerPart = parseInt(splittedTrimmedNumber[0]);
		var decimalsPartString = splittedTrimmedNumber[1] || 0;
		
		var integerPartString = String(integerPart).toArray().reverse().inGroupsOf(3).collect(function(g){return g.reverse().join("")}).reverse().join( options.thousandsSeparator );
		
		return options.symbol+" "+integerPartString + options.decimalSeparator + decimalsPartString;
	},
	
	constrain: function(nMin,nMax){
		return [[this,nMin].max(),nMax].min();
	},
	
	isOdd:function (){
		return (Math.pow(-1,this)<0);
	},
	
	isEven:function(){
		return !this.odd();
	},
	
	sign:function(){
		return this==0?0:this>0?1:-1;
	},
	
	toPixels: function(){
		return this+"px"; 
	},
	
	loopedRange: function(start,end){
		// if this number is outside start,end, if gets looped on that range. 
		// for example, 3.loopedRange(5,10) = 9. Because 4 falls outside range, it goes to the end and go backwards: 4=10 , 3=9
		var range = end-start+1;
		var newValue;
		newValue = this-start;
		newValue = newValue%range;
		if(newValue<0) newValue = range+newValue;
		return start+newValue;
		//if(this<0)
	},
	
	integer2Ip: function(){
		var A,B,C,D;
		var num = this;
		A = num % 256;
		num = Math.floor(num/256);
		B = num%256;
		num = Math.floor(num/256);
		C = num%256;
		num = Math.floor(num/256);
		D = num%256;
		return [D,C,B,A].join(".");
	}, 
	
	toOrdinalString: function(){
		// 1: primero, 2: segundo, etc
		var ret = '';
		switch (Number(this)){
			case 1: ret = 'primero'; break;
			case 2: ret = 'segundo'; break;
			case 3: ret = 'tercero'; break;
			case 4: ret = 'cuarto'; break;
			case 5: ret = 'quinto'; break;
			case 6: ret = 'sexto'; break;
			case 7: ret = 'septimo'; break;
			case 8: ret = 'octavo'; break;
			case 9: ret = 'noveno'; break;
			case 10: ret = 'décimo'; break;
		}
		return ret;
	}
	

});


Object.extend(Number,{
	currencyDecimals: 2,
	thousandsSeparator: ',',
	decimalSeparator: '.',
	
	b2d: function (binary){
		var ret = 0;
		$A(binary).reverse().each(function(digit,index){
			var val = digit*Math.pow(2,index);
			ret+=val;
			//console.debug(digit,val);
		});
		return ret;
	},

	d2b: function (decimal,digits){
		var ret = "";
		var value = decimal;
		if(decimal==0) ret = "0";
		else{
			while(value>=1) {
				value = Math.floor(value);
				ret = (value % 2) + ret;
				value/=2;
			}
		}
		if(digits) ret = ret.fixLength(digits,"0");
		return ret;
	}

});



Object.extend(String.prototype,{
	decimals: function(n){
		var num = Number(this);
		if(num) return num.decimals(n);
		else return "";
	},
	
	checkFileExtension:function(extensiones){
		//extensiones es un string o un array
		var archivo = this;
		var ret=false;
		if(extensiones && typeof extensiones=="string") extensiones=new Array(extensiones);
		if(!extensiones) ret=true;
		else{
			ret = archivo.ext() && ((extensiones.join(",").toLowerCase()+",").indexOf(archivo.ext().toLowerCase()+',')) >=0;
		}
		return ret;
	},
	
	ext : function(){
		var posicion = this.lastIndexOf(".");
		if(posicion==-1) return null;
		return (this.substr(posicion+1)).toLowerCase();
	},

	removeExt : function(){
		///devuelve el nombre del archivo sin la extensión
		var posicion = this.lastIndexOf(".");
		if(posicion==-1) return this;
		return (this.substr(0,posicion)).toLowerCase();
	},

	path : function(){
		//devuelve el path de un archivo
		var pos = this.lastIndexOf("/");
		if(pos==-1) 
		var pos = this.lastIndexOf("\\");
		if(pos==-1) return this;
		return this.substr(0,pos+1);
	},

	filename : function(){
		//devuelve el nombre del archivo sin el path
		var pos = this.lastIndexOf("/");
		if(pos==-1) 
		var pos = this.lastIndexOf("\\");
		if(pos==-1) 
		return this;
		return this.substr(pos+1);
	},
	
	testWildcards: function(wildcards){
		wildcards = "^"+wildcards.reemplazar("*","[\\w-\s]*")+"$";
		var re = new RegExp(wildcards,"i");
		return re.test(this);
	},
	
	fixLastCharacter : function (character){
		//si character no es el último caracter de este string, se lo agrega y devuelve el resultado sin modificar el string original
		if(this.substr(this.length-1,1)!=character) return this+character;
		else return this;
	}, 
	
	newFilename : function (){
		var re = /(.+)\((\d*)\)/ig;
		var arr = re.exec(this);
		var newFile;
		if(arr) 
			newFile = this.path()+arr[1].filename()+"("+Number(arr[2]).succ()+")"+ (this.ext()?"."+this.ext():"");
		else newFile = this.path()+this.filename().removeExt()+"(1)"+(this.ext()?"."+this.ext():"");
		return newFile;
	},

	trim : function(caracteres){
		//caracteres es el caracter o array de caracteres posibles que se descarta/n del principio y fin
		if (caracteres==null) caracteres = " ";
		var returnString = $A(this);
		caracteres = $A(caracteres);
		//if(typeof caracteres == "string") caracteres = [caracteres];
		for (var i = 0;i<caracteres.length;i++){
			var c = caracteres[i];
			while(returnString[0]==c && returnString.length>0) returnString.shift();
			while(returnString[returnString.length-1]==c && returnString.length>0) returnString.pop();
		}
		return returnString.join("");
	},
	
	fixQuotes : function (c){
		if(c==null) c = "'";
		var re = new RegExp(c,"g");
		var ret = this.replace(re,"\\"+c);
		return ret;
	},
	
	parseFloat: function(){
		//var ret = this.replace(/,/g,".");
		//return ret;
		return parseFloat(this);
	},
	
	parseInt:function(){
		var re = /\d+/ig;
		return re.exec(this);
	},
	
	minusFirst: function(str,fnBusqueda){
		var str = String(str);
		fnBusqueda = fnBusqueda || String.prototype.indexOf;
		var p = fnBusqueda.call(this,str);
		if(p==-1) return this;
		var parte1 = this.substr(0,p);
		var parte2 = this.substr(p+str.length,this.length);
		return String(parte1+parte2);
	},
	
	minusLast:function(str){
		return this.minusFirst(str,String.prototype.lastIndexOf)
	},
	
	trimFromLast:function(what,offset){
		var offset=offset||0;
		var found=this.lastIndexOf(what);
		return found==-1? String(this):this.substr(0,found+offset)
	},
	
	trimFromFirst:function(what,offset){
		var offset=offset||0;
		var found=this.indexOf(what);
		return found==-1 ? String(this) : this.substr(0,found+offset)
	}
	
	,trimToFirst:function(what,offset){
		var offset=offset||0;
		var found=this.indexOf(what);
		return found==-1 ? "" : this.substr(found+what.length+offset)},trimToLast:function(what,offset){var offset=offset||0;
		var found=this.lastIndexOf(what);
		return found==-1 ? String(this) : this.substr(found+what.length+offset);
		return found==-1 ? "" : this.substr(found+what.length+offset)
	},
	
	parseFilePath:function(){
		var path=this;
		["\\","/"].each(function(bar){
			for (var found = path.indexOf(bar+".."); found != -1; found = path.indexOf(bar+"..")){
				var npath = path.substr(0,found);
				path = npath.trimFromLast(bar)+path.substr(found+3)
			}
		});
		return path.toString();
	},
	
	repeat: function(times){
		var ret = "";
		for (var i=1;i<=times;i++) ret+=this;
		return ret;
	},
	
	toNull: function(){
		if(this=="") return null;
		return this;
	},
	
	p:function(){
		return "<p>"+this+"</p>";
	},
	
	functionBody:function(){
		return this.substr(13,this.length-14);
	},
	
	functionParameters:function(){
		var primParentesis = this.indexOf("(");
		var segParentesis  = this.indexOf(")");
		if(primParentesis == -1 || segParentesis == -1) return;
		return this.substring(primParentesis+1,segParentesis);
	},
	
	functionName:function(){
		var primParentesis = this.indexOf("(");
		var functionString = this.indexOf("function ");
		var comienzo = functionString!=-1?functionString.length:0;
		if(primParentesis==-1) primParentesis = this.length;
		return this.substring(comienzo,primParentesis);
	},
	
	findToRegExp:function(){
		var texto = this;
		texto = texto.trim(",");
		texto = texto.split(",").inject([],function(what,i){
			what.push("("+i.fixRegExp().sinTildesRegExp()+")");
			return what;
		}).join("|");
		texto = "(^|\\s)"+texto;
		var pattern = new RegExp(texto,"i");
		return pattern;
	},

	
	queryString:function(){
		var pos = this.indexOf("?");
		if(pos==-1) return "";
		return this.substr(pos+1,this.length);
	},
	
	url:function(){
		var pos = this.indexOf("?");
		if(pos==-1) return this;
		return this.substr(0,pos);
	},
	
	urlProtocol: function(){
		var bb = this.indexOf("//");
		var protocol = "";
		if(bb!=-1 && bb<8) protocol = this.substr(0,bb+2);
		return protocol;
	},
	
	urlDomain:function(){
		var url = this.url().substr(this.urlProtocol().length);
		var length = url.indexOf("/");
		var domain = length>0 ? url.substring(0,length) : url;
		return domain;
	},
	
	locationProtocol:function(){
		return this.trimFromFirst(":",1);
	},
	
	locationHost:function(){
		return this.trimToFirst(this.locationProtocol()+"//").trimFromFirst("/");
	},
	
	locationHostName:function(){
		return this.locationHost().trimFromLast(":");
	},
	
	locationSearch:function(){
		var pos=this.indexOf("?");
		if(pos!=-1) return this.substr(pos);
		else return"";
	},
	
	locationPathname:function(){
		return this.trimToFirst(this.locationHost()).trimFromLast(this.locationSearch()).trimFromFirst("#");
	},
	
	locationHash:function(){
		return this.trimToFirst('#',-1);
	},
	
	fixLength:function(length,char,atRight){
		if(!char)char=" ";
		var ret = this;
		if(this.length>length) {
			if(atRight) ret = this.substr(0,length);
			else ret = this.substr(this.length-length,length);
		}
		else{
			var fix = char.repeat(length-this.length);
			if(atRight) ret +=fix;
			else ret = fix+ret;
		}
		return ret;
	},
	
	reemplazar: function(cadena,nuevacadena){
		var arr=this.split(cadena);
		return arr.join(nuevacadena);
	},
	
	before: function(str,_last){
		//return the characters before str, or before last str
		var p = this[_last?"lastIndexOf":"indexOf"](str);
		if(p==-1) return "";
		return this.substr(0,p);
	},
	
	after: function(str,_last){
		//return the remaining characters after str, or after last str
		var p = this[_last?"lastIndexOf":"indexOf"](str);
		if(p==-1) return "";
		return this.substr(p+1);
	},
	
	fixRegExp: function(){
		var re = /\.|\*|\|/ig;
		return cadena.replace(re,function(what){return "\\"+what});
	},
	
	sinTildesRegExp:	function(){
		var sintildes = {"a":"á","e":"é","i":"í","o":"ó","u":"ú"};
		var contildes = {"á":"a","é":"e","í":"i","ó":"o","ú":"u"};
		var todo = Object.extend(Object.extend({},sintildes),contildes);
		var retext = [];
		for (var i in sintildes) retext.push("("+i+"|"+sintildes[i]+")");
		var re = new RegExp(retext.join("|"),"ig");
		return this.replace(re,function(what){
			return "[" + what.toLowerCase() + "|" + todo[what.toLowerCase()] + "]";
		});
	},
		
	sinTildes:function(){
		var sintildes={"a":"á","e":"é","i":"í","o":"ó","u":"ú"};
		var ret=this;
		for(i in sintildes){
			ret=ret.gsub(sintildes[i],i);
			ret=ret.gsub(sintildes[i].toUpperCase(),i.toUpperCase());
		};
		return ret;
	},
	
	getURIcontent:function(){
		return this.minusFirst("url(").minusFirst("\"").minusLast("\"").minusLast(")")
	},
	
	tag:function(tagName,options){
		var options=options||{};
		var result=["<"+tagName];
		for(var i in options)result.push(" "+i+"=\""+options[i]+"\"");
		result=result.concat([">",this,"</"+tagName+">"]);
		return result.join("");
	},
	
	link:function(href,options){
		return this.tag("a",Object.extend({href:href},options));
	},
	
	big:function(){
		return this.tag("big");
	},
	
	italics:function(){
		return this.tag("i");
	},
	
	lastCharacter:function(){
		return this.substr(this.length-1);
	},
	
	firstCharacter:function(){
		return this.substr(0,1);
	},
	
	toVarName:function(){
		return this.toLowerCase().sinTildes().replace(/\s+/ig,'-').camelize();
		/*return this.dasherize().camelize().sub(/\w/,function(match){
			return match[0].toLowerCase();
		});*/
	},
	
	truncateBefore:function(length,truncation){
		length=length||30;
		truncation = Object.isUndefined(truncation) ? '...' : truncation;
		return this.length > length ?
			truncation + this.slice(this.length-length + truncation.length): String(this);
	},
	
	equals: function(other){
		return this == other;
	},
	
	shuffle: function(shuffleKey,reverse){
		return $A(this).shuffle(shuffleKey,reverse).join("");
	},
	
	substrRight: function(count){
		var start = this.length - count;
		var length = count;
		if(start<0) {
			start = 0;
			length = this.length;
		}
		return this.substr(start,length);
	},

	ip2Integer: function(){
		var ip = this.split(".").collect(function(i){return parseInt(i)});
		return (ip[0] * Math.pow(256,3)) + (ip[1] * Math.pow(256,2)) + (ip[2] * Math.pow(256,1)) + (ip[3]);
	},
	
	stripStyles:function(){
		return this.replace(new RegExp(Choquin.StyleFragment, 'img'), '');
	},
	
	stripComments: function(){
	    return this.replace(new RegExp(Choquin.CommentFragment, 'img'), '');
	},
	
	html2Text: function(){
		return result = this.stripComments().stripScripts().stripStyles().stripTags().replace(/[\t]/img,'').replace(/[\n\r]+/img,'\n').replace(/([\n\r\b\t\s])+/ig,'$1');
	}

	
});


Object.extend(String.prototype,{
	isEmail:function (){
		var reMail=new RegExp("^[\\w\.=-]+@[\\w\\.-]+\\.[a-z]{2,4}$");
		return reMail.test(this);
	},
	
	isNumber:function(){
		var re = /^(-)?\d+([.,]\d+)?$/;
		return re.test(this);
	},
	
	isFloat:function(){
		var re = /^(-)?\d+([.,]\d+)$/;
		return re.test(this);
	},
	
	isInteger:function(){
		var re = /^(-)?\d+$/;
		return re.test(this);
	},

	isPositiveInteger:function(){
		var re = /^(-)?\d+$/;
		return re.test(this)&&Number(this)>0;
	},
	
	isComodin:function(){
		return this.indexOf("***")!=-1||this.indexOf("$$$")!=-1;
	},
	
	isDate:function(){
		return!!this.match(/^\d{1,2}\/\d{1,2}\/\d{4}(\s\d{1,2}:\d{1,2}(:\d{1,2})?)?$/)
	},
	
	isValidCUIT: function() {
		var cuit = String(this);
		cuit = cuit.toString().replace(/[-_]/g, "");
		if (cuit == '') return true; //No estamos validando si el campo esta vacio, eso queda para el "required"
		if (cuit.length != 11) return false;
		else {
			var mult = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2];
			var total = 0;
			for (var i = 0; i < mult.length; i++) {
				total += parseInt(cuit.charAt(i)) * mult[i];
			}
			var mod = total % 11;
			var digito = mod == 0 ? 0 : mod == 1 ? 9 : 11 - mod;
		}
		return digito == parseInt(cuit.charAt(10));
	}
	

});


Object.extend(String,{
	null2Empty : function (val){
		if(val==null||val==undefined) val= "";
		return String(val);
	},
	
	randomLetter:function(primerLetra,ultimaLetra){
		var codeA = (primerLetra||"a").charCodeAt(0);
		var codeZ = (ultimaLetra||"z").charCodeAt(0);
		return String.fromCharCode(codeA+Math.round(Math.random()*(codeZ-codeA)));
	},

	randomNumber:function(){
		return this.randomLetter("0","9");
	},

	randomString:function(cant){
		var ret;
		var options = Object.extend({},options);
		if(!cant)cant=1;
		var arr= new Array();
		var codeA="a".charCodeAt(0);
		var codeZ="z".charCodeAt(0);
		var code0="0".charCodeAt(0);
		var code9="9".charCodeAt(0);
		
		if(!options.onlyNumbers) for (var i=codeA;i<=codeZ;i++) arr.push(String.fromCharCode(i));
		if(!options.onlyLetters) for (var i=code0;i<=code9;i++) arr.push(String.fromCharCode(i));
	
		ret="";
		for(var i=0;i<cant;i++) ret+=arr[Math.round(Math.random()*(arr.length-1))];
		return ret;
	},
	
	fixLength:function(obj,quantity,char,atRight){
		return String(obj).fixLength(quantity,char,atRight);
	}	
});


////////////////////////////////////////////////
Object.extend(Date,{
 	arrDayNames : ["domingo","lunes","martes","miércoles","jueves","viernes","sábado"],
	dayName:function(number){
		return this.arrDayNames[number];
	},
	
	fromString: function(text){
	//convierte un texto a un objeto Date
	
		var YYYY,dd,MM,hh,mm,ss;
		var hh=mi=ss = 0;
		
		if(String(text).length==4){
			///supongo que pas slo el ao
			text = new Date(text,0,1,0,0,0); 
			return text;
		}
	
		if (String(text).indexOf("/")!=-1){
			text = String(text);
			ddp = text.indexOf("/");
			dd = text.substr(0,ddp);
			mm = text.substr(ddp+1,text.length);
			mmp = mm.indexOf("/");
			mm = mm.substr(0,mmp);
			
			/*if (Application("language")=="en"){
				mm1 = mm;
				mm = dd; 
				dd = mm1;
			}*/
	
			mm = Number(mm)-1;
	
			
			ultao = text.indexOf(" ");
			if (ultao == -1) {
				ultao = text.length;
			}
			ultao-=ddp+mmp+2;
			
			yyyy = text.substr(ddp+mmp+2,ultao);
			yy = yyyy.substr(2,4);
		
			
			var espacio = text.lastIndexOf(" ");
			var dospuntos = text.indexOf(":");
			if (dospuntos==-1) dospuntos=text.indexOf(".");
			if (espacio>0 && dospuntos>0){
				var dospuntos2 = text.lastIndexOf(":");
				hh = Number(text.substr(espacio+1,dospuntos-espacio-1));
				var finmi = dospuntos2>dospuntos?dospuntos2:text.length;
				//var finmi=dospuntos2!=-1?dospuntos2-dospuntos-1:text.length;
				mi = Number(text.substring(dospuntos+1,finmi));
				if(dospuntos2>dospuntos) 
					ss = Number(text.substr(dospuntos2+1,text.length));
				//WriteLn(hh+":"+mi+":"+ss+"\n"+dospuntos+" "+finmi);
			}

			text = new Date(yyyy,mm,dd,hh,mi,ss); /////////////cambiado en ISHYR
			
		}
		else text = new Date(text);
		return text;
	},
	
	format: function(date,format){
		if(!date) return "";
		if (!format) {
			format="dd/mm/yyyy";
			//if (Application("language")=="en") format="mm/dd/yyyy";
			//else format = "dd/mm/yyyy";
		}
		
		var fec = date;
		var yyyy = fec.getFullYear();
		var dd = String.fixLength(fec.getDate(),2,"0");
		var mm = String.fixLength(fec.getMonth()+1,2,"0");
		var hh = String.fixLength(fec.getHours(),2,"0");
		var nn = String.fixLength(fec.getMinutes(),2,"0");
		var ss = String.fixLength(fec.getSeconds(),2,"0");
		var ds = Date.dayName(fec.getDay());
		
		ret = format;
		
		var re = /ddss/ig;
		ret = ret.replace(re,ds);
		var re = /dd/ig;
		ret = ret.replace(re,dd);
		var re = /mm/ig;
		ret = ret.replace(re,mm);
		var re = /yyyy/ig;
		ret = ret.replace(re,yyyy);

		var re = /hh/ig;
		ret = ret.replace(re,hh);
		var re = /nn/ig;
		ret = ret.replace(re,nn);
		var re = /ss/ig;
		ret = ret.replace(re,ss);
		var re = /ds/ig;
		ret = ret.replace(re,ds.substr(0,2));
	
		return(ret);
	}
	
});

Object.extend(Date.prototype,{
	format: Date.format.methodize(),
	
	
	addMinutes:function(min){
		var ret = new Date(this);
		return new Date(ret.setMinutes(this.getMinutes()+min));
	},
	
	addSeconds:function(val){
		var ret = new Date(this);
		return new Date(ret.setSeconds(this.getSeconds()+val));
	},
	
	addHours:function(val){
		var ret = new Date(this);
		return new Date(ret.setHours(this.getHours()+val));
	},
	
	addDays:function(val){
		var ret = new Date(this);
		return new Date(ret.setDate(this.getDate()+val));
	},
	
	addMonths:function(val){
		var ret = new Date(this);
		return new Date(ret.setMonth(this.getMonth()+val));
	},
	
	addYears:function(val){
		var ret = new Date(this);
		return new Date(ret.setFullYear(this.getFullYear()+val));
	},
	
	isValidDate:function (){
		return !isNaN(this.getFullYear());
	}
	
});

///////////////////////////////////////////////////////////
Object.extend(Function.prototype, (function() {
	var slice = Array.prototype.slice;
	
	function update(array, args) {
		var arrayLength = array.length, length = args.length;
		while (length--) array[arrayLength + length] = args[length];
		return array;
	};
	
	function merge(array, args) {
		array = slice.call(array, 0);
		return update(array, args);
	};
	
	function delayedCurry (timeout){
		var __method = this, args = slice.call(arguments, 1);
		timeout = timeout * 1000;
		return function(){
			var a = merge(args, arguments);
			window.setTimeout(function() {
				return __method.apply(__method, a);
			}, timeout);
		}
	};

	return {
		delayedCurry: delayedCurry
	};
})());

////////////////////// AJAX ///////////////////////////////

///////////////////////////////////////////////////////////
Object.extend(Ajax.Responders,{
	callInProgress: function(xmlhttp) {
		switch (xmlhttp.readyState) {
			//0 = uninitialized
			//1 = loading
			//2 = loaded
			//3 = interactive
			//4 = complete
			case 1: case 2: case 3:
				return true;
				break;
			// Case 4 and 0
			default:
				return false;
				break;
		}
	}
});

////////////////

Ajax.Request.addMethods({
	abort: function(){
		//call onAbort listener, if existent
		(this.options.onAbort || Prototype.emptyFunction)(this.transport, this.json);
		var response = new Ajax.Response(this);
		Ajax.Responders.dispatch('onAbort', this, response , response.headerJSON);
		//trash existiting handlers
		this.options={};
		//now abort the request
		this.transport.abort();
	}
});




//////////////////////  FORM  /////////////////////////////
Choquin.createNamespace("Choquin.Form.Methods");
Choquin.createNamespace("Choquin.Form.Element.Methods");

Choquin.Form.Methods = {
	validate:function(form,options){
		/**
		options.rules 
		options.cssClassError
		options.onError
		options.onValidate
		*/
		form = $(form);
		try{
			var options = options||{};
			var elements = options.rules ? form.getElements() : form.getElements();//form.select('[choquin:onvalidate]').concat (form.select('[onvalidate]'));
			var ret = true;
			var focused=false;
			var errorElements = [];
			var lastElement = null;
			elements.each(function(element){
				element = $(element);
				lastElement = element;
				element.validate(options);
				if(element.error){
					errorElements.push(element);
					
					if(!focused){
						var scrollElm = (element.parentNode && element.parentNode.identify() == element.identify() + '_container') ? element.parentNode : element;
						if(!scrollElm.isInViewport()){
							if(window.Effect && window.Effect.ScrollTo) {
								window.Effect.ScrollTo(scrollElm,{afterFinish:function(){
									element.focus();
								}});
							} else {
								element.focus();
								scrollElm.scrollTo();
							};
						} else element.focus();

						focused=true;
					};

				};
				ret = ret && !element.error;
			});
			if (!ret) {
				(options.onError||Prototype.emptyFunction)({form:form,errorElements:errorElements});
			} else {
				(options.onValidate||Prototype.emptyFunction)({form:form});

			}
		}catch(e){
			alert("Form.validate Error: "+e.message+ (lastElement?lastElement.name:''));
			window.console && window.console.error(lastElement);
			ret = false;
		}
		finally{
			form[ret?'addClassName' : 'removeClassName']('validated');
			form[ret?'addClassName' : 'removeClassName' ] (options.cssClassError || 'error');
			return ret;
		}
	}
};

Object.extend(Form,Choquin.Form.Methods); 
Element.addMethods("form",Choquin.Form.Methods);



Choquin.Form.Element.Methods = {
	validate: function(element,options){
		var options = options||{};
		element = $(element);

		var validation = element.onvalidate || element.readAttribute("choquin:onvalidate") || element.readAttribute("onvalidate")||element.readAttribute("onValidate") || (options.rules && options.rules[element.readAttribute('name')]);
		if(validation){
			//testear el valor del elemento..
			element.error = !Choquin.eval.bind(element)(validation);
				
			var onerror = element.readAttribute("onvalidate:error");
			var onok = element.readAttribute("onvalidate:ok");
			if(element.error && onerror) Choquin.eval.bind(element)(onerror);
			if(!element.error && onok) Choquin.eval.bind(element)(onok);
			element[element.error?'addClassName':'removeClassName'](options.cssClassError||'error');
			if(element.handler) element.handler.onvalidate(element,options);
			var messageElement = $(element.identify()+'_message');
			if(messageElement) messageElement[element.error?'addClassName':'removeClassName']('visible');
		}
	},
			  
	getValues: function(element) {
		///escribí esta nueva función para que soporte varios elementos con el mismo name, y devuelve SIEMPRE un array
		element = typeof element=='string' ? $$('#'+element):element;
		var ret= [];
		if(!element || element.length==0) return ret;
		if(element.options || !element.length) element=[element];
		for(var i=0;i<element.length;i++){
			var method = element[i].tagName.toLowerCase();
			var parameter = Form.Element.Serializers[method](element[i]);
			if (parameter){
				if(parameter.push)	ret = ret.concat(parameter);
				else ret.push(parameter);
			}
		}
		return ret;
	},

	setValues: function(element,value,nocase) {
		var old = element;
		element = $(element);
		if(!element) return;
		if(!element || element.options || !element.length) element=[element];
	
		for(var i=0;i<element.length;i++){
			var method = element[i].tagName.toLowerCase();
			switch(method){
				case 'input':
					switch(element[i].type.toLowerCase()){
						case 'submit':
						case 'hidden':
						case 'password':
						case 'text':
							element[i].value=value||"";
						break;
						
						case 'checkbox':
						case 'radio':
							var val=value;
							if(typeof val=="boolean") element[i].checked = val;
							else{
								if(!val || !val.any) val=$A(String(value));
								element[i].checked = val.any(function(node){return node==element[i].value});
							}
						break;
					}
				break;
				
				case 'textarea':
					element[i].value=value||"";	
				break;
				
				case 'select':
					var val=value;
					if(val==null || val==undefined) {
						if(element[i].type == 'select-one') {
							element[i].selectedIndex=0;
							continue;
						}
						else val=[];
					}
					if(!val.any) val=String(val).split(',');//val=$A(String(value));
					for(var i2=0;i2<element[i].options.length;i2++){
						var value = element[i].options[i2].value;
						var sel = !val.length?false:val.any(function(node){ return nocase? String(node).toLowerCase()==String(value).toLowerCase() : node==value});
						element[i].options[i2].selected = sel;
					}
				break;
			}
		}
		if (element[0].onchange) element[0].onchange();
		//llamo a la onchange ya que estamos...
		
		return element;
	},

	getValuesFromName: function(name){
		return $$('[name="'+name+'"]').invoke("getValue").flatten();
	}
};

Object.extend(Form.Element, Choquin.Form.Element.Methods);


Element.addMethods(["textarea","input","select"],Choquin.Form.Element.Methods);

$FN = Choquin.Form.Element.Methods.getValuesFromName;



///////// ELEMENT ////////////////////
Choquin.Element = {};
Choquin.Element.Methods = {
	showhide:function(element){
		//recibe elm1,elm2...,elmn,visibility,elma,elm4b,elmc,....elmx,visibility
		var last = [];
		arr={"true":"show","false":"hide"};
		for (var i=0;i<arguments.length;i++){
			if(arguments[i].constructor==Boolean) {
				last.invoke(arr[arguments[i]]);
				last=[];
			} else last.push($(arguments[i]))
		};
		return element;
	},
	
	toSWFObject:function(element){
		if(typeof swfobject=="undefined"){
			console.debug('swfobject is unregistered.');
			return;
		}
	
		var flash=$(element);
		if(!flash.id) flash.id = "flashcontent_"+String.randomString(20);
	
		var attributes = flash.readAttribute("attributes");
		var width = flash.readAttribute("width")||flash.style.width;
		var height = flash.readAttribute("height")||flash.style.height;
		var bgColor = flash.readAttribute("background-color");
		var version = flash.readAttribute("version")||"8";
		var src= flash.readAttribute("src");
		var wmode = flash.readAttribute("wmode");
		var quality = flash.readAttribute("quality")||"high";
		var flashvars = flash.readAttribute("flashvars")||"";
		flashvars = flashvars.split("&");
		var params={wmode:wmode,quality:quality,bgcolor:bgColor};
		var flashvarsObj={};
		flashvars.each(function(flashvar){
			var flashvar=flashvar.split("=");
			flashvarsObj[String(flashvar[0])]=String(flashvar[1]);
		});
		swfobject.embedSWF(src,flash.identify(),width,height,version,'expressInstall.swf',flashvarsObj,params,attributes);
	},
	
	toHTML:function(element){
		if(typeof element=='string')element=$(element);
		return Try.these(
			function() {
				var xmlSerializer = new XMLSerializer();
				return  element.nodeType == 4 ? element.nodeValue :
				xmlSerializer.serializeToString(element);
			},
			function() {
			  return element.xml || element.outerHTML || $(element).clone().wrap().up().innerHTML;
			}
		  ) || '';
	
	},
	
	getStyles: function(element) {
	  element = $(element);
	  return $A(element.style).inject({}, function(styles, styleName) {
		styles[styleName.camelize()] = element.getStyle( styleName );
		return styles;
	  } );
	
	},
	
	clone: function(element, deep) {
		if (!(element = $(element))) return;
		var clone = element.cloneNode(deep);
		clone._prototypeUID = void 0;
		if (deep) {
		  var descendants = Element.select(clone, '*'),
			  i = descendants.length;
		  while (i--) {
			descendants[i]._prototypeUID = void 0;
		  }
		}
		return Element.extend(clone);
	  },

	
	moveChildsTo: function(element,container){
		element = $(element),container = $(container);
		$A(element.childNodes).each(function(node){
			container.insert(element.removeChild(node));
		});
	},
	
	
	viewportOffset: function(forElement) {   //// FIX FOR OPERA !!!!
		var valueT = 0, valueL = 0;
		
		var element = forElement;
		do {
		valueT += element.offsetTop  || 0;
		valueL += element.offsetLeft || 0;
		
		// Safari fix
		if (element.offsetParent == document.body &&
		Element.getStyle(element, 'position') == 'absolute') break;
		
		} while (element = element.offsetParent);
		
		element = forElement;
		do {
			if (!Prototype.Browser.Opera || element.tagName == 'HTML') {
				valueT -= element.scrollTop  || 0;
				valueL -= element.scrollLeft || 0;
			}
		} while (element = element.parentNode);
		

		return Element._returnOffset(valueL,valueT)
	},
	
	addEnterLeave:function(element){
		element.observe('mouseover',function(evt){
			try{
				var parent=evt.relatedTarget;
				var element=this;
				while(parent&&parent!=element){
					try{parent=parent.parentNode}
					catch(e){parent=element};
				}
				if (parent != element)	this.fire("mouse:enter");
			}catch(e){}
		});
		element.observe('mouseout',function(evt){
			try{
				var parent=evt.relatedTarget;
				var element=this;
				while ( parent && parent != element ){
					try { parent = parent.parentNode }
					catch(e){ parent = element }
				}
				if(parent!=element) this.fire("mouse:leave")
			}catch(e){}
		});
	},
	
	getClientDimensions:function(element){element=$(element);
		var display=$(element).getStyle('display');
		if(display!='none'&&display!=null)return{width:element.clientWidth,height:element.clientHeight};
		var els=element.style;
		var originalVisibility=els.visibility;
		var originalPosition=els.position;
		var originalDisplay=els.display;
		els.visibility='hidden';
		els.position='absolute';
		els.display='block';
		var originalWidth=element.clientWidth;
		var originalHeight=element.clientHeight;
		els.display=originalDisplay;
		els.position=originalPosition;
		els.visibility=originalVisibility;
		return{width:originalWidth,height:originalHeight}
	},
	
	removeFromParent: function(element){
		//prototype tiene remove, pero no parece funcionar, no sé por qué
		element = $(element);
		if (element.parentNode) element.parentNode.removeChild(element);
		return element;
	},
	
	isInViewport:function(element){
		//returns if an element is in the visible portion of the view
		element = $(element);
		return $R(0,Window.getHeight()).include(element.viewportOffset().top) &&
			$R(0,Window.getWidth()).include(element.viewportOffset().left) 
	},
	
	getHeight: Element.Methods.getHeight.wrap(function(proceed,element){
		element = $(element);
		var overflow = element.getStyle('overflow');
		element.setStyle({overflow:'hidden'});
		var result = proceed(element);
		element.setStyle({overflow:overflow});
		return result;
		//console.debug('conchuda');
	}),
	
	getWidth: Element.Methods.getWidth.wrap(function(proceed,element){
		element = $(element);
		var overflow = element.getStyle('overflow');
		element.setStyle({overflow:'hidden'});
		var result = proceed(element);
		element.setStyle({overflow:overflow});
		return result;
		//console.debug('conchuda');
	}),
	
	copyAttributesFrom: function(element, source, attributesNames) {
		element = $(element);
		source = $(source);
		if (attributesNames) attributesNames = Object.toArray(attributesNames);
		/*sourceRegistry.each( function(pair) {
			var eventName = pair.key, responders = pair.value;
			if(eventsNames && !eventsNames.include(eventName)) return;
			responders.each( function(r) {
			  Element.observe(element,eventName,r.handler);
			  //Element.stopObserving(element, eventName, r.handler);
			});
		});*/
		attributesNames.each(function(attributeName){
			element.writeAttribute(attributeName,source.readAttribute(attributeName));
		});
		
		return element;
	},
	
	hasParentNode: function(element){
		element = $(element);
		return !!($(element.parentNode) && $(element.parentNode).childElements && $(element.parentNode).childElements().include(element));
	}

};

Element.addMethods(Choquin.Element.Methods);
Element.addMethods({
	setType:function(element,type){element=$(element);
		if(element.type == type) return element;
		var couldChange = false;
		//try{element.setAttribute('type',type);}catch(e){couldChange = false};
		if(!couldChange){
			// IE doesn't write type attribute...
			var clone = (element['_'+type] = element['_'+type] || element.clone({type:type}));
			element.replace(clone);
			clone['_'+element.type] = element;
			if(type!='file') clone.value = element.value;
			return clone;
		}
		return element;
	}
});

//Element.addMethods();

Element.addMethods('iframe',{
	getContentDocument:function(element){
		element=$(element);
		return element.contentDocument||element.contentWindow.document;
	},

	document: function(element) { //this isn't the best?
		element = $(element);
		if (element.contentWindow)
		  return element.contentWindow.document;
		else if (element.contentDocument)
		  return element.contentDocument;
		else
		  return null;
	},

	$: function(element, frameElement) { //well neither is this really
		element = $(element);
		var frameDocument = element.document();
		if (arguments.length > 2) {
		  for (var i = 1, frameElements = [], length = arguments.length; i < length; i++)
			frameElements.push(element.$(arguments[i]));
		  return frameElements;
		}
		if (Object.isString(frameElement))
		  frameElement = frameDocument.getElementById(frameElement);
		return frameElement || element;
	}
});



Element.addMethods('select',{
	appendOption: function(element,option){
		//to do
	},

	getSelectedOption: function(element){
		element = $(element);
		if (element.type=="select-multiple"){
			return $A(element.options).select(function(option){return option.selected})
		}
		else return element.options[element.selectedIndex];
	}

});

//alert('métodos agregados al select');

Object.extend(Form.Element.Methods,{
});


///////////////////////////////////////////

/**
* Event.simulate(@element, eventName[, options]) -> Element
*
* - @element: element to fire event on
* - eventName: name of event to fire (only MouseEvents and HTMLEvents interfaces are supported)
* - options: optional object to fine-tune event properties - pointerX, pointerY, ctrlKey, etc.
*
* $('foo').simulate('click'); // => fires "click" event on an element with id=foo
*
**/
(function(){
  
  var eventMatchers = {
    'HTMLEvents': /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
    'MouseEvents': /^(?:click|mouse(?:down|up|over|move|out))$/
  };
  
  var defaultOptions = {
    pointerX: 0,
    pointerY: 0,
    button: 0,
    ctrlKey: false,
    altKey: false,
    shiftKey: false,
    metaKey: false,
    bubbles: true,
    cancelable: true
  };
  
  Event.simulate = function(element, eventName) {
    var options = Object.extend(Object.clone(defaultOptions), arguments[2] || { });
    var oEvent, eventType = null;
    
    element = $(element);
    
    for (var name in eventMatchers) {
      if (eventMatchers[name].test(eventName)) { eventType = name; break; }
    };
 
    if (!eventType)
      throw new SyntaxError('Only HTMLEvents and MouseEvents interfaces are supported');
 
    if (document.createEvent) {
      oEvent = document.createEvent(eventType);
      if (eventType == 'HTMLEvents') {
        oEvent.initEvent(eventName, options.bubbles, options.cancelable);
      } else {
        oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView,
          options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
          options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element);
      };
      element.dispatchEvent(oEvent);
    } else {
      options.clientX = options.pointerX;
      options.clientY = options.pointerY;
      oEvent = Object.extend(document.createEventObject(), options);
      element.fireEvent('on' + eventName, oEvent);
    };
	
    return element;
  };
  
  Element.addMethods({ simulate: Event.simulate });
})();



(function() {
	function copyEventsFrom(element, source, eventsNames) {
		element = $(element);
		source = $(source);
		var registry = Element.retrieve(element, 'prototype_event_registry');
		var sourceRegistry = Element.retrieve(source, 'prototype_event_registry');
		if (eventsNames) eventsNames = Object.toArray(eventsNames);
		
		sourceRegistry.each( function(pair) {
			var eventName = pair.key, responders = pair.value;
			if(eventsNames && !eventsNames.include(eventName)) return;
			responders.each( function(r) {
			  Element.observe(element,eventName,r.handler);
			  //Element.stopObserving(element, eventName, r.handler);
			});
		});
		
		return element;
	}

	

	Object.extend(Event, {
		copyEventsFrom:		copyEventsFrom
		//fire:          fire,
		//observe:       observe,
		//stopObserving: stopObserving
	});
	
	Element.addMethods({
		copyEventsFrom:		copyEventsFrom
		//fire:          fire,
		//observe:       observe,
		//stopObserving: stopObserving
	});
	
})();

/////////////////////////////////////////////////////


if(!window.Window) Window = {};

Object.extend(Window,{
	stop: function(ventana){
		if (!ventana) ventana = window;
		if (!document.all) ventana.stop(); else ventana.document.execCommand('Stop');
	},
	
	/*getWidth: function(){
		  var myWidth = 0;
			if( typeof( window.innerWidth ) == 'number' ) {
				//Non-IE
				myWidth = window.innerWidth;
			} else if( document.documentElement && ( document.documentElement.clientWidth) ) {
				//IE 6+ in 'standards compliant mode'
				myWidth = document.documentElement.clientWidth;
			} else if( document.body && ( document.body.clientWidth) ) {
				//IE 4 compatible
				myWidth = document.body.clientWidth;
			}
		return myWidth;
	},
	
	getHeight: function(){
		var myHeight = 0;
		if( typeof( window.innerWidth ) == 'number' ) {
			//Non-IE
			myHeight = window.innerHeight;
		} else if( document.documentElement && ( document.documentElement.clientHeight ) ) {
			//IE 6+ in 'standards compliant mode'
			myHeight = document.documentElement.clientHeight;
		} else if( document.body && ( document.body.clientHeight ) ) {
			//IE 4 compatible
			myHeight = document.body.clientHeight;
		}
		return myHeight;
	},*/
	
	getWidth: function(){
		return document.viewport.getWidth();
	},
	
	getHeight: function(){
		return document.viewport.getHeight();
	},
	
	getDimensions: function(){
		var width = this.getWidth();
		var height = this.getHeight();
		var ret = [width,height];
		ret.width = width;
		ret.height = height;
		return ret;
	},
	
	load: function(url){
		window.location.href = url;
	},
	
	disableBackspaceHistory: function(){
		document.observe('keydown',function(event){
			if(event.keyCode == Event.KEY_BACKSPACE && !$w('input textarea').include(event.element().tagName.toLowerCase())) event.stop();
		});
	}

});

Window.Document = {
	getDimensions: function() {        
	     var xScroll, yScroll;
		
		if (window.innerHeight && window.scrollMaxY) {	
			xScroll = window.innerWidth + window.scrollMaxX;
			yScroll = window.innerHeight + window.scrollMaxY;
		} else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
			xScroll = document.body.scrollWidth;
			yScroll = document.body.scrollHeight;
		} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
			xScroll = document.body.offsetWidth;
			yScroll = document.body.offsetHeight;
		}
		
		var windowWidth, windowHeight;
		
		if (self.innerHeight) {	// all except Explorer
			if(document.documentElement.clientWidth){
				windowWidth = document.documentElement.clientWidth; 
			} else {
				windowWidth = self.innerWidth;
			}
			windowHeight = self.innerHeight;
		} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
			windowWidth = document.documentElement.clientWidth;
			windowHeight = document.documentElement.clientHeight;
		} else if (document.body) { // other Explorers
			windowWidth = document.body.clientWidth;
			windowHeight = document.body.clientHeight;
		}	
		
		// for small pages with total height less then height of the viewport
		if(yScroll < windowHeight){
			pageHeight = windowHeight;
		} else { 
			pageHeight = yScroll;
		}
	
		// for small pages with total width less then width of the viewport
		if(xScroll < windowWidth){	
			pageWidth = xScroll;		
		} else {
			pageWidth = windowWidth;
		}

		var ret = [pageWidth,pageHeight];
		ret.width = pageWidth;
		ret.height = pageHeight;
		return ret;
	},
	
	getWidth: function(){
		return this.getDimensions()[0];
	},
	
	getHeight: function(){
		return this.getDimensions()[1];
	}
};

///////////////////////////////////////////////////
var Browser = Browser||{};

Object.extend(Browser,{
	url:function(){
		return document.location.href.url();
	},
	
	page:function(){
		return this.url().filename();
	},
	
	navigate:function(url){
		document.location.href=url;
	},

	
	reload:function(querystring){
		if ( querystring==null )	querystring="";
		if ( typeof querystring == "object") querystring = Object.toQueryString(querystring);
		if ( querystring.charAt(0) != "?" ) querystring = "?" + querystring;
		document.location.href=this.url()+querystring;
	},
	
	confirm:function(text){
		return window.confirm(text);
	},
	
	open:function(url,winName,options){
		var options=Object.extend({},options);
		try{eval(winName).close();}catch(e){};
		var win = window.open(url,winName,options);
		eval (winName+"=win");
	},
	
	preload: function(){
		$A(arguments).each(function(arg){
			var img = new Element("img",{src:arg});
		});
	}
	
});

Browser.redirect = Browser.navigate;






////////////////////EventDispather/////////////////////////

Choquin.Event = Base.extend({
	constructor: function(type,target){
		this.type = type;
		this.target = target;
		this.stopped = false;
	},
	
	element: function(){
		return this.target;
	},
	
	inspect: function(){
		return '#<Event: type:'+this.type+", target:"+this.target+ '>';
	},
	
	toString: function(){
		return "[event Event]";
	},
	
	stop: function(){
		this.stopped = true;
	}
});


Choquin.EventDispatcher=Base.extend({
	constructor:function(){
		///esta clase permite despachar eventos y es compatible con Event.observe !!!
		//console.debug("constructor de EventDispatcher");
	}
});

Choquin.EventDispatcher.Methods = {
	observe:function(type, listener, useCapture){
		//type: Function ; listener: String ; useCapture: Boolean
		var observer = {type:type,listener:listener,useCapture:useCapture};
		
		if (this._findObserver(type,listener,useCapture)) return false;
		
		this._getObservers().push(observer);
		this._createOnFunctions(type);
		return true;
	},
	
	fire:function(type){
		if(!this._firingEvent){
			this._firingEvent=true;
			var args=$A(arguments);
			args.shift();
			var event=type instanceof Choquin.Event?type:new Choquin.Event(type,this);
			args.unshift(event);
			try{
				if(this._constructor) this._constructor.fire.apply(this._constructor,args);
				if(!event.stopped){
					var observers=this._getObservers().select(function(observer){return observer.type==event.type});
					observers.each(function(observer){
						observer.listener.apply(this,args);
						return event.stopped
					},this)
				}
			}catch(e){throw(e)}
			finally{this._firingEvent=false}
		}
	},
	
	stopObserving:function(type,listener,useCapture){
		this._observers=this._getObservers().without(this._findObserver(type,listener,useCapture));
	},
	
	_findObserver:function(type,listener,useCapture){
		return this._getObservers().find(function(observer){
			return observer.type==type&&observer.listener==listener&&observer.useCapture==useCapture}
		);
	},
	
	_createOnFunctions:function(type){
		if(!this["on"+type]) {
			this["on"+type]=function(){
				this.fire.apply(this,[type].concat($A(arguments)))
			}.bind(this);
		} else {if(!this["on"+type].__createdByEventDispatcher){
			var oldFn=this["on"+type];
			this["on"+type]=function(){
				this.fire.apply(this,[type].concat($A(arguments)));
				oldFn.apply(this,$A(arguments))}.bind(this);
			}
		}
		this["on"+type].__createdByEventDispatcher=true;
	},
	
	_getObservers:function(){
		return this._observers = this._observers || [];
	}
};

Choquin.EventDispatcher.implement(Choquin.EventDispatcher.Methods);

Choquin.EventDispatcher.extend = Choquin.EventDispatcher.extend.wrap( 
	function(proceed, source,value) { 
		var ret = proceed(source,value); 
		ret.prototype._constructor = ret;
		Object.extend(ret,Choquin.EventDispatcher.Methods);
		return ret;
	}
); 


////////////////////////////////////


Choquin.Watcher = Class.create(PeriodicalExecuter, {
  initialize: function($super, getValue, frequency, callback) {
    //getValue es la función que devuelve al valor a verificar
    $super(callback, frequency);
    this.getValue   = getValue;
   	this.lastValue = this.getValue();
  },

  execute: function() {

    try{
		var value = this.getValue();
		if ( this.lastValue != value) {
		  this.callback(this.element, value);
		  this.lastValue = value;
		}
	}catch(e){}
  }
});



///////////////// from http://livepipe.net/downloads/prototype.tidbits.1.7.0.js /////////////////////////////////////
Element.addMethods({
	makeUnselectable: function(element,cursor){
		cursor = cursor || 'default';
		element.onselectstart = function() {
	        return false;
	    };
	    element.unselectable = "on";
	    element.style.MozUserSelect = "none";
	    element.style.cursor = cursor;
		return element;
	},
	makeSelectable: function(element){
		element.onselectstart = function() {
	        return true;
	    };
	    element.unselectable = "off";
	    element.style.MozUserSelect = "";
	    element.style.cursor = "inherit";
		return element;
	}
});

var Cookie = {
	set: function(name,value,seconds){
		if(seconds){
			d = new Date();
			d.setTime(d.getTime() + (seconds * 1000));
			expiry=';expires='+d.toGMTString();
		} else expiry='';
		document.cookie = name+"="+escape(value)+expiry+";path=/"
	},
	
	get:function(name){
		nameEQ=name+"=";
		ca=document.cookie.split(';');

		for(i = 0; i < ca.length; i++){
			c = ca[i];
			while(c.charAt(0) == ' ') c = c.substring(1,c.length);
			if(c.indexOf(nameEQ)==0) return unescape(c.substring(nameEQ.length,c.length))
		}
		return null;
	},
	
	unset:function(name){
		Cookie.set(name,'',-1);
	}
};





//////////////// from http://code.google.com/p/active-support-for-javascript/
// String Interpolation

var InterpolatableString = Class.create();
InterpolatableString.prototype = {
	initialize: function(string, binding) {
		this.string = string;
		this.tokens = (string.match(/#{([^}]+)}/ig) || []).map(function(token) {
			return new InterpolatableString.Token(token, binding);
		});
	},
	toString: function() {
		return this.tokens.inject(this.string.toString(), function(result, token) {
			var pattern = token.toRegExp();
			var replacement = token.evaluate().reemplazar('$','$$'); //addon de licho para que puedan usarse los $
			var ret = result.replace(token.toRegExp(), replacement).toString();
			return ret;
			//return result.gsub(pattern,replacement ).toString();
		}.bind(this));
	}
};

InterpolatableString.Token = Class.create();
InterpolatableString.Token.prototype = {
	initialize: function(token, binding) {
		this.token = token.replace(/^#{(.*)}$/, "$1");
		this.binding = binding;
	},
	toRegExp: function() {
		var token = ("#{" + this.token + "}").replace(/\(/, "\\(").replace(/\)/, "\\)").replace(/\[/, "\\[").replace(/\]/, "\\]").replace(/\./, "\\.").replace(/\-/, "\\-");
		return new RegExp(token, "i");
	},
	evaluate: function() {
		var token = this.token;
		return (function() { return eval(token); }.bind(this.binding))();
	}
};

var $Q = function(string, binding) {
	return new InterpolatableString(string,(binding || window)).toString();
};



//////////////////////////////////////////////////////////////////////////////
InterpolatableString.Token.prototype.evaluate = function() {
	var token = this.token;
	return String.interpret((function() { return eval(token); }.bind(this.binding))());  // MOFICADO POR LICHO PARA QUE PERMITA EVALUACIONES QUE DEVUELVEN NULL
};


String.prototype.interpolate2=function(binding){
	return new InterpolatableString(this,binding||window).toString();
};

//////////////////////////////////////////////////////////////////////////////

Choquin.TreeNode=Base.extend({
	constructor:function(){
		this.childNodes=[];
		this.parentNode=null
	},
	
	appendChild:function(child){
		this.childNodes.push(child);
		child.parentNode=this;
	},
	
	removeChild:function(child){
		this.childNodes=this.childNodes.without(child);
		return child;
	},
	
	descendants:function(){
		return this.childNodes.inject([],function(array,childNode){
			array.push(childNode);
			if(childNode.childNodes.size()>0) array.pushArray(childNode.descendants());
			return array;
		});
	},
	
	ancestors:function(){}
});


var Analytics=Analytics||{
	trackers: [], //lista de trackers que pueden utilizar excludeInternalTraffic
	excludeInternalTraffic:function(trackerID){
		try { 
		if(trackerID) var pageTracker = _gat._getTracker(trackerID);
		if(typeof pageTracker!='undefined') pageTracker._setVar('trafico_interno');
		this.trackers.each(function(pageTracker){
			pageTracker._setVar('trafico_interno');
		});
		console.warn('exluding internal traffic');
	}catch(e){}
}};
