/**
 * Author: Herve Menager
 * Organization:'Biological Software and Databases' Group, Institut Pasteur, Paris.
 * Distributed under GPLv2 Licence. Please refer to the COPYING.LIB document.
 */

var Widget = Class.create({

	// need to be defined
	// this.el
	// this.cgiurl
	// this.parameters
	// this.onCompleteFunction
	// this.insertion

	initialize : function(cgiparams, otherparams) {
		// set default value for parameters, in case it is not specified
		this.el = $(this.el);
		this.parameters = $H();
		this.cgiparams = cgiparams;
		this.otherparams = otherparams;
		this.initAjaxParams();
		this.buildFlag = false;
		this.build();
	},

	// load the widget from its cgi.
	// -the optional parameter onCompletedFunction specifies an additional
	// function launched
	// once the widget has been built.
	// -the onCompletedThisObject object helps keeping the context
	build : function(onCompletedFunction, oncCompletedThisObject) {
		if (!this.buildFlag) { 
			// builFlag is set to true when calling the
			// build function, and avoids concurrent calls
			// to a cgi for the same widget.
			this.el.setLoading();
			this.buildFlag = true;
			this.parameters.set('isInPortal', 'true'); // when called using
														// Ajax, the CGI is
														// always in the
														// portal...
			var cgipar = this.parameters.merge($H(this.cgiparams));
			new Ajax.Updater(this.el, this.cgiurl, {
				method :'get',
				parameters :cgipar.toQueryString(),
				onComplete : function() {
					this.el = $(this.el);
					this.buildFlag = false;
					this.onCompleteFunction();
					this.el.unsetLoading();
				}.bind(this)
			});
		}
	}

});

/**
 * @class Feed is a class that downloads/updates JSON Data from a given URL
 *        and stores it.
 */
var Feed = Class.create({

	initialize : function(url) {
		this.url = url;
		this.data = new Object();
		this._datastr = ''; // kept for comparison sake
		this.get();
		document.observe('mobyle:itemremove',function(event){
			if(event.memo.feed==this){
				this.get();
			}
		}.bind(this));
	},
	
	get : function(pecb, callback){
		new Ajax.Request(this.url, {
			method : 'get',
			onSuccess : function(transport){
				this._process(transport);
				if(callback){
					callback();
				}
			}.bind(this)
		});
	},
	
	_process : function(transport){
		if(this._datastr!=transport.responseText){
			this._datastr = transport.responseText;
			var response = this._datastr.evalJSON();
			this.data = response;
			document.fire('mobyle:feedupdate/'+this.url, {});
		}
	}

});

/**
 * @class FeedView is a class that provides a view to a Feed object which is actually a list
 */
var FeedView = Class.create({

	id: null,
	data : null,
	property: null,
	feed: null,

	initialize : function() {
		this.el = $(this.id);
		this.onUpdate();
		document.observe('mobyle:feedupdate/'+this.feed.url,function(event){
			this.onUpdate();
		}.bind(this));
	},
	
	onUpdate : function(){
		this.el.setLoading();
		this.data = this.extract(this.feed.data);
		this.build();
		this.el.unsetLoading();
	},
	
	check : function(){

	},
	
	build : function(){
	},
	
	extract : function(data) {
		var data = this.property ? data[this.property] : data;
		return data;
	}
});

/**
 * @class ListView
 */
var ListView = Class.create(FeedView,{

	data : $H(),
	filterFn: null,
	sortFn: null,
	htmlProperties: $A([]),
	
	extract : function($super, data) {
		var data = $H($super(data));
		this.htmlProperties.each(function(prop){
			data.each(function(item){
				if(item.value[prop]){
					item.value[prop] = item.value[prop].escapeHTML();
				};
			});
		});
		if (this.filterFn && data) {
			data.each(function(pair) {
				if (!this.filterFn(pair)) {
					data.unset(pair.key);
				}
			}.bind(this));
		}
		if (this.sortFn) {
			data = data.sortBy(this.sortFn);
		}
		if(!data){
			return $H();
		}else{
			return data;
		}
	},
	
	check : function(){
	}
	
});

var DataUList = Class.create(ListView,{

	build : function() {
		// remove previous options
		this.el.immediateDescendants().invoke('remove');
		// add html list items
		if(this.data){
			this.data.each( function(item) {
				li = new Element('li', {
					'id':this.liIdT.evaluate(item),
					'title': this.liTitleT.evaluate(item),
					'class' : this.liClassT.evaluate(item)
				}).update(this.liTextT.evaluate(item));
				if (this.onLiClick) {
					li.onclick = this.onLiClick.bindAsEventListener(this,
							this.liIdT.evaluate(item), this.liTextT
									.evaluate(item));
				}
				this.el.appendChild(li);
			}.bind(this));
		}
	}

});

/**
 * @class FilesUList is a View of the list of files It displays the list of
 *        files in an HTML unordered list
 * 
 * @extends DataUList
 */
var FilesUList = Class.create(DataUList, {

	id : 'filesListUl',
	liIdT : new Template('#{key}'),
	liTitleT : new Template('#{value.title}'),
	liClassT : new Template(''),
	liTextT : new Template('#{value.bioTypes} #{value.dataType}: #{value.userName}'),
	property: 'data',

	sortFn: function(data){
		return data.value.dataType + data.value.userName;
	}

		
});

/**
 * @class JobsUList is a View of the list of jobs It displays the list of
 *        jobs in an HTML unordered list
 * 
 * @extends DataUList
 */
var JobsUList = Class.create(DataUList, {

	id : 'jobsListUl',
	liIdT : new Template('#{key}'),
	liTitleT : new Template('status: #{value.status}'),
	liClassT : new Template('job_link, jobStatus#{value.status}'),
	liTextT : new Template('#{value.programName} - #{value.jobDate}'),
	property: 'jobs',

	onLiClick : function(event, id, text) {
		$('jobs_tabPanel').getJobTab(id);
	},

	sortFn: function(job){
		return job.value.jobDate;
	}

});

/**
 * @class DataTable is a View of the Data class It displays
 *        filtered elements of a list as an HTML table It is the equivalent to a
 *        View in a classical MVC
 * 
 */
var DataTable = Class.create(ListView,{

	build : function() {
		// remove previous options
		this.el.childElements().invoke('remove');
		// table headers
		var tr = document.createElement("tr");
		this.columns.each( function(column) {
			var th = document.createElement("th");
			th.innerHTML = column.key;
			tr.appendChild(th);
		}.bind(this));
		this.el.appendChild(tr);
		// table body (data)
		if(this.data){
			this.data.each( function(item) {
				var tr = document.createElement("tr");
				this.columns.each( function(column) {
					var td = document.createElement("td");
					td.innerHTML = column.value.evaluate(item);
					tr.appendChild(td);
				}.bind(this));
				this.el.appendChild(tr);
			}.bind(this));
		}
	}

});

/**
 * @class FilesTable is a view of the list of files stored in a
 *        MobyleSession. Additionally, it provides control (removal, renaming)
 *        over these data
 * @extends DataTable
 */
var FilesTable = Class.create(DataTable, {

	id : 'dataManagement',
	filter : null,
	columns : $H( {
				'Name' :new Template('#{value.userName}'),
				'DataType' :new Template('#{value.dataType}'),
				'BioTypes' :new Template('#{value.bioTypes}'),
				'Size' :new Template('#{value.size} KiB'),
				'Data begin' :new Template('#{value.title}'),
				'Actions' :new Template(
					'<form name="bookmark_remove_#{key}" action="data_remove.py" method="post"><input type="hidden" name="id" value="#{key}" /><input type="submit" id="dataBookmark_#{key}" value="remove"></form>')
				}),
	property: 'data',
	htmlProperties: $A(['title']),
	
	initialize : function($super) {
		$super(user.workspace);
		$(this.id).observe('submit',function(event){
			Event.stop(event);
			new Ajax.Request(event.element().action,
				{
					method:'post',
					postBody: event.element().serialize(),
					onCreate: event.element().disable.bind(event.element()),
					onComplete: function(){document.fire('mobyle:itemremove',{feed : user.workspace})}
				});
		});
	}

});


/**
 * @class DataSelect is a View of the Data class It displays
 *        filtered elements of a list as an HTML select input It is the
 *        equivalent to a View in a classical MVC
 * 
 */
var DataSelect = Class.create(ListView,{
	
	cpEls : [],
	
	build : function() {
		// backing up current selected entry before rebuilding it
		if (this.el.selectedIndex != -1) {
			var selEntryBackup = this.el.options[this.el.selectedIndex]; 
		}
		// display/hide the select element, label and button
		if (this.data.size() == 0) {
			this.cpEls.each(function(el){
				el = $(el);
				el.hide();
				Form.disable(el);
			});
		} else {
			this.cpEls.each(function(el){
				el = $(el);
				el.show();
				Form.enable(el);
			});
		}
		// remove previous options
		this.el.immediateDescendants().invoke('remove');
		// default (blank) option
		var option = document.createElement("option");
		option.setAttribute("value", '');
		option.appendChild(document.createTextNode("< >"));
		this.el.appendChild(option);
		// add html options
		this.data.each( function(item) {
			var option = document.createElement("option");
			option.setAttribute("value", this.optionValue
					.evaluate(item));
			option.setAttribute("title", this.optionTitle
					.evaluate(item));
			option.appendChild(document
							.createTextNode(this.optionText
									.evaluate(item)));
			this.el.appendChild(option);
		}.bind(this));
		if (selEntryBackup && !selEntryBackup.value.blank()) { 
			// only restore an entry if one had been selected...
			this.selectEntry(selEntryBackup.value, true);
		}
		if (this.onBuiltFunction) {
			this.onBuiltFunction();
		}
	},

	selectEntry : function(value, silentChange) {
		$A(this.el.options).each( function(option) {
			if (option.value == value) {
				option.selected = true;
				if (!silentChange) {
					if (this.el && this.el.onchange) {
						this.el.onchange(); 
					}
					if (this.submitEl && this.submitEl.onclick) {
						this.submitEl.onclick();
					}
				}
			}
		}.bind(this));
	}

});

var FilesGauge = Class.create(ListView,{

	id : 'sessionUsage',
	property: 'data',
	
	initialize : function($super) {
		this.gaugeSize = parseFloat($('sessionLimit').value);
		$super();
	},

	build : function() {
		// loading the data from the model
		this.level = 0.0;
		if(this.data){
			this.data.each( function(item) {
				this.level = this.level + parseFloat(item.value.size);
			}.bind(this));
		}
		this.usage = this.level / this.gaugeSize * 100;
		this.el.style.width = this.usage + "%";
	}

});

var User = Class.create({

	initialize: function() {
		this.workspace = new Feed('session_workspace.py');
		this.setEmailPopUp = Object.extend(new PopUp('setEmail','user'),{
			submit: function($super) {
				user.setEmail($F(this.group + this.name + 'Email'));
				this.proceed();
			}
		});
		this.signInPopUp = new AjaxPopUp('signIn','user');
		this.registerPopUp = new AjaxPopUp('register','user');
		this.activatePopUp = Object.extend(new AjaxPopUp('activate','user'),{
			reset: function(){
				$(this.id).reset();
				$('activateEmail').value = readCookieValue('email');
				$('activateKey').value = readUrlParameterValue('actkey');				
			}
		});
		this.signOutPopUp = new AjaxPopUp('signOut','user');
		this.captchaPopUp = Object.extend(new AjaxPopUp('captcha','user'),{
			baseSrc: 'session_captcha_get.py',
			getNewProblem: function(){
			    $('captchaWait').show();
			    $('captchaImage').hide();
				$('captchaImage').src = this.baseSrc + '?date=' + new Date();
			},
			reset: function($super, callback){
				$(this.id).reset();
				$(this.id).cover();
				Event.observe("captchaImage", 'load', function(e) {
					Event.stop(e);
				    $('captchaWait').hide();
				    $('captchaImage').show();
					$(this.id).uncover();
				}.bindAsEventListener(this));
				this.getNewProblem();
			}
		});
		this.updateFromCookie();
	},
	
	load: function() {
		this.workspace.get();
	},
	
	reload: function() {
		this.load();
	},
	
	check: function() {
		//TODO
	},
	
	setEmail: function(email) {
		editCookieValue('email',email);
		this.updateFromCookie();
	},
	
	updateFromCookie : function() {
		if(document.cookie.toString()==''){
			$('nocookie').show();
		}else{
			$('nocookie').hide();
		}
		this.oldSessionKey = this.sessionKey;
		this.email = readCookieValue("email");
		this.sessionKey = readCookieValue("sessionKey");
		this.activated = (readCookieValue("activated") == "True");
		this.authenticated = (readCookieValue("authenticated") == "True");
		$("userEmail").innerHTML = this.email;
		if(this.authenticated){
			$("userStatus").innerHTML = '(registered)';
		}else{
			$("userStatus").innerHTML = options['anonymousSession'] ? '(guest)' : '(register to submit)';
		}
		if (this.oldSessionKey != this.sessionKey) {
			this.reload();
		}
		if (options['authenticatedSession'] && this.authenticated){
			this.setEmailPopUp.disable();
			this.signInPopUp.disable();
			this.registerPopUp.disable();
			if (this.activated){
				this.activatePopUp.disable();
			}else{
				this.activatePopUp.enable();
				this.activatePopUp.show();
			}
		}else{
			this.setEmailPopUp.enable();
			this.signInPopUp.enable();
			this.registerPopUp.enable();
			this.activatePopUp.disable();
		}
		if (!options['authenticatedSession']){
			this.signInPopUp.disable();
			this.registerPopUp.disable();
			this.activatePopUp.disable();
		}
		if (!options['anonymousSession']){
			this.setEmailPopUp.disable();
		}
	}

});

var ModalBox = Class.create({

	initialize: function(name, group, type) {
		this.name = name;
		this.group = group;
		this.type = type;
		this.id = this.group + this.name + this.type;		
		// the "shim" is a hack to avoid display problems when the pop-ups hover java applets for instance
		this.shim = new Element('iframe', { 'id':this.id+'_iframe','style': 'border: none; margin: none; position: absolute'});
		this.shim.hide();
		$(this.group + "PopupOverlay").insert(this.shim);
	},

	show: function(){
		$(this.group + "PopupOverlay").show();
		$(this.group + "Popup").show();
		$(this.id).siblings().invoke('hide');
		$(this.id).show();
		this.shim.show();
		this.shim.clonePosition($(this.group + "Popup"));
	},

	hide: function(){
		$(this.id).hide();
		$(this.group + "Popup").hide();
		$(this.group + "PopupOverlay").hide();
		this.shim.hide();
	},
	
	setMessage: function(message){
		$(this.id).down('.message').update(message);
	}
	
});

var PopUp = Class.create(ModalBox, {
 
	initialize: function($super, name, group) {
		$super(name, group, 'Form');
		this.responseOk = true;
		Event.observe(this.group + this.name + 'Open', 'click', function(e) {
			Event.stop(e);
			this.show();
		}.bindAsEventListener(this));
		Event.observe(this.group + this.name + 'Form', 'submit', function(e) {
			Event.stop(e);
			this.submit();
		}.bindAsEventListener(this));
		Event.observe(this.group + this.name + 'Cancel', 'click', function(e) {
			this.hide();
		}.bindAsEventListener(this));
	},
	
	show: function($super, callback){
		this.reset();
		$super();
		this.callback = callback ? callback: undefined;
		var firstField = $(this.id).down('[tabindex=1]');
		firstField ? firstField.focus() : false;
	},

	submit: function(){
		$(this.group + this.name + this.type).cover();
		$(this.group + this.name + this.type).removeAllMessages();
	},
	
	disable: function(){
		$(this.group + this.name +'Open').hide();
		$(this.group + this.name + 'Open').up().hide();
	},

	enable: function(){
		$(this.group + this.name +'Open').show();
		$(this.group + this.name +'Open').up().show();
	},
	
	proceed: function(){
		this.hide();
		if(this.callback){
			this.callback();
			this.callback = undefined;
		}
	},
	
	reset: function(){
		$(this.id).reset();
	}
	
});

var AjaxPopUp = Class.create(PopUp, {
	
	submit: function($super) {
		$super();
		this.p = $(this.id).serialize();
		new Ajax.Request($(this.id).action,
			{
				method:'post',
				postBody: this.p,
				onSuccess: this.onSuccess.bind(this)
			});
	},

	onSuccess: function(transport){
		this.answer = transport.responseText.evalJSON();
		if(this.answer.ok=="True"){
			user.updateFromCookie();
			user.reload();
			this.responseOk=true;
		}else{
			$(this.id).setMessage(this.answer.msg.escapeHTML());
			this.responseOk=false;
		}
		if(this.responseOk){
			this.proceed();
		}else{
		    this.reset();
		}
		if($(this.id)){
			$(this.id).uncover();
		}
	},

	proceed: function($super){
		user.updateFromCookie();
		$super();
	},
	
	prepare: function($super){
		$super();
	}
	
});

/**
 * tabMethods is the set of methods that manage tabbed panels manipulation
 */
var tabMethods = {

	/**
	 * insert a tabs container inside any given container
	 * 
	 * @param {Element}
	 *            element container
	 * @param {Data}
	 *            ajaxData [optional] the list to which this tabsContainer registers as a view
	 */
	insertTabsContainer : function(element) {
		if(!$(element).down('.handlesList')){
			$(element).insert("<ul class='handlesList' />");
			$(element).insert("<div class='panelsList' />");
		}
	},
	
	getTabContainerTabs: function(element){
		if (!($(element).down('ul.handlesList') && $(element).down(
		'div.panelsList'))) {
			throw Error("element " + $(element).id
			+ " is not a valid tabs container");
		}else{
			tabs = $(element).down('ul.handlesList').select('.tabHandle');
			return tabs.collect(function(s) {
				  return s.id.sub('_tabHandle','');
			});
		}
	},

	/**
	 * insert a tab in a given tabs container.
	 * 
	 * @param {Element}
	 *            element the element containing the tabs (handles+panels)
	 * @param {String}
	 *            id the id of the tab to be created
	 * @param {String}
	 *            label the label of the tab to be created (displayed in the
	 *            handle)
	 * @param {boolean}
	 *            close indicates wether or not a close control should be
	 *            created
	 */
	insertTab : function(element, id, label, close, onClose) {
		if (!($(element).down('ul.handlesList') && $(element).down(
				'div.panelsList'))) {
			throw Error("element " + $(element).id + " is not a valid tabs container");
		}
		// create tab html if it does not exist
		if (!$(id + '_tabPanel')) {
			// insert handle
			$(element).down('ul.handlesList').__insertHandle(id, label, close);
			// insert panel
			$(element).down('div.panelsList').__insertPanel(id);
		}
		// set show on click behaviour
		$(id + '_tabHandle').observe('click', function(event) {
			event.element().showTab();
			event.stop();
		});
		$(id + '_tabHandle').onClose = onClose ? onClose : function(){};
		// set close behaviour
		if (close) {
			$(id + '_tabHandle').up('tr').down('.closeLink').observe('click', function(event) {
				event.element().up('tr').down('.tabHandle').onClose();
				event.element().up('tr').down('.tabHandle').removeTab();
				event.stop();
			});
		}
		$(id + '_tabHandle').showTab();
	},

	/**
	 * remove a tab
	 * 
	 * @param {Element}
	 *            element the handle link of the element that should be removed
	 */
	removeTab : function(element) {
		var panelId = $(element).href.gsub('(.*)\#', '');
		if ($(element.up('li')).siblings().last())
			var nextTab = $(element.up('li')).siblings().last().down(
					'.tabHandle');
		$(element.up('li')).remove();
		$(panelId).remove();
		// show previous tab
		if (nextTab)
			nextTab.showTab();
		var memo = {'handleId':element.id,'panelId':panelId}
		document.fire('mobyle:tabremoved',memo);
	},

	/**
	 * insert the html for a tabbed panel
	 * 
	 * @param {Element}
	 *            element the tab panels container
	 * @param {String}
	 *            id the id of the created tab
	 */
	__insertPanel : function(element, id) {
		var panelTemplate = new Template(
				"<div class='tabPanel' id='#{id}_tabPanel'></div>");
		$(element).insert(panelTemplate.evaluate( {
			'id' :id
		}));
	},

	/**
	 * insert the html for a tab handle
	 * 
	 * @param {Element}
	 *            element the tab handles container
	 * @param {String}
	 *            id the id of the tab to be created
	 * @param {String}
	 *            label the label of the tab to be created (displayed in the
	 *            handle)
	 * @param {boolean}
	 *            close indicates wether or not a close control should be
	 *            created
	 */
	__insertHandle : function(element, id, label, close) {
		if (close) {
			var closeLink = "<td><a href='#' class='closeLink' title='close this tab'>x</a></td>";
		}
		var handleTemplate = new Template(
				"<li><table><tr><td><a href='##{id}_tabPanel' class='tabHandle' id='#{id}_tabHandle'>#{label}</a></td>#{closeLink}</tr></table></li>");
		$(element).insert(handleTemplate.evaluate( {
			'id' :id,
			'label' :label,
			'closeLink' :closeLink
		}));
	},

	/**
	 * display a tab
	 * 
	 * @param {Element}
	 *            element the handle link or the panel of the element that
	 *            should be displayed
	 */
	showTab : function(element) {
		if ($(element).hasClassName('tabPanel')) {
			var handleId = $(element).id.sub('_tabPanel', '_tabHandle');
			var element = $(handleId);
		} else if (!$(element).hasClassName('tabHandle')) {
			throw Error("this element is not a tab handle or panel");
		}
		var liEl = $(element.up('li'));
		liEl.siblings().each( function(el) {
			if (el.down('a.tabHandle')) {
				el.removeClassName('selected');
				var panelId = el.down('a').href.gsub('(.*)\#', '');
				$(panelId).hide();
			}
		});
		liEl.addClassName('selected');
		var panelId = liEl.down('a').href.gsub('(.*)\#', '');
		$(panelId).show();
		// recursively show the parent tabs of the shown tab
		if ($(element).up('.tabPanel')) {
			$(element).up('.tabPanel').showTab();
		}
	},
	
	/**
	 * insert / show a job tab
	 * 
	 * @param {Element}
	 *            element the tab handles container
	 * @param {String}
	 *            id the job id
	 */
	getJobTab : function(element, id) {
		var tabId = id + '_tabHandle';
		// if the job tab exists
		if ($(tabId)) {
			$(tabId).showTab();
		} else {
			// otherwise we just create it
			$(element).insertTab(id, "loading job<br />&nbsp;", true);
			var p = $H( {
				jobid :id
			});
			var op = $H( {
				el :$(id + '_tabPanel')
			});
			new Job(p, op);
		}
	},
	
	getFormTab : function(element, programUrl, paramName, fileName) {
		var tabId = programUrl + '_tabHandle';
		// if the form exists
		if ($(tabId)) {
			$(tabId).showTab();
			// update the databox if needed
			if (paramName && fileName) { // update the databox if this is part of
											// a pipe
				var updatedDatabox = portal.forms.get(programUrl).databoxes
						.get(paramName);
				updatedDatabox.setInputField(updatedDatabox.databoxFields
						.get('result'));
				updatedDatabox.historyFields.get('result').dataSelectData
						.selectEntry(fileName);
			}
		} else {
			onClose = function(){}
			// otherwise create the form
			$(element).insertTab(programUrl, "loading program", true);
			var p = $H( {
				programUrl :programUrl
			});
			var otherpars = $H( {
				el :$(programUrl + '_tabPanel')
			});
			if (paramName && fileName) {
				otherpars.update( {
					paramName :paramName,
					fileName :fileName
				})
			}
			var form = new PForm(p, otherpars);
			portal.forms.set(programUrl, form);			
		}
	},
	
	getTutorialTab : function(element, href, label) {
		var tabId = href + '_tabHandle';
		// if the tutorial exists
		if ($(tabId)) {
			$(tabId).showTab();
		} else {
			// otherwise create the tutorial tab and fill it
			$(element).insertTab(href, label, true);
			new Ajax.Updater($(href + '_tabPanel'), href, {
				method :'GET'
			});
		}
	
	},
	
	/**
	 * hides an element this method overrides prototypejs', because display:none
	 * only sometimes is not always sufficient to hide a panel in IE
	 * 
	 * @param {Element}
	 *            element the element to hide
	 */
	hide : function(element) {
		$(element).style.display = 'none';
		$(element).style.visibility = 'hidden';
	},
	
	/**
	 * show an element this method overrides prototypejs', because display:none only
	 * sometimes is not always sufficient to hide a panel in IE
	 * 
	 * @param {Element}
	 *            element the element to show
	 */
	show : function(element) {
		$(element).style.display = '';
		$(element).style.visibility = '';
	},
	
	setLoading : function(element) {
		$(element).addClassName('loading');
	},

	unsetLoading : function(element) {
		$(element).removeClassName('loading');
	}
	
};

/**
 * formMethods is the set of methods that manage form-related operations
 */
var formMethods = {
	/**
	 * sets an error message corresponding to the element
	 * 
	 * @param {Element}
	 *            element the DOM element 
	 * @param {Message}
	 *            message the message to display
	 */
	setMessage : function(element, message) {
		this.msgEl = this.msgEl ? this.msgEl : element;
		$(this.msgEl).insert({'top':"<div class='error'>"+message+"</div>"});
	},
	
	/**
	 * removes every error message corresponding to the element
	 * 
	 * @param {Element}
	 *            element the DOM element 
	 */
	removeAllMessages: function(element) {
		msgEl = this.msgEl ? this.msgEl : element;
		$(this.msgEl).descendants().each(
				function(el){
					if(el.match('.error')){
						el.remove();
					}
				}
		);
	},
	
	cover: function(element){
		element.insert({'top':'<div class="popup_background"/>'});
	},
	
	uncover: function(element){
		element.select('.popup_background').invoke('remove');
	}

};

Element.addMethods(tabMethods);
Element.addMethods(formMethods);

/**
 * @class Databox is a class that represents a databox in the portal page
 *        It includes:
 *        <ul>
 *        <li>cgi call to get the html form from the server</li>
 *        <li>fields creation</li>
 *        <li>data verification</li>
 *        </ul>
 * 
 */
var Databox = Class.create({

	initialize : function(el, form, programName, parameterName,
			parameterDataType, parameterBioType, parameterCard) {
		this.el = el;
		this.form = form;
		this.programName = programName;
		this.parameterName = parameterName;
		this.parameterDataType = parameterDataType;
		this.parameterBioType = parameterBioType;
		this.parameterCard = $A(parameterCard.split(','));
		this.minCard = this.parameterCard.first();
		this.lastCard = this.parameterCard.last();
		if (this.minCard == '') {
			this.minCard = '0';
		}
		if (this.maxCard == '') {
			this.maxCard = 'n';
		}
		this.databoxId = this.programName + '_' + this.parameterName;
		try {
			this.prompt = $('for_' + this.databoxId).innerHTML; 
			// getting the parameter prompt using the DOM
		} catch (e) {
			this.prompt = this.parameterName;
		}
		// data value
		if ($(this.databoxId + '_data')) {
			this.valueEl = $(this.databoxId + '_data');
		}
		// data name in session
		if ($(this.databoxId + '_sname')) {
			this.sNameEl = $(this.databoxId + '_sname');
		}
		// data user name)
		if ($(this.databoxId + '_uname')) {
			this.uNameEl = $(this.databoxId + '_uname');
		}
		this.databoxFields = $H();
		this.historyFields = $H();
		if ($('data_input_paste_' + this.databoxId)) {
			this.databoxFields.set('paste', new DataboxField(
					$('data_input_paste_' + this.databoxId),
					$('dataInputType_' + this.databoxId + '_paste'),
					this));
			this.historyFields.set('paste',
					new HistoryDataField(
							$('history_paste_' + this.databoxId),
							'paste', this));
		}
		if ($('data_input_db_' + this.databoxId)) {
			this.databoxFields
					.set(
							'db',
							new DBDataField(
									$('data_input_db_' + this.databoxId),
									$('data_input_db_db_' + this.databoxId),
									$('data_input_db_id_' + this.databoxId),
									$('data_input_db_submit_' + this.databoxId),
									$('dataInputType_' + this.databoxId + '_db'),
									this));
			this.historyFields.set('db', new HistoryDataField(
					$('history_db_' + this.databoxId), 'db', this));
		}
		if ($('data_input_upload_' + this.databoxId)) {
			this.databoxFields.set('upload', new FileDataField(
					$('data_input_upload_' + this.databoxId),
					$('dataInputType_' + this.databoxId + '_upload'),
					this));
			this.historyFields.set('upload',
					new HistoryDataField(
							$('history_upload_' + this.databoxId),
							'upload', this));
		}
		if ($('data_input_result_' + this.databoxId)) {
			this.databoxFields.set('result', new DataboxField(
					$('data_input_result_' + this.databoxId),
					$('dataInputType_' + this.databoxId + '_result'),
					this));
			var db = this;
			this.historyFields.set('result',
				new (Class.create(HistoryDataField,{
				getCpEls : function(){
					return $A([$('data_input_result_' + this.databox.databoxId),$('dataInputType_' + this.databox.databoxId + '_result').up()]);
				}
			}))($('history_result_' + this.databoxId),'result', this));
		}
		if (this.valueEl != null) {
			this.valueInputField = new InputField(this.valueEl,
					this.valueEl.up('.parameters'), this.prompt);
			this.editBtn = $('data_input_edit_' + this.databoxId);
			this.clearBtn = $('data_input_reset_' + this.databoxId);
			this.forbidEditMsg = $('data_input_editforbid_' + this.databoxId);
			this.forbidEditMsg.hide();
			this.clearBtn.onclick = this.clear.bindAsEventListener(this);
			this.editBtn.onclick = this.switchToEdit.bindAsEventListener(this);
		}
		if (this.databoxFields.get('paste')) {
			this.setInputField(this.databoxFields.get('paste'));
		} else {
			this.setInputField(this.databoxFields.get('upload'));
		}
	},

	switchToEdit : function() {
		this.setInputField(this.databoxFields.get('paste'), true);
		this.sNameEl.clear();
	},

	setDataBoxValueTo : function(fileName) {
		this.setInputField(this.databoxFields.get('result'));
		this.historyFields.get('result').dataSelectData
				.selectEntry(fileName);
	},

	setInputField : function(selectedField, doNotClear) {
		if (this.valueInputField) {
			this.valueInputField.removeInvalidFieldMessage();
			this.setEditable(selectedField.el.id == this.databoxFields
					.get('paste').el.id);
		}
		if (!doNotClear) {
			this.clear();
		}
		this.databoxFields.each( function(hash) {
			databoxField = hash.value;
			databoxField.removeInvalidFieldMessage();
			if (databoxField.el.id == selectedField.el.id) {
				databoxField.display();
				databoxField.radioEl.checked = true;
			} else {
				databoxField.hide();
				databoxField.radioEl.checked = false;
			}
		});
	},

	setEditable : function(editable) {
		if (editable) {
			this.valueEl.enable();
			this.valueEl.setStyle( {
				overflow :'auto'
			});
			this.editBtn.disable();
			this.editBtn.hide();
		} else {
			this.valueEl.disable();
			this.valueEl.setStyle( {
				overflow :'hidden'
			});
			this.editBtn.enable();
			this.editBtn.show();
		}
	},

	onGetData : function(responseText) {
		var txt = responseText.strip();
		if(txt!=''){
			if(txt.isJSON()){
				user.workspace.get();
				response = txt.evalJSON();
				if(response.content!=null){
					if (this.valueEl != null) {
						this.valueEl.value = response.content;
						this.setEditable(!response.headFlag == "HEAD");
					}
					if (this.sNameEl != null) {
						this.sNameEl.value = response.safeName;
					}
					if (this.uNameEl != null) {
						this.uNameEl.value = response.userName;
					}
					if (this.valueEl != null && response.headFlag != "HEAD") {
						this.editBtn.enable();
						if (this.forbidEditMsg)
							this.forbidEditMsg.hide();
					} else {
						if (this.editBtn)
							this.editBtn.disable();
						if (this.forbidEditMsg)
							this.forbidEditMsg.show();
						if (this.valueEl)
							this.valueEl.value += "...";
					}
				}
				if (response.errormsg) {
					this.valueInputField
							.setInvalidFieldMessage(response.errormsg);
				}
			}else{
				this.valueInputField.setInvalidFieldMessage('upload failed, please check your data');
			}
		}
	},

	clear : function() {
		if (this.sNameEl != null) {
			this.sNameEl.value = '';
		}
		if (this.uNameEl != null) {
			this.uNameEl.value = '';
		}
		if (this.valueEl != null) {
			this.valueEl.value = '';
		}
		if (this.forbidEditMsg)
			this.forbidEditMsg.hide();
	},

	callCheckCGI : function(callbackFct) {
		if (!this.valueEl.value.blank()) {
			$(this.databoxId + "_fmt_count").innerHTML = '';
			$(this.databoxId + "_fmt_name").innerHTML = '';
			$(this.databoxId + "_fmt_errormsg").innerHTML = '';
			var cgipars = new Object();
			cgipars.datatype = this.parameterDataType;
			cgipars.data = this.valueEl.value;
			this.valueInputField.removeInvalidFieldMessage();
			new Ajax.Request(
					'data_check.py',
					{
						method :'post',
						postBody :$H(cgipars).toQueryString(),
						onLoading : function() {
							this.valueEl.readonly = true;
						}.bind(this),
						onSuccess : function(transport) {
							this.valueEl.readonly = false;
							var response = transport.responseText
									.evalJSON();
							// if there is an error message, it is a
							// server problem, hence we do not block
							// submission
							if (response.errormsg) {
								$(this.databoxId + "_fmt_errormsg").innerHTML = response.errormsg;
							}
							if (response.format != null) {
								$(this.databoxId + "_fmt_name").innerHTML = response.format;
							} else {
								$(this.databoxId + "_fmt_name").innerHTML = '';
							}
							if (response.format != null) {
								$(this.databoxId + "_fmt_count").innerHTML = response.count;
							} else {
								$(this.databoxId + "_fmt_count").innerHTML = '';
							}
						}.bind(this),
						onComplete : function() {
							this.check();
						}.bind(this)
					});
		}
	}

});

var InputField = Class.create({

	initialize: function(msgEl,summaryMsgEl,msgPrompt) {
		this.msgEl = $(msgEl);
		if(summaryMsgEl!=undefined){
			this.summaryMsgEl = $(summaryMsgEl);
		}
		if(msgPrompt!=undefined){
			this.msgPrompt=msgPrompt + ": ";
		}else{
			this.msgPrompt='';
		}
//		Event.observe(this.msgEl, 'focus', 
//			function(e){
//				this.removeInvalidFieldMessage();
//			}.bindAsEventListener(this));
	},

	setInvalidFieldMessage: function(message){
		this.message = message;
		// remove previous messages about the field
		this.removeInvalidFieldMessage();
		// create field message
		var m = document.createElement("div");
		Element.extend(m);
		m.addClassName("error");
		m.appendChild(document.createTextNode(message));
		this.msgEl.parentNode.insertBefore(m,this.msgEl);
		// create summary message
		if(this.summaryMsgEl!=undefined){
			var p = document.createElement('span');
			p.appendChild(document.createTextNode(this.msgPrompt));
			var m = document.createElement("div");
			Element.extend(m);
			m.addClassName("error");
			m.appendChild(p);
			m.appendChild(document.createTextNode(message));
			this.summaryMsgEl.parentNode.insertBefore(m,this.summaryMsgEl);
		}
	},
    
	removeInvalidFieldMessage: function(){
		// remove field message
		if(this.msgEl.previousSiblings()!=undefined){
			this.msgEl.previousSiblings().each(
				function(el){
					if(el.hasClassName("error")){
						el.remove();
					}
				}.bind(this));      
		}
		// remove summary message
		if(this.summaryMsgEl!=undefined && this.summaryMsgEl.previousSiblings()!=undefined){
			this.summaryMsgEl.previousSiblings().each(
					function(el){
						if(el.hasClassName("error") && el.down('span').innerHTML.unescapeHTML()==this.msgPrompt){
							el.remove();
						}
					}.bind(this));
		}
	}

});


var DataboxField = Class.create(InputField,{

	initialize : function($super, el, radioEl, databox) {
		this.el = el;
		$super(this.el, this.el.up('.parameters'), databox.prompt);
		this.databox = databox;
		this.radioEl = radioEl;
		this.radioEl.onclick = this.onSelectRadio.bindAsEventListener(this);
	},

	onSelectRadio : function() {
		this.databox.setInputField(this);
	},

	display : function() {
		Element.show(this.el);
		this.radioEl.setAttribute("checked", "checked");
	},

	hide : function() {
		Element.hide(this.el);
		this.radioEl.removeAttribute("checked");
	},

	disable : function() {
		Element.hide(this.radioEl.parentNode);
	}

});

var DBDataField = Class.create(DataboxField,{

	initialize : function($super, el, dbEl, idEl, sbEl, radioEl, databox) {
		$super(el, radioEl, databox);
		this.dbEl = dbEl;
		this.idEl = idEl;
		this.sbEl = sbEl;
		this.dataSelectDB = new (Class.create(DataSelect,{
			id : this.dbEl.id,
			optionValue : new Template('#{key}'),
			optionTitle : new Template('#{value.label}'),
			optionText : new Template('#{key}'),
			feed : portal.banks,
			filterFn : this.dbFilter.bind(this),
			cpEls : $A([this.dbEl,this.radioEl.parentNode])
		}))();
		this.sbEl.onclick = this.checkAndGetEntry.bindAsEventListener(this);
	},

	dbFilter : function(bank) {
		bank = bank.value;
		if (bank.dataType == this.databox.parameterDataType) { 
			// datatype filter
		if (this.databox.parameterBioType == null || bank.bioTypes == null
				|| this.databox.parameterBioType.blank() || bank.bioTypes.blank
				&& bank.bioTypes.blank()
				|| bank.bioTypes.include(this.databox.parameterBioType)
				|| this.databox.parameterBioType.findAll
				&& this.databox.parameterBioType.findAll( function(bt) {
					if (bank.bioTypes.include(bt))
						return bt;
				}).size() > 0) {
					return true;
				}
			}
		return false;
	},

	checkAndGetEntry : function(evt) {
		// Element.removeClassName(this.el,'invalid_form_field');
		if (this.idEl.value != '' && this.dbEl.value != '') {
			this.getDBEntry();
		} else {
			if (this.dbEl.value == '') {
				// Element.addClassName(this.dbEl,'invalid_form_field');
				this.setInvalidFieldMessage("Invalid database.");
			}
			if (this.idEl.value == '') {
				// Element.addClassName(this.idEl,'invalid_form_field');
				this.setInvalidFieldMessage("Invalid identifier.");
			}
			return false;
		}
	},

	getDBEntry : function() {
		// removing previous error message (entry not found) if it exists
		this.removeInvalidFieldMessage();
		// call to golden
		var cgipars = new Object();
		cgipars.datatype = this.databox.parameterDatatype;
		cgipars.db = $F(this.dbEl);
		cgipars.id = $F(this.idEl);
		var databox = this.databox;
		new Ajax.Request('bank_get.py', {
			method :'post',
			postBody :$H(cgipars).toQueryString(),
			onLoading : function() {
				databox.valueEl.readonly = true;
			},
			onSuccess : function(transport) {
				databox.valueEl.readonly = false;
				var response = transport.responseText.evalJSON();
				var firstValueEntered = !databox.valueEl.value.blank()
				databox.valueEl.value = databox.valueEl.value + response.data;
				// process replied error message from cgi
				if (!response.msg.blank()) {
					this.setInvalidFieldMessage(response.msg);
				} else {
					this.removeInvalidFieldMessage();
					this.dbEl.selectedIndex = 0;
					this.idEl.value = '';
				}
			}.bind(this)
		});
	}
});

var FileDataField = Class.create(DataboxField,{

	initialize : function($super, el, radioEl, databox) {
		$super(el, radioEl, databox);
		this.uploadFileInputEl = $('data_input_upload_value_' + databox.databoxId);
		this.uploadFileFormEl = $('uploadform_' + databox.databoxId);
		this.uploadTargetEl = $('uploadtarget_' + databox.databoxId);
		Event.observe(this.uploadTargetEl, 'load', this.fileLoaded
				.bindAsEventListener(this));
		Event.observe(this.uploadFileInputEl,'change',this.upload.bindAsEventListener(this));
	},

	fileLoaded : function() {
		this.uploadFileInputEl.value = '';
		this.removeInvalidFieldMessage();
		if (this.uploadTargetEl.contentDocument) {
			// W3-compatible version
			var d = this.uploadTargetEl.contentDocument;
			var txt = d.documentElement.textContent;
		} else {
			// IE-compatible version
			var d = document.frames[this.uploadTargetEl.id].document;
			var txt = d.documentElement.innerText;
		}
		this.databox.onGetData(txt);
	},
	
	upload : function() {
		this.uploadFileFormEl.submit();
	}
});

var HistoryDataField = Class.create({
		
	initialize : function(el, userMode, databox) {
		this.el = el;
		this.userMode = userMode;
		this.databox = databox;
		this.submitEl = $('history_' + this.userMode + '_submit_'
				+ this.databox.databoxId);
		this.dataSelectData = new (Class.create(DataSelect,{
			id : this.el.id,
			optionValue : new Template('#{key}'),
			optionTitle : new Template('#{value.userName} first line: #{value.title}'),
			optionText : new Template('#{value.userName}'),
			submitEl : this.submitEl,
			feed : user.workspace,
			property : 'data',
			filterFn : this.historyDataFilter.bind(this),
			cpEls : this.getCpEls()
		}))();
		this.submitEl.onclick = this.getEntry.bindAsEventListener(this);
	},

	getCpEls : function(){
		return $A([this.el.up()]);
	},

	historyDataFilter : function(bookmark) {
		dt = portal.datatypes.data[bookmark.value.dataType];
		if (dt && dt.ancestorTypes.include(this.databox.parameterDataType)) { // datatype filter
			if (bookmark.value.userModes.include(this.userMode)) { // user mode filter
				if (this.databox.parameterBioType == null
						|| bookmark.value.bioTypes == null // biotype(s) filter
						|| this.databox.parameterBioType.blank
						&& this.databox.parameterBioType.blank()
						|| bookmark.value.bioTypes.blank
						&& bookmark.value.bioTypes.blank()
						|| this.databox.parameterBioType.size
						&& this.databox.parameterBioType.size() == 0
						|| bookmark.value.bioTypes.size
						&& bookmark.value.bioTypes.size() == 0
						|| bookmark.value.bioTypes
								.include(this.databox.parameterBioType)
						|| this.databox.parameterBioType.findAll
						&& this.databox.parameterBioType.findAll( function(bt) {
							if (bookmark.value.bioTypes.include(bt))
								return bt;
						}).size() > 0) {
					return true;
				}
			}
		}
		return false;
	},

	getEntry : function() {
		if ($F(this.el) != '') {
			// call to history
			var cgipars = new Object();
			cgipars.dataId = $F(this.el);
			this.el.disable();
			this.submitEl.disable();
			new Ajax.Request('data_get.py', {
				method :'get',
				parameters :$H(cgipars).toQueryString(),
				onSuccess : function(transport) {
					this.databox.onGetData(transport.responseText);
				}.bind(this),
				onComplete : function() {
					this.el.selectedIndex = 0;
					this.el.enable();
					this.submitEl.enable();
				}.bind(this)
			});
		} else {
			this.databox.clear();
		}
	}

});

/**
 * @class PForm is a Widget class that represents a form in the
 *        portal page It includes:
 *        <ul>
 *        <li>cgi call to get the html form from the server</li>
 *        <li>databoxes creation</li>
 *        <li>form verification</li>
 *        <li>form submission</li>
 *        </ul>
 * 
 * @extends Widget
 */
var PForm = Class.create(Widget, {

  cgiurl:'form.py',

  initAjaxParams: function() {
    this.el=$(this.otherparams.get('el'));
  },
  
  onCompleteFunction: function() {
      // show e-mail input field on the left if not specified previously (i.e.
		// and no registered session as well)
      if(!(user.email && user.email!='') && $('email_tabHandle')){
        $('email_tabHandle').showTab();
      }
      this.programName=this.cgiparams.get('programUrl');
      this.formEl=$(this.programName+"Form");
      if(this.formEl){
        // general error messages field
        this.inputField = new InputField(this.formEl);
        // creating the databoxes for the corresponding form
        this.databoxes=$H();
        this.el.select(".databox").each(function(node) {
          var isDataInput = node.getAttribute('isDataInput');
          var el = node;
          var form = this;
          var programName = this.programName;
          var parameterName = el.select('.parameterName')[0].value;
          var parameterDataType = el.select('.parameterDataType')[0].value;
          var parameterBioType = el.select('.parameterBioType')[0].value;
          var parameterCard = el.select('.parameterCard')[0].value;
          this.databoxes.set(parameterName, new Databox(el, form, programName, parameterName, parameterDataType, parameterBioType, parameterCard));
          if(this.otherparams.get('paramName')==parameterName){
            this.databoxes.get(parameterName).setDataBoxValueTo(this.otherparams.get('fileName'));
          }
        }.bind(this));
      
        // creating the additional parameters
        this.parameters = $H();
        this.formParEls = this.el.select('.form_parameter');
  
        var inputNames = this.formParEls.collect(function(el){return el.getAttribute('name');}).uniq();
        
        inputNames.each(function(inputName) {
          parName = inputName.sub(this.programName+'_', '');
          if(!this.parameters.get(parName)){
        	var paramEls = this.formParEls.findAll(function(s) {
                return s.name==inputName;
            })
            if(paramEls.size()>0){
            	this.parameters.set(parName,new PFormParameter(paramEls));
            }
          }
        }.bind(this));
        // setting wait screens
        this.wait = new ModalBox('Generic',this.programName,'Wait');
        // setting submit button behaviour
        this.submitButton = $("submit_" + this.programName);
        this.submitButton.onclick = this.requestSubmit.bindAsEventListener(this);
        // setting reset button behaviour
		Event.observe("reset_" + this.programName, 'click', function(e) {
			Event.stop(e);
			this.wait.show();
			this.build();
		}.bindAsEventListener(this));
        // behaviour for help pages button that scrolls to the help links
        if($("help_pages_btn_" + this.programName)){
          if($("help_pages_list_" + this.programName).select('a').size()==1){
          	$("help_pages_btn_" + this.programName).onclick = function(){window.open($("help_pages_list_" + this.programName).down('a').href)}.bindAsEventListener(this);        	  
          }else{
        	$("help_pages_btn_" + this.programName).onclick = function(){$("help_pages_list_" + this.programName).scrollTo()}.bindAsEventListener(this);
          }
        }
		// update tab handle label
		if($(this.programName+'_tabHandle')){
		  $(this.programName+'_tabHandle').innerHTML = this.el.select('.formTitle h1')[0].innerHTML;
		}
      }else{
		// update tab handle label
		if($(this.programName+'_tabHandle')){
		  $(this.programName+'_tabHandle').innerHTML = this.el.select('.error_title')[0].innerHTML;
		}	  	
	  }
  },

  reset: function(){
	  this.build();
  },
  
  check: function(){
	  this.inputField.removeInvalidFieldMessage();
      var validatedFieldsResults = $H();
      if(this.emailInputField){
        validatedFieldsResults.set('email', this.emailInputField.check());
      }
      this.databoxes.values().each(function(param){
    	  if (param.valueInputField){
    		param.valueInputField.removeInvalidFieldMessage();
    	  }
        });
      this.parameters.values().each(function(param){
        param.removeInvalidFieldMessage();
      });
      var firstFalseFound = validatedFieldsResults.values().find(function(value){
          return (value==false);
        });
      return (firstFalseFound==undefined);
  },
  
  requestSubmit: function() {
	  if(user.email.blank() && $F('email_mandatory_'+ this.programName)=='true'){
		  user.setEmailPopUp.show(this.requestSubmit.bind(this));
		  return;
	  }
	  if(!user.authenticated && !user.activated){
		  user.captchaPopUp.show(this.requestSubmit.bind(this));
		  return;
	  }
	  if(user.sessionKey){
		  if(user.activated==true){
			  // submit right away
			  this.submit();
		  }else if (user.authenticated==true){
			  this.inputField.setInvalidFieldMessage("To be allowed to submit a job you need to confirm your e-mail adress. You can do so by clicking on the link we sent you.");
		  }else{
			  this.inputField.setInvalidFieldMessage("To be allowed to submit a job you need to find the solution to the captcha problem.");
		  }
	  }else{
		  this.inputField.setInvalidFieldMessage("You need to open a session on the system before to be able to submit a job.");
	  }
  },
    
  submit: function() {
      if(this.check()){
        // launching the job...
        this.p = new Object();
        if(user.email){
          this.p['email']=user.email;
        }
        if(this.emailInputField){
          this.p['email']=$F(this.emailInputField.el);
        }
        this.setCgiPars();
        // disabling the form and showing wait screen
        this.submitButton.disable();
        this.wait.show();
        // continue launching...
        this.p._action = 'getJSON';
        new Ajax.Request('session_job_submit.py',
        {
        method:'post',
        postBody: $H(this.p).toQueryString(),
        onSuccess: function(transport){
          // we get a json response, that is parsed to create the job's menu
			// entry
              user.updateFromCookie();
              var job = transport.responseText.evalJSON();
              if(job.id && !job.errormsg){ 
                job.program = this.programName;
				// the two following lines are here for ie-compatibility
				// purposes:
				// the hiding cannot be done in ie if the popups are in an
				// already hidden element
				$('jobs_tabPanel').getJobTab(job.id);
              }else{
          		// job help popup
          		this.jobHelpPopUp =	new JobHelpPopUp(this.programName, job);
                if(job.errorparam){
                  if (this.databoxes.get(job.errorparam)){
                    this.databoxes.get(job.errorparam).valueInputField.setInvalidFieldMessage(job.errormsg);
                  }
                  if (this.parameters.get(job.errorparam)){
                    this.parameters.get(job.errorparam).setInvalidFieldMessage(job.errormsg);
                  }
                 job.errormsg = job.errorparam + ': ' + job.errormsg;
                }else{
                  this.inputField.setInvalidFieldMessage(job.errormsg);
                }
              }
          }.bind(this),
        onComplete: function(){
          // hiding wait screen and enabling back the form...
          this.submitButton.enable();
          this.wait.hide();
          // update user environment data
          user.reload();
        }.bind(this)
        });
      }
  },
  
  setCgiPars: function() {
      var elements = Form.getElements(this.el);
      var f =this;
      elements.each(
      function(fe){
        if($F(fe)!=null){
          // Warning no parameter value should be stripped here, since some
			// data formats include white spaces and/or carriage returns (e.g.,
			// swissprot)
          f.p[fe.name] = $F(fe).toArray().join('');
        }
      }
      );
  }
  
});

var PFormParameter = Class.create({

  initialize: function(el) {
      this.el = el;
      this.name = this.el.first().name;
      try{
        this.prompt = $('for_'+this.el.first().id).innerHTML.unescapeHTML(); 
        // getting the parameter prompt using the DOM
      }catch(e){
        this.prompt = this.name;
      }
      Object.extend(this, new InputField(el.first(),el.first().up('.parameters'),this.prompt));
      this.mandatory = this.el.first().hasClassName('mandatory');	
      this.type = this.el.first().type;
      this.el.each(function(element){
        element.onchange = this.removeInvalidFieldMessage.bindAsEventListener(this);
	  }.bind(this));
  },
 
  getValue: function(){
      if(this.type=='radio'){
        var valueEl = this.el.find(function(element){
          return element.checked;
        });
        if(valueEl){
          return valueEl.value;
        }else{
          return null;
        }
      }else{
        return this.el.first().value;
      }
  }  
  
});

var Email = Class.create({

  initialize: function(el, formEl) {
      this.el = el;
      Object.extend(this, new InputField(el,formEl,"e-mail"));
            this.mandatory = this.el.hasClassName('mandatory');
      this.el.onchange = this.check.bindAsEventListener(this);
      if(user.email){
        // e-mail value must be preset and readonly if it has been previously
		// set in the session
        this.el.value=user.email;
      }
  },

  check: function(){
        re = /^.+@.+\..{2,3}$/;
        if(this.mandatory && this.el.value==''){
          this.setInvalidFieldMessage("mandatory to run this program");
          $('email_tabHandle').showTab();
          return false;
        }
        if(this.el.value!='' && !re.test(this.el.value)){
          this.setInvalidFieldMessage("Invalid e-mail value: "+this.el.value);
          $('email_tabHandle').showTab();
          return false;
        }else{
          this.removeInvalidFieldMessage();
          return true;
        }
  }
  
});

var RemoveJobPopUp = Class.create(AjaxPopUp,{

	initialize: function($super, jobId){
		$super('RemoveJob',jobId);
		this.jobId = jobId;
		this.enable();
	},

	proceed: function($super){
		$super();
		$(this.jobId+'_tabHandle').removeTab();
	}
});

var JobHelpPopUp = Class.create(AjaxPopUp,{

	initialize: function($super, tabId, jobInfo){
		$super('JobHelp',tabId);
		this.tabId = tabId;
		this.enable();
		this.confirmBox = new ModalBox('HelpConfirmation',this.tabId,'Box');
		Event.observe(this.tabId+'HelpConfirmationOk','click',
			function(e) {
				this.confirmBox.hide();
				Event.stop(e);
			}.bind(this)
		);
		this.jobInfo = jobInfo;
	},

	reset: function($super){
		$super();
		$(this.tabId+'JobHelpEmail').value = user.email;
		$(this.tabId+'JobHelpId').value = this.jobInfo['id'];
		$(this.tabId+'JobHelpDate').value = this.jobInfo['date'];
		$(this.tabId+'JobHelpStatus').value = this.jobInfo['status'];
		$(this.tabId+'JobHelpErrorParam').value = this.jobInfo['errorparam'];
		$(this.tabId+'JobHelpErrorMsg').value = this.jobInfo['errormsg'];
	},	
	
	proceed: function($super){
		$super();
		$(this.tabId+'HelpConfirmationMessage').value = this.answer.msg;
		this.confirmBox.show();
	}
});

/**
 * @class ProgramsList is a Widget class that represents the list of
 *        available programs on the server (and potentially elsewhere). It
 *        includes a search field that lets users filter the programs using a
 *        search string. It includes:
 *        <ul>
 *        <li>cgi call to get the list of programs</li>
 *        <li>Tree behaviour initialization</li>
 *        </ul>
 * 
 */
var ProgramsList = Class.create(Widget, {

	el :'programs_list_channel',

	cgiurl :'programs_list.py',

	initAjaxParams : function() {
	},

	onCompleteFunction : function() {
		// initializing search form values and backing them up
		if (this.cgiparams && this.cgiparams.searchString) {
			$("searchString").value = this.cgiparams.searchString;
			this.previousSearchString = this.cgiparams.searchString;
		} else {
			this.previousSearchString = '';
		}
		if (this.cgiparams && this.cgiparams.fullSearch) {
			var fsEl = $("fullSearch");
			for (i = 0; i < fsEl.options.length; i++) {
				if (fsEl.options[i].value.strip() == this.cgiparams.fullSearch
						.strip()) {
					fsEl.selectedIndex = i;
				}
			}
			this.previousFullSearch = this.cgiparams.fullSearch;
		} else {
			this.previousFullSearch = "on";
		}
		this.initProgramNodes();
		this.initCategoryNodes();
		Event.observe("programSearch", 'submit', function(e) {
			$("programSearch").disable();
			if ($("programsList")) {
				$("programsList").disable();
			}
			this.submitSearch();
			Event.stop(e);
		}.bindAsEventListener(this));
		if ($("programsList")) {
			Event.observe("programsList", 'click', function(e) {
				$("programSearch").disable();
				if ($("programsList")) {
					$("programsList").disable();
				}
				this.displayAll();
				Event.stop(e);
			}.bindAsEventListener(this));
		}

	},

	initProgramNodes : function() {
		var mpl = this;
		this.el.select(".tree_program").each( function(element) {
			new TreeProgramNode(element, mpl);
		});
	},

	initCategoryNodes : function() {
		this.el.select(".tree_category,.tree_server").each( function(element) {
			new TreeNode(element);
		});
	},

	submitSearch : function() {
		// before to set the timeout, we check if the search parameters
		// have really changed
		p = new Object();
		p.searchString = $F("searchString");
		this.cgiparams = p;
		this.build();
	},

	displayAll : function() {
		var p = new Object();
		this.cgiparams = p;
		this.build();
	}

});

var treeNodeMethods = {

	showTreeNode : function(element) {
		element = $(element);
		element.ancestors().each( function(ancestorEl) {
			if (ancestorEl.hasClassName("tree_category")) {
				Element.removeClassName(ancestorEl, 'tree_close');
			}
		});
	},

	hideTreeNode : function(element) {
		element = $(element);
		element.ancestors().each( function(ancestorEl) {
			if (ancestorEl.hasClassName("tree_category")) {
				Element.addClassName(ancestorEl, 'tree_close');
			}
		});
	}

};

Element.addMethods(treeNodeMethods);

/**
 * @class TreeProgramNode initializes the behaviour of a program link in
 *        the ProgramsList
 * 
 * @author Herve Menager
 * @organization Institut Pasteur
 * @contact mobyle@pasteur.fr
 * @extends Widget
 */
var TreeProgramNode = Class.create({

	initialize : function(el) {
		this.el = el;
		this.initializeBehaviour();
		Object.extend(this, this.el);
	},

	initializeBehaviour : function(el) {
		this.linkEl = this.el.select(".program_link").first(); 
		// N.B.: There is only one link for a category
		Event.observe(this.linkEl, 'click', function(e) {
			Event.stop(e);
			this.showService();
		}.bindAsEventListener(this));
	},

	showTooltip : function() {
		if (this.timeOutTooltipId) {
			clearTimeout(this.timeOutTooltipId);
		}
		this.timeOutTooltipId = setTimeout(( function() {
			Element.show(this.tooltipEl)
		}).bind(this), 500);
	},

	hideTooltip : function() {
		if (this.timeOutTooltipId) {
			clearTimeout(this.timeOutTooltipId);
		}
		Element.hide(this.tooltipEl);
	},

	moveTooltip : function(e) {
		Element.setStyle(this.tooltipEl, {
			position :"absolute",
			top :(Event.pointerY(e) - e.currentTarget.offsetParent.offsetTop)
					+ "px",
			left :(Event.pointerX(e) - e.currentTarget.offsetParent.offsetLeft)
					+ "px"
		});
	},

	showService : function() {
		// get the program name corresponding to the form that has to be
		// generated
		// create a form tab by invoking the tab's id (program name here)
		var programUrl = this.linkEl.href;
		var programName = this.linkEl.innerHTML;
		$('services_tabPanel').getFormTab(programUrl, programName);

	}
});

/**
 * @class TreeNode initializes the behaviour of a category node in the
 *        ProgramsList
 * 
 * @author Herve Menager
 * @organization Institut Pasteur
 * @contact mobyle@pasteur.fr
 * @extends Widget
 */
var TreeNode = Class.create({
	
	initialize : function(el) {
		this.el = el;
		this.initializeBehaviour();
	},

	initializeBehaviour : function(el) {
		this.linkEl = this.el.select(".tree_node_link").first(); 
		// N.B.: There is only one link for a category
		Event.observe(this.linkEl, 'click', function(e) {
			this.toggleTreeStyle();
			Event.stop(e);
		}.bindAsEventListener(this));
		if ($("searchString").value == '') {
			this.toggleTreeStyle();
		}
	},
	
	toggleTreeStyle : function() {
		if (!Element.hasClassName(this.el, 'tree_close')) {
			Element.addClassName(this.el, 'tree_close');
		} else {
			Element.removeClassName(this.el, 'tree_close');
		}
		return false;
	}
});

var Job = Class.create(Widget, {

	cgiurl :'job_view.py',

	initAjaxParams : function() {			
		this.el = this.otherparams.get('el');
		this.jobid = this.cgiparams.get('jobid');
		this.parameters.set('_action', 'getHTML');
		this.bwatchFn = this.watchFn.bindAsEventListener(this);
		this.bremoveFn = this.removeFn.bindAsEventListener(this);
		document.observe('mobyle:feedupdate/'+user.workspace.url,this.bwatchFn);		
		document.observe('mobyle:tabremoved',this.bremoveFn);
	},
	
	watchFn : function(event){
		if ($(this.jobid + '_status')){
			if(!user.workspace.data['jobs'][this.jobid]){
				$(this.jobid+'_tabHandle').removeTab();
			}else if(user.workspace.data['jobs'][this.jobid]['status']!=$(this.jobid + '_status').value){
				this.updateStatus();
			}
		}		
	},
	
	removeFn : function(event){
		if(event.memo.handleId==this.jobid+'_tabHandle'){
			document.stopObserving('mobyle:feedupdate/'+user.workspace.url,this.bwatchFn);
			document.stopObserving('mobyle:tabremoved',this.bremoveFn);
		}
	},

	onCompleteFunction : function() {
		this.programName = $(this.jobid + '_programName').value;
		this.initResultBoxes();
		this.jobInfo = {
				'id': this.jobid,
				'date': $(this.jobid + '_date').value,
				'status': $(this.jobid + '_status').value,
				'errormsg': $(this.jobid + '_message').value,
				'shortName': $(this.jobid + '_shortName').value
		}
		// job help popup
		this.jobHelpPopUp =	new JobHelpPopUp(this.jobid, this.jobInfo);
		// remove job popup
		this.removeJobPopUp = new RemoveJobPopUp(this.jobid);
		// update status button
        this.wait = new ModalBox('Generic',this.jobid,'Wait');
        Event.observe(this.el.down(".update_status"),'click',
    		function(el){
    			this.updateStatus();
			}.bind(this));
		// go back to the form button
		Event.observe('backtoform_' + this.jobid, 'click',
				function(e) {
					$('services_tabPanel').getFormTab(
							this.programName);
					Event.stop(e);
				}.bindAsEventListener(this));
		// update tab handle label
		if ($(this.jobid + '_tabHandle')) {
			$(this.jobid + '_tabHandle').innerHTML = this.jobInfo['shortName']
					+ "<br/>" + this.jobInfo['date'];
		}
	},

	initResultBoxes : function() {
		this.el.select(".results_file").each(
			function(element) {
				var parameterName = element
						.select('.parameterName')[0].value;
				var fileName = element
						.select('.fileName')[0].value;
				var resultId = element
						.select('.resultId')[0].value;
				if (element.select(
						'.parameterDataType')
						.size() > 0) {
					var dataType = element
							.select('.parameterDataType')[0].value;
				} else {
					var dataType = null;
				}
				if (element.select(
						'.parameterBioType').size() > 0) {
					var bioTypes = element.select(
							'.parameterBioType')
							.pluck('value');
				} else {
					var bioTypes = null;
				}
				if (element
						.select('.parameterCard')
						.size() > 0) {
					var card = element
							.select('.parameterCard')[0].value;
				} else {
					var card = null
				}
				new ResultBox(element, this,
						this.programName,
						parameterName, fileName,
						resultId, dataType,
						bioTypes, card);
			}.bind(this)
		);
	},
	
	updateStatus : function() {
		this.wait.show();
		new Ajax.Request('job_status.py', {
			method :'get',
			parameters :$H({'jobId':this.jobid}).toQueryString(),
			onSuccess : function(transport) {
				result = transport.responseText.evalJSON();
				if(this.jobInfo['status']!=result['status']){
					this.build(); // update the whole result page
				}else if(this.jobInfo['errormsg']!=result['msg']){
					// only update status message
					$(this.jobid + '_status').value = result['msg'];
					this.el.down('.errormsg').update(result['msg']);
					this.jobInfo['errormsg'] = result['msg'];
				}
			}.bind(this),
			onComplete : function(transport) {
        		this.wait.hide();
			}.bind(this)
		});
	}

});

var ResultBox = Class.create({

	initialize : function(el, job, programName, parameterName, fileName,
			resultId, dataType, bioTypes, card) {
		this.el = el;
		this.inputField = new InputField(this.el);
		this.job = job;
		this.jobId = job.id;
		this.programName = programName;
		this.parameterName = parameterName;
		this.fileName = fileName;
		this.resultId = resultId;
		this.dataType = dataType;
		this.bioTypes = bioTypes;
		this.card = card;
		this.maxCard = this.card.charAt(this.card.length - 1);
		this.selectEl = this.el.select(".pipeselect").first();
		pipesSortFunction = function(pipe) {
			return pipe.key;
		}
		this.pipeLinkEl = this.el.select(".piperesultlink").first();
		this.dataSelectPipes = new (Class.create(DataSelect,{
			id : this.selectEl.id,
			optionValue : new Template('#{key}'),
			optionTitle : new Template('#{value.programTitle} - #{value.prompt}'),
			optionText : new Template('#{value.programName} (#{value.name})'),
			submitEl : this.submitEl,
			feed : portal.inputs,
			filterFn : this.pipeFilter.bind(this),
			sortFn : pipesSortFunction,
			cpEls: $A([this.selectEl.up()])
		}))();
		this.saveLinkEl = this.el.select(".saveresultlink").first();
		if (this.pipeLinkEl) {
			this.pipeLinkEl.onclick = this.pipe.bindAsEventListener(this);
		}
		this.saveLinkEl.onclick = this.addResult.bindAsEventListener(this);
	},
	
	pipeFilter : function(input) {
		var input = input.value;
		dt = portal.datatypes.data[this.dataType];
		if (dt && dt.ancestorTypes.include(input.dataTypeClass)) { 
			// datatype filter
			// we do not take cardinalities into account, given the lack of data
			// cardinality management for now in Mobyle
			// if(input.maxCard>=this.maxCard || input.maxCard=='n'){ // maximal
			// cardinality filter
			if (this.bioTypes == null || input.bioTypes == null
					|| this.bioTypes.size() == 0 || input.bioTypes.size() == 0
					|| this.bioTypes.findAll( function(bt) {
						if (input.bioTypes.include(bt))
							return bt;
					}).size() > 0) {
				return true;
			}
			// }
		}
		return false;
	},

	pipe : function() {
		if (!$F(this.selectEl).blank()) {
			this.pipe = $F(this.selectEl).split("|");
			this.pipeInputProgram = this.pipe[0];
			this.pipeInputParameter = this.pipe[1];
			this.addResult(this.pipeCallback);
			// save function creates a pipe in the callback if pipe flag is activated
		}
	},

	addResult : function(callback) {
		this.inputField.removeInvalidFieldMessage();
		Form.Element.disable(this.pipeLinkEl);
		Form.Element.disable(this.saveLinkEl);
		this.callback = callback;
		this.p = new Object();
		this.p.fileName = this.fileName;
		this.p.parameterName = this.parameterName;
		if (!$F(this.resultId).blank()) {
			this.p.newFileName = $F(this.resultId);
		}
		this.p.jobId = this.job.jobid;
		new Ajax.Request('data_bookmark.py', {
			method :'post',
			postBody :$H(this.p).toQueryString(),
			onSuccess : function(transport) {
				result = transport.responseText.evalJSON();
				this.safeFileName = result.safeFileName;
				if (result.errormsg) {
					this.inputField.setInvalidFieldMessage(result.errormsg);
				} else {
					cb = typeof(this.callback)=='function' ? this.callback.bind(this) : null;
					user.workspace.get(null,cb);
				}
			}.bind(this),
			onComplete : function() {
				this.onSuccessFunction = null;
				this.pipeLinkEl.enable();
				this.saveLinkEl.enable();
			}.bind(this)
		});
	},

	pipeCallback : function() {
		try{
			this.callback = undefined;
			$('services_tabPanel').getFormTab(this.pipeInputProgram,
					this.pipeInputParameter, this.safeFileName);
		}catch(e){
			logError(e);
		}
	}

});

var Portal = Class.create({

	initialize : function(el) {
		portal = this; 
		// we declare the object to be able to refer to it even
		// during initialization (hack...)
		this.el = el;
		this.drawer = new Drawer($("drawer"));
		new ProgramsList();
		$('portalMain').insertTab('welcome', 'Welcome');
		$('portalMain').insertTab('services', 'Programs');
		$('services_tabPanel').insertTabsContainer();
		$('portalMain').insertTab('data', 'Data Bookmarks');
		$('portalMain').insertTab('jobs', 'Jobs');
		$('jobs_tabPanel').insertTabsContainer();
		$('portalMain').insertTab('tutorials', 'Tutorials');
		$('tutorials_tabPanel').insertTabsContainer();
		$('welcome_tabHandle').showTab();
		// link from data bookmarks menu
		Event.observe("filesListUl", 'click', function(e) {
			$('data_tabHandle').showTab();
			Event.stop(e);
		}.bindAsEventListener(this));
		// tutorial links mechanism initialization
		$("tutorialsdd").down().descendants().each( function(el) {
			if (el.href) {
				Event.observe(el, 'click', function(e) {
					$('tutorials_tabPanel').getTutorialTab(el.href, el.title);
					Event.stop(e);
				}.bindAsEventListener(this));
			}
		}.bind(this));
		this.banks = new Feed('banks_list.py');
		this.inputs = new Feed('inputs_list.py');
		this.datatypes = new Feed('datatypes_list.py');
		this.filesList = new (Class.create(FilesUList, {feed:user.workspace}))();
		$('filesListRefreshButton').onclick = user.workspace.get.bind(user.workspace);
		this.forms = $H();
		// setting up the data management page
		this.dataManagement = new (Class.create(FilesTable, {feed:user.workspace}));
		this.dataGauge = new (Class.create(FilesGauge, {feed:user.workspace}));
		this.jobsList = new (Class.create(JobsUList, {feed:user.workspace}))();
		$('jobsListRefreshButton').onclick = user.workspace.get.bind(user.workspace);
		if ($('openedForms')) {
			$('openedForms').value.split(',').each( function(form) {
				$('services_tabPanel').getFormTab(form);
			});
		}
		if ($('openedJobs')) {
			$('openedJobs').value.split(',').each( function(job) {
				$('jobs_tabPanel').getJobTab(job);
			});
		}
		new ToolTipManager();
		if($('autoRefreshFreq').value>0){
			new PeriodicalExecuter(user.workspace.get.bind(user.workspace),$('autoRefreshFreq').value);
		}
		// managing comments here now
		Event.observe(document.body, 'click', function(e) {
			if(e.element().hasClassName('commentToggle')){
			    $(e.element().readAttribute('for')).toggle();
				Event.stop(e);
			}
		});
	}

});

var ToolTipManager = Class.create({

	initialize : function() {
		this.initializeBehaviour();
	},

	initializeBehaviour : function(el) {
		Event.observe(document.body, 'mouseover', function(event) {
			var element = Event.element(event);
			// if a tooltip exists for this program, hide it and show it only
			// when mouse is over it
				if (element && typeof element.next == "function"
						&& element.next()
						&& element.next().hasClassName('tooltip')) {
					var tooltipEl = element.next();
					this.showTooltip(this, tooltipEl);
					element.onmouseover = this.showTooltip.bindAsEventListener(
							this, tooltipEl);
					element.onmouseout = this.hideTooltip.bindAsEventListener(
							this, tooltipEl, element);
					tooltipEl.onmouseout = this.hideTooltip
							.bindAsEventListener(this, tooltipEl, element);
					element.onmousemove = this.moveTooltip.bindAsEventListener(
							this, tooltipEl);
					tooltipEl.onclick = element.onclick;
				}
			}.bind(this));
	},

	showTooltip : function(e, tooltipEl) {
		if (this.timeOutTooltipId) {
			clearTimeout(this.timeOutTooltipId);
		}
		this.timeOutTooltipId = setTimeout(( function() {
			Element.show(tooltipEl)
		}).bind(this), 500);
	},

	hideTooltip : function(e, tooltipEl, element) {
		if (e.relatedTarget != tooltipEl && e.relatedTarget != element) { 
			// do not accept this event if we leave to enter the tooltip element
			if (this.timeOutTooltipId) {
				clearTimeout(this.timeOutTooltipId);
			}
			Element.hide(tooltipEl);
		}
	},

	moveTooltip : function(e, tooltipEl) {
		if(e.currentTarget!=null){
			Element.setStyle(tooltipEl, {
				position :"absolute",
				top :(Event.pointerY(e) - e.currentTarget.offsetParent.offsetTop)
						+ "px",
				left :(Event.pointerX(e) - e.currentTarget.offsetParent.offsetLeft)
						+ "px"
			});
		}
	}
});

var Drawer = Class.create({

	initialize : function(el) {
		this.el = el;
		this.titleEls = this.el.select("dt");
		$A(this.titleEls).each( function(dtEl) {
			Event.observe(dtEl, 'click', function(event) {
				Event.element(event).toggleDrawerItem();
				Event.stop(event);
			});
		});
	}

});

var drawerMethods = {

	showDrawerItem : function(element) {
		drawerMethods.invokeDrawerItem(element, 'show');
	},

	hideDrawerItem : function(element) {
		drawerMethods.invokeDrawerItem(element, 'hide');
	},

	toggleDrawerItem : function(element) {
		drawerMethods.invokeDrawerItem(element, 'toggle');
	},

	invokeDrawerItem : function(element, action) {
		if ($(element).match('dt') && $(element).next('dd')) {
			Element.Methods[action]($(element).next('dd'));
		}
	}

}

Element.addMethods(drawerMethods);

function readCookieValue(key) {
	var regex = new RegExp(key + '\s*="?(.*?)"?(;|$)');
	var cookie = document.cookie.toString();
	var match = cookie.match(regex);
	return match ? unescape(match[1]): '';
}

function editCookieValue(key, value) {
	var regex = new RegExp(key + '\s*="?(.*?)"?(;|$)');
	var repl = key+'='+encodeURIComponent(value)+'';
	var cookie = document.cookie.toString();
	document.cookie = repl;
	return;
}

function readUrlParameterValue(name) {
  name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  var regexS = "[\\?&]"+name+"=([^&#]*)";
  var regex = new RegExp( regexS );
  var results = regex.exec( window.location.href );
  if( results == null )
    return "";
  else
    return results[1];
}

//firebug logging
function logError(err) {
	console.group("Mobyle error");
	console.dir(err);
	console.trace();
	console.groupEnd();
}

Ajax.Responders
		.register( {
			// this hack forces to avoid caching throughout every ajax request,
			// because IE tends to force cache when using GET requests.
			onCreate : function(o) {
				o.options.requestHeaders = $H( {
					"Cache-Control" :"post-check=1,pre-check=2,no-cache,must-revalidate",
					"If-Modified-Since" :"Sat, 1 Jan 2000 00:00:00 GMT"
				});
			},
			// this traces any ajax exception that might be raised
			onException : function(req, err) {
				logError(err);
			}
		});

// this wraps any event handling function to catch raised exceptions
Event.observe = Event.observe.wrap( function(proceed, element, eventName,
		handler) {
	handler = handler.wrap( function(proceed, e) {
		try {
			return proceed(e);
		} catch (err) {
			logError(err);
		}
	});
	return proceed(element, eventName, handler);
});

Event.observe(window, 'load', function() {
	options = {
		'anonymousSession' : ($F('anonymousSession') == "True"),
		'authenticatedSession' : ($F('authenticatedSession') == "True")
	};
	user = new User();
	portal = new Portal("mainContainer");
});