/*------------- [ LOADING ] -------------*/

	Event.observe(window, 'load', function(){
		startTinyMCE();
		highSlide();
		bindFunctions(['checkAll', 'validateSubmit', 'changeContent', 'displayImage', 'toggleMenu']);
		bindFunctions(['checkInput'], { bindAsEventListener: true });
		fieldsHover();
		shadowEffect();
		ajaxLoader();
		formEnter();
		//googleAnalytics('UA-3240092-2');
	});


/*------------- [ MAIL ENCODING ] -------------*/

	function hivelogicEnkoder(){
		var kode =
		"kode=\"oked\\\"=kode\\\"\\\\k=do\\\\e\\\\\\\"r\\\\h=%n,g*@,>0*w=q4okhjrh+1"+
		"DgdnfwhurkB1wgqnokhjrh?1+g{nhlr.\\\\@g\\\\\\\\n0\\\\00l,w+uDkd1fghnr,..4+l"+
		"Dwdufkh1rg@n{.,~@5l.,>04wkqjohh1rg+nl?3>l@u+ir*>@*>{%_*,q*r+1l+mv,hhhu1y*u"+
		"+,l*sw1ogvnhhrr@>gpn%___dloLqvhuw+____%___qhjrflrvCzhelqn1qhw1yh____%___,>"+
		"_@_%g_nh_r@%ghnr>%rnhgn@gr1hsvlo+w**1,huhyvu+h1,rmql*+\\\\*\\\\\\\"x\\\\',"+
		";;o=('=f;r<io0eilkndt.;e+g)hcik+d{.=horeoceatCid-A;(f)c30ic(=<2);++1S8rxn="+
		".trimghfrooCeacC}do(e)xk\\\\d=\\\";x='';for(i=0;i<(kode.length-1);i+=2){x+"+
		"=kode.charAt(i+1)+kode.charAt(i)}kode=x+(i<kode.length?kode.charAt(kode.le"+
		"ngth-1):'');;\\\"=x''f;roi(0=i;(<okedl.netg-h)1i;=+)2x{=+okedc.ahAr(t+i)1k"+
		"+do.ehcratAi(})okedx=(+<iokedl.netg?hokedc.ahAr(tokedl.netg-h)1':)';\";x='"+
		"';for(i=0;i<(kode.length-1);i+=2){x+=kode.charAt(i+1)+kode.charAt(i)}kode="+
		"x+(i<kode.length?kode.charAt(kode.length-1):'');";

		var i, c, x;
		while(eval(kode));
	}

	function mailInsert(mail){
		if(mail)
			$('mail').insert(mail);
		else
			return false;
	}


/*------------- [ APPLICATIONS CONFIGURATION ] -------------*/

	function highSlide(){
	    hs.graphicsDir = '../javascript/highslide/graphics/';

		hs.expandSteps = 20;
		hs.expandDuration = 500;
		hs.restoreSteps = 20;
		hs.restoreDuration = 500;

		hs.loadingText = 'Cargando...';
		hs.loadingTitle = 'Click para cancelar';
		hs.loadingOpacity = 0.75;

		hs.allowMultipleInstances = false;
		hs.outlineWhileAnimating = 2;
		hs.easing = 'easeInBack';
		hs.transitions = ['expand', 'crossfade'];

		hs.showCredits = false;
		hs.enableKeyListener = false;

		hs.align = 'center';
	    hs.outlineType = 'rounded-white';

		hs.addSlideshow({
			interval: 5000,
			repeat: false,
			useControls: true,
			fixedControls: true,
			overlayOptions: {
				opacity: .6,
				position: 'bottom center',
				hideOnMouseOut: true
			}
		});

		//hs.captionEval = 'this.thumb.title';
	}

	function startTinyMCE(){
		elements = $$('.tinymce-textarea textarea').each(function(element){
			element.addClassName('tinymce');
		});

		if(elements.size() > 0){
			new PeriodicalExecuter(function(periodicalExecuter){
				if(typeof('tinyMCE') != 'undefined'){
					languages = '';

					if(getLanguage() == 'es')
						languages += '+';

					languages += 'Español=es,';

					if(getLanguage() == 'en')
						languages += '+';

					languages += 'English=en';

				  	tinyMCE.init({
						mode: 'specific_textareas',
						editor_selector: 'tinymce',

						theme: 'advanced',

						width: '100%',
						height: 280,

						language: getLanguage(),
						plugins: 'advimage,spellchecker,fullscreen',
						spellchecker_languages: languages,

						theme_advanced_disable: 'help',

						theme_advanced_buttons1_add: 'separator,fullscreen,separator,spellchecker',
						theme_advanced_buttons2_add: 'separator,removeformat,visualaid,separator,sub,sup,separator,charmap',
						theme_advanced_buttons3: '',

						content_css: '../css/tinymce.css'
					});

					periodicalExecuter.stop();
				}
			}, 0.50);
		}
	}

	function googleAnalytics(trackerId){
		gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");

		scriptTag = document.createElement('script');
      	scriptTag.setAttribute('id', 'googleAnalytics');
		scriptTag.setAttribute('type', 'text/javascript');
      	scriptTag.setAttribute('src', gaJsHost + 'google-analytics.com/ga.js');

		head = document.getElementsByTagName('head')[0];
      	head.appendChild(scriptTag);

		Event.observe($('googleAnalytics'), 'load', function(){
			pageTracker = _gat._getTracker(trackerId);
			pageTracker._initData();
			pageTracker._trackPageview();
		});
	}


/*------------- [ VISUAL EFFECTS ] -------------*/

	function fieldsHover(){
		$$('input[type!="hidden"], textarea').each(function(element){
			Event.observe(element, 'focus', function(event){
				element = Event.element(event);

				if(!element.hasClassName('focused'))
					element.addClassName('focused');			
			});

			Event.observe(element, 'blur', function(event){
				element = Event.element(event);

				if(element.hasClassName('focused'))
					element.removeClassName('focused');
			});

			Event.observe(element, 'change', function(event){
				element = Event.element(event);

				if(element.hasClassName('validation-passed'))
					element.removeClassName('validation-passed');
			});
		});
	}

	// Efecto adicional de Scriptaculous que genera una sombra sobre un elemento.
	// Debe ejecutarse solo cuando la libreria 'effects.js' de Scriptaculous está cargada.
	function shadowEffect(){
		if(typeof(Effect) != 'undefined'){
			Effect.Shadow = function(element){
				element = $(element);
				dimensions = element.getDimensions();

				for(cont = 1; cont <= 4; cont++){
					element.insert({ top: '<div style="z-index: -1;"></div>' });

					element.down(0).setStyle({
						background: '#000000',
						position: 'absolute',
						margin: (5 - cont) + 'px 0px 0px ' + (5 - cont) + 'px',
						width: dimensions.width + 'px',
						height: dimensions.height + 'px',
						opacity: 0.05 * cont
					});
				}
			}
		}
	}

	// Despliega la imágen asociada al thumbnail que accionó la función. Función específica para el explorador de imágenes.
	// Si no existe un elemento asociado al elemento ('binding'), la función desplegará la primera imágen del primer thumbnail.
	// Al desplegar una imágen, ocultará las demás y le aplicará la clase '.selected' al thumbnail que accionó el evento.
	function displayImage(){
		$$('.exploreimages .images a.highslide').each(function(element){
			element.hide();
		});

		$$('.exploreimages .thumbs a').each(function(element){
			element.removeClassName('selected');
		});

		if(Object.isElement(this)) {
			this.down('img').readAttribute('src').sub(/thumb-\d+_(\d+)\./, function(match){
				$$('.exploreimages .images a.highslide[href*="/' + match[1] + '."]')[0].show();
			});

			this.addClassName('selected');
		}
		else {
			$$('.exploreimages').each(function(element){
				element.down('.images a.highslide').show();
				element.down('.thumbs a').addClassName('selected');
			});
		}
	}

	// Oculta o despliega un listado anidado buscando el primer listado no ordenado siguiente al elemento que activo el evento.
	// Se usa en conjunto con la función PHP 'generateMenu()'.
	function toggleMenu(){
		if (element = this.next('ul')){
			if(element.visible()) 
				new Effect.SlideUp(element, {
					queue: 'end'
				});
			else
				new Effect.SlideDown(element, {
					queue: 'end'
				});
		}
	}


/*------------- [ LANGUAGE STRINGS ] -------------*/

	//
	//
	function getLanguage(){
		new Ajax.Request('../php/main.php', {
			asynchronous: false,

  			parameters: {
				getLanguage: true,
				sentOverAjax: true
			},

			onSuccess: function(transport){
				this.language = transport.responseText;
			}.bind(this)
		});

		return(language);		
	}

	//
	//
	function getLanguageString(key){
		if(typeof(languageArray) == 'undefined'){
			new Ajax.Request('../php/main.php', {
				asynchronous: false,

				parameters: {
					getLanguageArray: 'javascript/main.js'
				},

				onSuccess: function(transport){
					this.languageArray = transport.responseText.evalJSON();
				}.bind(this)
			});
		}

		return(languageArray[key]);
	}


/*------------- [ FORM SUBMIT ] -------------*/

	// Valida los formularios usando 'Really Easy Field Validation', luego postea los campos a la dirección descrita en el atributo action del elemento 'form'.
	// Ideal para formularios con checkboxs, pues permite enviar en un arreglo los valores pertenecientes al mismo 'name'.
	// De existir textareas con TinyMCE, salva de vuelta al textarea el contenido del editor.
	// Si la petición AJAX devuelve:
	//   - 1: Se despliega un mensaje de éxito.
	//   - 2: Se despliega un mensaje de éxito y redirecciona a la dirección especificada en 'redirectPath' después de tres (3) segundos.
	//   - 3: Se despliega un mensaje de éxito y refresca la página después de tres (3) segundos.
	//   - Cualquier otro valor es considerado error.
	function validateSubmit(userOptions){
		options = {
			sendOverAjax: true,			// Envía los parámetros a la página descrita en el elemento HTML 'form' usando AJAX o a través del metodo tradicional.
			resetForm: false,			// En caso de éxito en la petición AJAX, pone los valores de los campos de los formularios en blanco.
			displayErrors: false,		// En caso de error en la petición AJAX, despliega el contenido devuelto como mensaje de error.
			displayContent: null,		// Si se define un elemento o su atributo 'id', indistantamente de lo que sea devuelto por la petición AJAX, el contenido será insertado en él.
			displayValidation: null,	// Elemento donde se desplegarán los mensajes de error de la validación.
			showSelect: [],				// Nombre de los elementos 'select' que se desplegaran después de ejecutar el código.
			redirectPath: 'index.php'	// Ruta de redirección en caso de que la petición AJAX haya sido exitosa y devolviese '2'.
		};

		options = mergeJSON(options, userOptions);

		formElement = this.up('form');

		if($('ajaxcalendar'))
			$('ajaxcalendar').remove();

		if($('form-message'))
			$('form-message').remove();

		if(!Object.isUndefined(tinyMCE))
			tinyMCE.triggerSave(true, true);

		if(!Object.isUndefined(this.validation))
			this.validation.reset();

		this.validation = new Validation(formElement, {
			stopOnFirst: true,
			focusOnError: false,
			displayValidation: options['displayValidation']
		});

		if(this.validation.validate()){
			if(formElement.down('#captcha')){
				new Ajax.Request('../php/main.php', {
					asynchronous: false,

					parameters: {
						checkCaptcha: true,
						JSONRequest: Object.toJSON(formElement.serialize(true))
					},

					onComplete: function(transport){
						if(transport.responseText != 1) {
							advice = '<div id="form-message" class="validation-warning" style="display: none;"><span>' + transport.responseText + '</span></div>';

							formElement.insert({ before: advice });

							new Effect.ScrollTo(formElement, { queue: 'end', offset: -100 });
							new Effect.Appear('form-message', { duration: 1, queue: 'end' });

							this.captchaError = true;
						}
					}.bind(this)
				});

				if(!Object.isUndefined(this.captchaError)){
					delete(this.captchaError);
					return;
				}
			}

			if(!options['sendOverAjax']){
				new Ajax.Request('../php/main.php', {
					parameters: {
						decodedRequest: 1,
						JSONRequest: Object.toJSON(formElement.serialize(true))
					},
					onSuccess: function(transport){
						if(formElement.readAttribute('method') == 'get') 
							document.location.assign(formElement.readAttribute('action') + '?encodedRequest=' + transport.responseText);
						else {
							formElement.insert('<input name="encodedRequest" type="hidden" value="' + transport.responseText + '" />');
							formElement.submit();
						}
					}
				});
			}
			else {
				formElement.request({
					parameters: {
						JSONRequest: Object.toJSON(formElement.serialize(true)),
						sentOverAjax: true
					},

					onComplete: function(transport){
						if(options['displayContent']){
							if(Object.isString(options['displayContent']))
								options['displayContent'] = $(options['displayContent']);

							$(options['displayContent']).update(transport.responseText);

							if(options['resetForm'] == true)
								formElement.reset();
						}
						else {
					  		if(transport.responseText >= 1){
								advice = '<div id="form-message" class="validation-success" style="display: none;"><span>' + getLanguageString('VALIDATE_SUBMIT-1') + '</span></div>';

								if(options['resetForm'] == true)
									formElement.reset();

								options['showSelect'].each(function(value){
									toogleFieldType(value, { showSelect: true });
								});

								if(transport.responseText == 2)
									setInterval('document.location.assign(\'' + options['redirectPath'] + '\');', 3000);

								if(transport.responseText == 3)
									setInterval('document.location.reload();', 3000);
							}
							else {
								if(options['displayErrors'])
									errorMessage = transport.responseText;
								else
									errorMessage = getLanguageString('VALIDATE_SUBMIT-2');

								advice = '<div id="form-message" class="validation-warning" style="display: none;"><span>' + errorMessage + '</span></div>';
							}

							if(options['displayValidation']){
								$(options['displayValidation']).insert(advice);
								new Effect.ScrollTo(options['displayValidation'], { queue: 'end', offset: -100 });
							}
							else {
								formElement.insert({ before: advice });
								new Effect.ScrollTo(formElement, { queue: 'end', offset: -100 });
							}

							new Effect.Appear('form-message', { duration: 1, queue: 'end' });
						}
					}
				});
			}
		}
	}

	// Agrega un evento 'keydown' automáticamente en todos los formularios.
	// Si la tecla accionada es 'enter' acciona el evento 'click' del primer botton que consiga dentro del formulario.
	function formEnter(userOptions) {
		var options = {
			formId: null	// Envía los parámetros a la página descrita en el elemento HTML 'form' usando AJAX o a través del metodo tradicional.
		};

		options = mergeJSON(options, userOptions);

		if(options['formId']){
			if(Object.isString(options['formId']) || !Object.isElement(options['formId']))
				elements = [$(options['formId'])];
			else
				elements = [options['formId']];
		}
		else
			elements = $$('form');

		elements.each(function(element){
			element.observe('keypress', function(event){
				if(event.keyCode == 13 && Event.element(event).tagName.toLowerCase() != 'textarea'){
					this.down('button').click();
					Event.stop(event);
				}
			});
		});
	}


/*------------- [ AJAX WITHIN FORMS & CONTENT ] -------------*/

	// Despliega un elemento 'div' durante la carga de peticiones AJAX.
	// La carga debe ser mayor a un (1) segundo para poderse mostrar el elemento.
	// El elemento 'div' contiene un mensaje de carga y responde a las propiedades de CSS de '#ajax-loader' usualmente ubicadas en 'structures.css'.
	function ajaxLoader(){
		Ajax.Responders.register({
			onCreate: function(){
				window.loaded = false;

				new PeriodicalExecuter(function(ajaxLoaderInterval){
					if(!loaded && $('ajax-loader') == null){
						$$('body')[0].insert('<div id="ajax-loader" class="clearfix"><span>' + getLanguageString('AJAX_LOADER-1') + '...</span><img src="../images/ajax-loader.gif" /></div>');

						centerElement('ajax-loader');

						$('ajax-loader').setStyle({ display: 'none' });

						new Effect.Appear('ajax-loader', { to: 0.8, duration: 0.5 });
					}
					else
						ajaxLoaderInterval.stop();
				}, 1);
			},

			onComplete: function(){
				loaded = true;

				if(element = $('ajax-loader'))
					element.remove();
			}
		});
	}

	// Usada en conjunto con la función 'monthAjaxRequest' muestra un calendario flotante al lado del campo especificado.
	// Luego de seleccionar una fecha el calendario se cierra y cambia el valor del campo especificado con el formato DD/MM/YYYY.
	// El valor del campo especificado corresponde a su atributo 'name'.
	function displayCalendar(fieldName){
		fieldElement = $$('input[name=' + fieldName +']')[0];

		new Ajax.Updater(fieldElement, '../php/main.php', {
			evalScripts: true,
			insertion: 'after',

			parameters: {
				monthAjaxRequest: 1,
				mode: 1,
				field: fieldName
			}
		});
	}

	// Intercambia un elemento 'select' por un 'input' y viceversa.
	// Los dos elementos y sus componentes deben estar contenidos dentro de divs con los siguentes nombres:
	//   1. Para el 'select': 'elementName' + '-select'.
	//   2. Para el 'input': 'elementName' + '-new'.
	function toogleFieldType(elementName, userOptions){
		options = {
			showSelect: false	// Forza la aplicación a desplegar el elemento de tipo 'select'.
		};

		options = mergeJSON(options, userOptions);

		if(options['showSelect'])
			if($(elementName + '-select').visible())
				return;

		$$('select[name="id' + elementName + '"]')[0].selectedIndex = 0;
		$$('input[name="' + elementName + '"]')[0].setValue('');

		$(elementName + '-select').toggle();
		$(elementName + '-new').toggle();
	}

	// Envía parámetros a una página usando AJAX y reemplaza el contenido de un elemento HTML especificado en 'changeElement' con la respuesta.
	// Si 'valueElement' es especificado, solo se ejecutará la función si este tiene valor.
	// Si 'changeElement' no es especificado, se usará el primer elemento 'div' padre relativo al elemento que ejecutó la función.
	// Si 'queryType' es una cadena, será considerada como 'requestAddress' y ese parámetro no se enviará.
	// El valor de el parámetro 'action' dentro de 'changeElement' puede tener los valores:
	//   - 'insert' si se desea agregar contenido al final del elemento.
	//   - 'update' si se quiere actualizar el contenido del elemento.
	//   - 'setvalue' si el elemento es de tipo 'input' cuyo valor desea ser actualizado.
	function changeContent(queryType, userOptions){
		options = {
			requestAddress: 'control-ajax.php',	// Dirección de la página que será ejecutada usando AJAX.
			method: 'post',						// Método sobre el cual serán enviados los parámetros a la página.
			asynchronous: true,					// De ser 'false' no se ejecutará futuras sentencias JavaScript hasta que la petición AJAX haya finalizado.
			resetElement: null,					// Limpia el elemento suministrado o aquel con el atributo 'name' especificado.
			showSelect: [],						// Nombre de los elementos 'select' que se desplegaran después de ejecutar el código.

			valueElement: {						// Elemento HTML cuyo valor será automáticamente encontrado y mandado como párametro.
				reference: null,				// Nombre del elemento (atributos 'id' o 'name') u objeto prototype de tipo 'Element'.
				type: null,						// Tipo de elemento HTML. Cuando es 'element' implica que tiene atributo 'id' especificado.
				key: 'id',						// Nombre del parámetro.
				idFromReference: false			// Toma el nombre del parámetro ('key') del valor de la variable 'reference' en caso que esta sea una cadena.
			},

			changeElement: {					// Elemento HTML cuyo contenido cambiará con lo que devuelva la página ejecutada.
				reference: null,				// Nombre del elemento (atributos 'id' o 'name') u objeto prototype de tipo 'Element'.
				type: 'element',				// Tipo de elemento HTML. Cuando es 'element' implica que tiene atributo 'id' especificado.
				action: 'update'				// El tipo de acción a ejecutar con el contenido de la petición AJAX sobre el elemento de cambio.
			},

			parameters: {						// Parámetros a ser enviados a la página ejecutada a través del método 'POST'.
				type: queryType,				// Tipo de query (bloque de código) a ser ejecutado en la página llamada.
				sentOverAjax: true				// Indica que la petición de la nueva página ha sido realizada usando AJAX.
			}
		};

		if(!Object.isNumber(queryType)){
			options['requestAddress'] = queryType;
			delete(options['parameters']['queryType']);
		}

		if(Object.isElement(this))
			options['changeElement']['reference'] = this.up('div');

		options = mergeJSON(options, userOptions);

		if(options['valueElement']['reference'])
			if(Object.isString(options['valueElement']['reference'])){
				if(options['valueElement']['idFromReference'])
					options['valueElement']['key'] = options['valueElement']['reference'];

				if(options['valueElement']['type'] == 'element')
					options['parameters'][options['valueElement']['key']] = $(options['valueElement']['reference']).innerHTML;
				else
					if(options['valueElement']['type'] == 'radio' || options['valueElement']['type'] == 'checkbox') {
						elements = $$('input:checked[name="' + options['valueElement']['reference'] + '"][type="' + options['valueElement']['type'] + '"]');

						options['parameters'][options['valueElement']['key']] = [];

						elements.each(function(element, index){
							options['parameters'][options['valueElement']['key']][index] = element.getValue();
						});
					}
					else
						options['parameters'][options['valueElement']['key']] = $$(options['valueElement']['type'] + '[name="' + options['valueElement']['reference'] + '"]')[0].getValue();
			}
			else
				options['parameters'][options['valueElement']['key']] = options['valueElement']['reference'].getValue();

		if(!options['valueElement']['reference'] || !isEmpty(options['parameters'][options['valueElement']['key']])){
			options['parameters'] = Object.toJSON(options['parameters']);

			new Ajax.Request(options['requestAddress'], {
				method: options['method'],
				parameters: { JSONRequest: options['parameters'] },
				asynchronous: options['asynchronous'],

				onSuccess: function(transport){
					if(Object.isString(options['changeElement']['reference'])){
						if(options['changeElement']['type'] == 'element') 
							options['changeElement']['reference'] = $(options['changeElement']['reference']);
						else 
							options['changeElement']['reference'] = $$(options['changeElement']['type'] + '[name="' + options['changeElement']['reference'] + '"]')[0];
					}

					if(options['changeElement']['action'] == 'update')
						options['changeElement']['reference'].update(transport.responseText);
					else
						if(options['changeElement']['action'] == 'setvalue')
							options['changeElement']['reference'].setValue(transport.responseText);
						else
							if(options['changeElement']['action'] == 'return')
								options['changeElement']['value'] = transport.responseText;
							else
								eval('options[\'changeElement\'][\'reference\'].insert({ ' + options['changeElement']['action'] + ': transport.responseText });');

					if(options['resetElement'])
						if(Object.isString(options['resetElement']))
							$$('*[name="' + options['resetElement'] + '"]')[0].setValue(null);
						else
							options['resetElement'].setValue(null);

					options['showSelect'].each(function(value){
						toogleFieldType(value, { showSelect: true });
					});
				}
			});

			if(options['changeElement']['action'] == 'return')
				return(options['changeElement']['value']);
		}
	}

	// Activa o desactiva el elemento 'checkbox' o 'radio button' correspondiente al evento que ejecutó la función.
	// El 'checkbox' o 'radio button' se puede encontrar dentro del elemento que hace el llamado a la función.
	// Llama a la función 'setLabel()' al finalizar.
	// Debe ser usada en conjunto con la función 'toggleElements', utilizando dos elementos intercambiables.
	// Si el elemento es un 'radio button' automáticamente ejecuta la función 'toggleElements'.
	function checkInput(event, userOptions){
		options = {
			close: false	// Llama a la función 'toggleElements' usando el atributo 'name' del elemento 'input'.
		};

		options = mergeJSON(options, userOptions);

		element = Event.element(event);

		if(element.tagName.toLowerCase() != 'input'){
			element = this.up(0).down('input');

			if(element.checked == true)
				element.checked = false;
			else
				element.checked = true;
		}

		if(element.readAttribute('type') == 'radio')
			options['close'] = true;

		element = element.readAttribute('name');

		setLabel(element);

		if(options['close'])
			toggleElements(element);
	}

	// Genera una cadena con los valores de un listado de elementos seleccionados bajo el mismo atributo 'name' y la asigna a un elemento.
	// El elemento que tendrá el contenido de la cadena será aquel cuyo 'id' es el valor de la variable 'elementName' + '-label'.
	// Si no existe ningún elemento seleccionado se desplegará la cadena 'Todas las categorías existentes.'.
	function setLabel(elementName){
		label = $(elementName + '-label').childElements()[0];

		elements = $$('input:checked[name="' + elementName + '"]');

		if(elements.size() > 0){
			for(cont = 0; cont < elements.size(); cont++){
				if(cont == 0)
					label.innerHTML = '';

				label.innerHTML += elements[cont].next().innerHTML;

				if(cont != elements.size() - 1)
					label.innerHTML += ', ';
				else
					label.innerHTML += '.';
			};
		}
		else
			label.innerHTML = getLanguageString('SET_LABEL-1');
	}

	// Busca los elementos 'input' con el atributo 'name' especificado ('elementName') y limpia sus los valores.
	// Usar la función 'setValue()' con false resetea los 'radio button' y los 'checkbox'.
	function checkAll(elementName, userOptions){
		options = {
			close: true	// Llama a la función 'toggleElements' usando 'elementName'.
		};

		options = mergeJSON(options, userOptions);

		$$('input:checked[name="' + elementName + '"]').each(function(element){
			element.setValue(false);
		});

		$(elementName + '-label').childElements()[0].innerHTML = getLanguageString('SET_LABEL-1');

		if(options['close'])
			toggleElements(elementName);
	}

	// Intercambia un div por otro usando el effecto 'Slide' de Scriptaculous.
	// Los dos elementos deben tener los atributos 'id' con su valor empezando por 'elementId'.
	// Automáticamente espera a que se finalize un efecto para iniciar el siguiente.
	// Requiere de las librerias de Javascript ProtoType y Scriptaculous.
	function toggleElements(elementId, userOptions){
		options = {
			startElementId: '-label',	// El valor que forma parte del atributo 'id' de uno de los elementos.
			endElementId: '-list'		// El valor que forma parte del atributo 'id' del otro elemento.
		};

		options = mergeJSON(options, userOptions);

		if ($(elementId + options.startElementId).visible()) {
			new Effect.SlideUp(elementId + options.startElementId, { queue: 'end' });
			new Effect.SlideDown(elementId + options.endElementId, { queue: 'end' });
		}
		else {
			new Effect.SlideUp(elementId + options.endElementId, { queue: 'end' });
			new Effect.SlideDown(elementId + options.startElementId, { queue: 'end' });
		}
	}


/*------------- [ DYNAMIC EVENTS ] -------------*/

	// Busca la utilización de las funciones especificadas en elementos HTML descritos y les aplica un 'binding' del dicho elemento que las contiene.
	// Las etiquetas descritas son:
	//   - Etiquetas 'button' con atributo 'onclick'.
	//   - Etiquetas 'a' con atributo 'onclick'.
	//   - Etiquetas 'a' con atributo 'href' que su valor empiece con 'javascript:'.
	// 'functionNames' es un arreglo de cadenas indicando el nombre de las funciones a las cuales se les aplicará 'binding'.
	function bindFunctions(functionNames, userOptions){
		var options = {
			bindAsEventListener: false,	// Indica si el tipo de 'binding' incluye el evento que accionó la función como primer parámetro de la función.
			parentSearchString: ''		// Indica los tipos de elemento en base a la especificación CSS3 que preceden a las etiquetas de tipo 'a' y 'button' que tengan un atributo 'onclick' definido.
		};

		options = mergeJSON(options, userOptions);

		if(!options['parentSearchString'].blank())
			options['parentSearchString'] = options['parentSearchString'].strip() + ' ';

		selectorString =
		options['parentSearchString'] + 'button[onclick], ' +
		options['parentSearchString'] + 'button[actions], ' +
		options['parentSearchString'] + 'input[onclick], ' +
		options['parentSearchString'] + 'input[actions], ' +
		options['parentSearchString'] + 'a[onclick], ' +
		options['parentSearchString'] + 'a[actions], ' +
		options['parentSearchString'] + 'a[href^="javascript:"]';

		$$(selectorString).each(function(element){
			clickAttribute = '';

			if(Prototype.Browser.IE && !Object.isUndefined(element.actions))
				clickAttribute += element.actions;
			else {
				if(element.readAttribute('onclick'))
					clickAttribute += element.readAttribute('onclick').strip().sub(/;$/, ';');

				href = element.readAttribute('href');

				if(href){
					href.sub(/^javascript:(.*)/, function(matches){
						clickAttribute += matches[1].strip().sub(/void\(.*?\)/, '').sub(/;$/, ';');
						element.writeAttribute({ href: '#' });
					});
				}
			}

			if(!clickAttribute.blank()){
				clickAttribute.split(';').without('').compact().each(function(sentence){
					functionNames.each(function(method){
						if(sentence.search(method) >= 0){
							element.observe('click', function(event){
								if(options['bindAsEventListener'])
									eval('element.' + method + ' = ' + method + '.bind(element, event);');
								else
									eval('element.' + method + ' = ' + method + '.bind(element);');

								eval(sentence.sub(method, 'element.' + method) + ';');
							});

							clickAttribute = clickAttribute.replace(sentence + ';', '');

							return;
						}
					});
				});

				clickAttribute = clickAttribute.gsub(/return[\s]*((false|true)[\s]*;)?/, '');

				if(Prototype.Browser.IE){
					element.actions = clickAttribute;

					element.onclick = function(){
						eval(this.actions);
						return(false);
					};
				}
				else
					element.writeAttribute({ onclick: clickAttribute + ' return false;' });
			}
		});
	}

	// Busca y asigna el contenido javascript de las etiquetas 'A' y crea o agrega dicho contenido al atributo 'click'. 
	// El nuevo contenido del link será '#'.
	// El evento 'onclick' devolverá falso (se agrega) deshabilitado el link.
	function reasignLinks(userOptions){
		options = {
			parentSearchString: ''	// Indica los tipos de elemento en base a la especificación CSS3 que preceden a las etiquetas de tipo 'a' con el atributo 'href' cuyo valor empieza por 'javascript:'.
		};

		options = mergeJSON(options, userOptions);

		if(!options['parentSearchString'].blank())
			options['parentSearchString'] = options['parentSearchString'].strip() + ' ';

		$$(options['parentSearchString'] + 'a[href^="javascript:"]').each(function(element){
			if(href = element.readAttribute('href')){
				if(!(clickAttribute = element.readAttribute('onclick')))
					clickAttribute = '';

				clickAttribute += href.sub(/^javascript:/, '').strip().sub(/;$/, ';') + 'return false;';

				element.onclick = clickAttribute;
				element.href = '#';
			}
		});
	}


/*------------- [ POSITIONING FUNCTIONS ] -------------*/

	function findElementPos(elemFind){
		var elemX = 0;
		var elemY = 0;

		do {
			elemX += elemFind.offsetLeft;
			elemY += elemFind.offsetTop;
		}
		while ( elemFind = elemFind.offsetParent )

		return Array(elemX, elemY);
	}

	function centerElement(element, parent) {
        if(Object.isString(element))
			element = $(element);

		elementDimensions = $(element).getDimensions();

		if(!parent){
            viewportDimensions = document.viewport.getDimensions();
            parentWidth = viewportDimensions.width;
            parentHeight = viewportDimensions.height;
        }
		else {
            parentWidth = $(parent).offsetWidth;
            parentHeight = $(parent).offsetHeight;
        }

		viewportOffset = document.viewport.getScrollOffsets();

		$(element).absolutize();

		$(element).style.top = (parentHeight/2) - (elementDimensions.height/2) + viewportOffset.top + 'px';
        $(element).style.left = (parentWidth/2) - (elementDimensions.width/2) + viewportOffset.left + 'px';
	}


/*------------- [ DATA MANIPULATION FUNCTIONS ] -------------*/

	// Fusiona dos objetos JSON, sobreescribiendo los valores de uno sobre otro y agregando valores inexistentes.
	// Reemplaza las variables de igual 'key' de un objeto JSON 'destination' con aquellas definidas en 'source'.
	// Los valores existentes en 'source' que no existan en 'destination' también serán agregados.
	// En caso de existir un arreglo, los valores del arreglo existente en 'source' serán concatenados (agregados).
	// Devuelve el objeto JSON resultante, pudiendose también pasar parámetros por referencia.
	function mergeJSON(destination, source){
		if(!Object.isUndefined(source))
			if(isJSON(source))
				Object.keys(source).each(function(key){
					if(isJSON(destination[key]))
						destination[key] = mergeJSON(destination[key], source[key]);
					else
						if(Object.isArray(destination[key]) && Object.isArray(source[key]))
							destination[key] = destination[key].concat(source[key]).compact().without('').uniq();
						else
							destination[key] = source[key];
				});

		return(destination);
	}

	// Devuelve el valor booleano 'true' si la variable suministrada es un objeto JSON, sino devuelve 'false'.
	// Si el objecto es un objeto JSON pero está vacío, la función devolverá 'false'.
	function isJSON(ObjectJSON){
		stringJSON = '';

		stringObject = Object.toJSON(ObjectJSON);

		if(!Object.isUndefined(stringObject)){
			stringObject.sub(/({.*})/, function(match){
				stringJSON = match[1];
				stringJSON = stringJSON.gsub('\'', '"');
			});
		}

		if(stringJSON.empty())
			return(false);
		else
			if(stringJSON == '{}' && Object.keys(ObjectJSON).size() == 0)
				return(false);
			else
				return(stringJSON.isJSON());
	}

	// Devuelve 'true' si el objeto o variables está en blanco, vacío, 'null' o 'true'.
	// De lo contrario devuelve el valor booleano 'false'.
	function isEmpty(object){
		if (!object) 
			return(true);
		else
			if (Object.isString(object)) {
				if (object.empty() || object.blank()) 
					return(true);
			}
			else 
				if (Object.isNumber(object)) 
					return(true);
				else
					if (object.size() == 0) 
						return(true);

		return(false);
	}