window.addEvent('domready', function() { previewOnLoad() } );
$(document.body).addClass(Browser.name);
Element.implement({
	serialize : function() {
//	    var QS = this.toQueryString();
		return this.toQueryString().parseQueryString();
	},
	appendTopHTML: function(html) {
		this.set({
			'html':html + this.innerHTML
		});
	},
	appendBottomHTML: function(html) {
		this.set({
			'html':this.innerHTML + html
		});
	},
	asString: function() {
		var el = new Element('div');
		el.adopt(this);
		var markup = el.innerHTML;
		return markup;

	},
	formData: function(){
		var data = {};
		this.getElements('input, select, textarea', true).each(function(el){
		  if (!el.name || el.disabled ) return;
			var value = (el.tagName.toLowerCase() == 'select') ? el.get('value') : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? '' : el.value;
			if (data[el.name]){
			  // already there, so separate the new one with a ";"
			  if( value===''){
			    value =  data[el.name];
			  } else {
			    value =  data[el.name] + ";"+value;
			  }
			}
			data[el.name] = value;
		});
		return data;
	},
	dataForm: function(data){
		this.getElements('input, select, textarea', true).each(function(el){
			if(!el.name) return;
			if(data[el.name]) {
				if(el.type=='radio' || el.type=='checkbox') {
					var bits=data[el.name].split(';');
					bits.each(function(bit) {
						if(el.value==bit) el.checked=true;
					})
				} else {
					el.set('value', data[el.name])
				}
			}
		})
	}
});
/* scripts live here that are loaded for all readers */
function checkEmail(inputvalue){	
var pattern=/^([a-zA-Z0-9_.-])+@([a-zA-Z0-9_.-])+\.([a-zA-Z])+([a-zA-Z])+/;
var bool = pattern.test(inputvalue);
return bool;
}
var shimmer2 = new Class({
	Implements: Options,
    options: {
    	row: null,
    	borderSize: 10,
    	minHeight: 100,
    	minWidth: 100,
    	afterSaveEntry: null
    },
	initialize: function(options) {
		this.closed = false; // in case we want to know from outside this function
		this.setOptions(options);
		if(options.row) {
   			this.row = options.row; // for saving changes
  		} else {
    		this.row = null;
  		}
		var el = $(this.options.divId)
		var elSize = el.getSize();
		var elPos = el.getPosition();
		var hheight=elSize.y+20; // cover action buttons under divtable
		if(hheight<this.options.minHeight+2*this.options.borderSize) hheight=this.options.minHeight+2*this.options.borderSize;
		var wwidth=elSize.x;
		if(wwidth<this.options.minWidth+2*this.options.borderSize) wwidth=this.options.minWidth+2*this.options.borderSize;
		var attrs = {
		  'id' : 'shimmer_editDiv',
		  'style' : 'position: absolute; left: '+elPos.x+'px; top: '+elPos.y+'px; height: '+(hheight)+'px; width: '+wwidth+'px; z-index:1000;'
		}; // elSize.y + 20 allows for additional height of div holding quicklinks at the bottom of the table
		this.editDiv = new Element('div', attrs);
		this.editDiv.inject(el, 'after');
		this.editDiv.set('opacity', '0.7');
		hheight=elSize.y+20-2*this.options.borderSize;
		if(hheight<this.options.minHeight) hheight=this.options.minHeight;
		wwidth=elSize.x-2*this.options.borderSize;
		if(wwidth<this.options.minWidth) wwidth=this.options.minWidth;
		attrs = {
		   'id' : 'shimmer_editDivInner',
		   'style' : 'position: absolute; left: '+(elPos.x + this.options.borderSize)+'px; top: '+(elPos.y+this.options.borderSize)+'px; height: '+hheight+'px; width: '+wwidth+'px; z-index:1001;'
		}
		this.editDivInner = new Element('div', attrs);
		this.editDivInner.inject(this.editDiv, 'after');
		this.el = this.editDivInner; // sometimes need to know the el that contains the stuff
		this.options.showDiv(options.row, this.editDivInner, this); // paint contents of editDivInner, pass options.row because this.options is a copy
		attrs = {
		   'id' : 'shimmer_closeIcon',
		   'style' : 'position: absolute; border: 0; padding: 0; left: '+(wwidth-20) + 'px; top: -5px;',
		   'src' : '/' + db + '/files/images/$file/redX30.png'
		}
		this.closeIcon = new Element('img', attrs);
		this.closeIcon.inject(this.editDivInner, 'bottom');
		this.closeIcon.addEvent('click', this.closeEntry.bind(this));
	},
	saveEntry : function(button) {
		var createNew = !this.row.UNID;
		if(createNew) {
			var data=JSlib.divtable.options.data;
			data.push({}); // add an empty object to the data array
			this.row = data[data.length-1];
		}
		var request = $H($(this.el).formData());
		request.each(function(el, index) { // store in divTable data for display now
			this.row[index]=el
		}, this);
		submitAJAX('/'+db+'/databaseAJAXInput?openagent', request, button, this.options.afterSaveEntry.bind(this)) // send to server
		if(!createNew) { // if createNew, calling routine must call divtable.changeData(), after sorting if necessary
			JSlib.divtable.redraw();
		}
	},
	closeEntry : function() {
		this.closeIcon.removeEvents('click');
		this.closeIcon.dispose();
		if(this.options.beforeClose) this.options.beforeClose(this)
		this.editDivInner.dispose();
		this.editDiv.dispose();
		this.closed = true; // although the DOM is emptied, this object remains as a ghost
	} 
});
var shimmer = new Class({ // copied from tripdiary.js -> develop into a general display/edit facility
  initialize: function (row, divID, minWidth, minHeight, showDiv) {
  if(row) {
    this.row = row; // for saving changes
  } else {
    this.row = null;
  }
// get location of div, and dimensions
  el = $(divID);
  elSize = el.getSize();
  elPos = el.getPosition();
  this.borderSize = 40;
  var hheight=elSize.y+20;
  if(hheight<minHeight+2*this.borderSize) hheight=minHeight+2*this.borderSize;
  var wwidth=elSize.x;
  if(wwidth<minWidth+2*this.borderSize) wwidth=minWidth+2*this.borderSize;
  var attrs = {
    'id' : 'editDiv',
    'style' : 'position: absolute; left: '+elPos.x+'px; top: '+elPos.y+'px; height: '+(hheight)+'px; width: '+wwidth+'px; z-index:1000;'
  }; // elSize.y + 20 allows for additional height of div holding quicklinks at the bottom of the table
  var editDiv = new Element('div', attrs);
  editDiv.inject(el, 'after');
  editDiv.set('opacity', '0.7');
  hheight=elSize.y+20-2*this.borderSize;
  if(hheight<minHeight) hheight=minHeight;
  wwidth=elSize.x-2*this.borderSize;
  if(wwidth<minWidth) wwidth=minWidth;
  attrs = {
    'id' : 'editDivInner',
    'style' : 'position: absolute; left: '+(elPos.x + this.borderSize)+'px; top: '+(elPos.y+this.borderSize)+'px; height: '+hheight+'px; width: '+wwidth+'px; z-index:1001;'
  }
  var editDivInner = new Element('div', attrs);
  editDivInner.inject(editDiv, 'after');
  this.el = $('editDivInner'); // sometimes need to know the el that contains the stuff
  showDiv(row, editDivInner, this); // paint contents of editDivInner
  attrs = {
    'id' : 'closeIcon',
    'style' : 'position: absolute; border: 0; padding: 0; left: '+(wwidth-20) + 'px; top: -5px;',
    'src' : '/' + db + '/files/images/$file/redX30.png'
  }
  var closeIcon = new Element('img', attrs);
  closeIcon.inject(editDivInner, 'bottom');
  closeIcon.addEvent('click', this.closeEntry.bind(this));
},
  closeEntry : function() {
  $('closeIcon').removeEvents('click');
  $('closeIcon').dispose();
  $('editDivInner').dispose();
  $('editDiv').dispose();  
}
})
function submitAJAX(url, form, button, callback) { // wrapper to submit a HTML form to a lotusscript agent
/* url: agent to call, must expect JSON and return JSON with standard vars
   form: can be a div, all fields will be serialised and sent to the server agent as JSON
   button: the button clicked on, gets disabled and a loading image appears after it in the DOM
   callback: function to call if AJAX returns success and agent status=true */
	var loadingEl = $(button) ? disableEl(button, 'loadingEl') : "";
	if(form==null) {
	  var apars = {}
	} else if(instanceOf(form, Hash)) {
	  apars = form 
	} else {
   	  var formFields=$(form).formData(); // get values of fields in the form
	  var apars = new Hash ( formFields )
	}
	var nargs = submitAJAX.arguments.length; // is a callback supplied?
	new Request.JSON({
		url : url,
		method: 'post',
  		data: {json: JSON.encode(apars)},
  		onSuccess: function(responseJSON, responseText) {
  			if(responseJSON.response && !responseJSON.response.trim()=="") alert(responseJSON.response);
  			if (!responseJSON.status) {
  				if($('recaptcha_response_field')) Recaptcha.reload();
  			} else if(responseJSON.cookie) {
  				document.cookie = responseJSON.cookie
  			}
  			if(responseJSON.redirect) {
  				if(responseJSON.newWindow) {
  					window.open(responseJSON.redirect);
  				} else {
  					document.location.href = responseJSON.redirect;
  				}
  			}
  			if($('loadingEl')) $('loadingEl').dispose();
			if($(button) && (responseJSON.enableButton || !responseJSON.status)) button.set({'disabled': false});
			if(nargs==4 && responseJSON.status) { // callback function provided
				callback(responseJSON)
			}
		},
  		onFailure: function(transport) {
  			alert('Error in making your request - ' + transport.responseText );
  			if($('loadingEl')) $('loadingEl').dispose();
  			$(button) ? $(button).set({'disabled': false}) : ""; // allow resubmit on error
			return false
  		}
	}).send();
}
function disableEl(el, id) { // disable element and show loading GIF adjacent
	$(el).set('disabled', true); 
	var attrs = {
                'src'   : '/' + db + '/loading.gif?openImageResource',
                'alt' : 'loading',
                'id' : id
            };
    var image = new Element('img', attrs);
    image.inject($(el), 'after');
}
function storeHTMLobject(responseJSON) { // response from agent getHTMLobject
alert('storeHTMLobject is deprecated, use storeNavElement instead');
  JSlib.HTMLobject[responseJSON.title]=responseJSON.value;
}
function storeNavElement(responseJSON) { // response from agent getNavElement
  JSlib.navElement[responseJSON.title]=responseJSON.value;
  if(this.afterNavElement) this.afterNavElement(responseJSON.title);// user callback
}
function previewOnLoad() {
	var form=document.forms[0];
	JSlib.domReady=true; // user scripts can use this to execute only when DOM is loaded
	if(onLoad!="") eval(onLoad);
	if(widget) {return true};
	if(editor!="1" | !editActive) {return true};
	field=form.databaseWhich; // uncheck radio button - see script library preview setDatabasesInner & foll.
	if(field) for(i=0;i<field.length;i++) field[i].checked=false;
	field=document.forms[0].HTMLobjectWhich; // uncheck radio button - see preview setHTMLobjectInner
	if(field) for(i=0;i<field.length;i++) field[i].checked=false;
	field=document.forms[0].navElementWhich; // uncheck radio button, may have been saved in document
	if(field) for(i=0;i<field.length;i++) field[i].checked=false; // see preview setAddNavInner
	if(form.request) form.request.value=""; // prevent requests repeating unintentionally	   
     var cookieVal = getCookie('CMS_editarea');
	var width;
	var height;
	var left;
	var top;
     if(cookieVal==null) {
		width=form.mainwidth.value; 
	     height=form.mainheight.value;
	     left=form.mainleft.value;
	     top=form.maintop.value
     } else {
          var cookievalue = cookieVal.split(",");
          width = cookievalue[0];
          height = cookievalue[1];
          left=cookievalue[2];
          top=cookievalue[3];
     }
	dd.elements.editmain.resizeTo(width, height)
	document.getElementById("HTMLmain").style.width=Math.floor(width)-20;
	document.getElementById("HTMLmain").style.height=Math.floor(height) - 60;
	dd.elements.editmain.moveTo(left, top);
	if(form.tag) form.deftag = form.tag.value;
	if(form.errors && form.errors.value!="") showErrors();
}
function login() {
	if(editor!="1") {
		document.location.href='/' + db + '/index/'+tag+'?opendocument&login&menu';
		}
	else {
		document.getElementById('defaultMenu').style.visibility='visible';
		}
}
if($('CMS_hotspot')) $('CMS_hotspot').addEvent('click', login);
function queryString(keyval) {
	var URL = document.location.href;
	if(URL.indexOf(keyval.toLowerCase())<0) return '';
	var param = URL.substr(URL.toLowerCase().indexOf('&'+keyval.toLowerCase()+'=')+2+keyval.toLowerCase().length);
	if(param.indexOf('&')>-1) param = param.substring(0, param.indexOf('&'));
	return unescape(param)
}
function userSubmit() {
var form=document.forms.response;

var url = "/" + AjaxFilepath() + "/userFormSubmit?OpenAgent";
var button = document.getElementById("sendResponse");
return submitAJAX(url, form, button)
}
function afterUserSubmit(responseJSON) { // was used in userSubmit - need to return responseJSON somehow
var outputDiv=document.getElementById('divOutput');
var tryAgain = responseJSON.tryAgain;
if(tryAgain=="1") {
	var button = document.getElementById("sendResponse");
	button.disabled = false;
}
outputDiv.innerHTML = responseJSON.results;
}
function AjaxSendToFriend(form, button) {
	var errors="";
	var err = false;
//	N.B. the agent called by this function requires the reCAPTCHA fields to be present
 	err = false;
	errors = "";
	var field = form.recaptcha_response_field;
	if(field.value.trim()=="") {
  		err = true;
  		errors = "You must provide an answer to the reCAPTCHA character recognition.\n";
	}
	field = form.newsletter;
	if(!field) {
		err = true;
		errors = "the form must have a newsletter field";
	}
	if(err) {
		alert(errors);
		return false;
	}
	var url = "/" + db + "/sendToFriendAJAX?OpenAgent";
	return submitAJAX(url, form, button)
}
	
function ajaxDatabase(form, button) { // save input and textarea fields via Ajax  - with Prototype serialisation
//	N.B. the agent called by this function requires the reCAPTCHA fields to be present
 	err = false;
	errors = "";
	var field = $('recaptcha_response_field');
	if(field.get('value').trim()=="") {
  		err = true;
  		errors = "You must provide an answer to the reCAPTCHA character recognition.\n";
	}
	if(err) {
		alert(errors);
		return false;
	}
	var url = "/" + db + "/databaseAjaxInput?OpenAgent";
	return submitAJAX(url, form, button)
}
function validateUnsubscribe(form, button) {
	var errors="";
	var err=false;
	if(!form.option) {
		errors += "The unsubscribe form must have an 'option' field\n";
		err = true
	} else {
		var found = false
		for(var i=0; i<form.option.length; i++) {
			found = found || form.option[i].checked
		}
		if(!found) {
			errors += "You must select an option\n";
			err = true;
		}
	}
	if (form.option[1].checked) {
		if(!form.toDate) {
			errors += "The unsubscribe form must have a 'toDate' field\n";
			err = true;
		} else {
			if(form.toDate.value=="" || !isDate(form.toDate.value, 'd/M/y')) {
				errors += "Date must be in dd/mm/yyyy format";
				err = true;
			}
		}
	}
	if(!form.UNID) {
		errors += "The unsubscribe form must have a 'UNID' field\n";
		err = true;
	} else if (form.UNID.value.length!=32) {
		errors += "UNID must be 32 characters\n";
		err = true;
	}
	if(err) {
		alert(errors);
		return false
	}
	var UNID = ( form.UNID ? form.UNID.value : "" );
	if(UNID=="") { // UNID should be a form field (hidden), if absent or blank, will submit the form
		form.submit() // agent saveForm2 uses the UNID field to locate the document
		return true
	}
	var url = "/" + db + "/unsubscribeAJAX?OpenAgent";
	return submitAJAX(url, form, button)
	}
/*
Script: jsextentions.js
Provides the extentions to the built in Javascript classes.

Copyright:
Copyright (c) 2010 CBDWeb

Revision: 
$Revision: 804 $

Extends the String class and the Element class.

String gets 3 additional functions.
	rightOf( str ), which returns everything to the right of a given string.  i.e. "Hello big world".rightOf("ll") would return "o big world"
	fmtmap( str ), maps a string to a format string.
		for example  "john".fmtmap("Aaaa") will turn "john" to "John"
		"john".fmtmap("AaaA") will turn "john" to "JohN"
		"john".fmtmap("Ax") will turn "john" to "John"


I've no idea what stringSub is...

Also creates Autocompleter.Function from Autocompleter.Local


relies on

*/



String.implement({
	/**
	 * String.fmtmap
	 * Putpose:
	 * 		Take a given string, and apply a uppercase/lower case mapping.
	 * 		for example  "john".map("Aaaa") will turn "john" to "John"
	 * 		"john".map("AaaA") will turn "john" to "JohN"
	 * 		"john".map("Ax") will turn "john" to "John"
	 * @param {String} str
	 */
	fmtmap : function(str){
		// this functionmaps a string to a format string.
		// for example  "john".map("Aaaa") will turn "john" to "John"
		// "john".map("AaaA") will turn "john" to "JohN"
		// "john".map("Ax") will turn "john" to "John"

		var result = '';
		for( var i = 0; i < str.length; i++){
			var fmt = str.substr(i,1);
			var nextchar  = this.substr(i,1);

			if(fmt == "A"){
				// maje this letter capital.
				result = result + nextchar.toUpperCase();
			} else if(fmt == "a") {
				// make this letter lowercare
				result = result + nextchar.toLowerCase();
			}	else if(fmt == "x") {
				// give me everything else.
				result = result + this.substr(i);
			} else if( fmt =="#" ) {
				// just treat this as an integer
				return (""+parseInt( this, 10 ));  // treat this as a decvimal value.
			}
		}
		return result;
	},
	/**
	 * String.left
	 * Purpose:
	 * 		Returns the n leftmost characters of the string.
	 * 		If less characters in this string, it gives all that are available.
	 * @param {Object} n
	 */
	left : 	function(n){
						if (n <= 0){return "";}
						else if (n > this.length){return this;}
						else { return this.substring(0,n);}
					},
	/**
	 * String.right
	 * Purpose:
	 * 		Returns the n rightmost characters of the string.
	 * 		If less characters in this string, it gives all that are available.
	 * @param {Object} n
	 */
	right	: function(n){
 	   				if (n <= 0){ return "";}
 	   				else if (n > this.length){ return this;}
 	   				else {
 	      			var iLen = this.length;
 	      			return this.substring(iLen, iLen - n);
 	   				}
				},
	/**
	 * String.leftOf
	 * Purpose:
	 * 		Returns all of the characters to the left of the first occurance of the given string.
	 * 		If the given string is not present, it returns ""
	 * @param {Object} n
	 */
	leftOf: function(str){
					var pos = this.indexOf(str);
					return pos <= 0 ? "" : this.left(pos);
				},
	/**
	 * String.rightOf
	 * Purpose:
	 * 		Returns all of the characters to the right of the first occurance of the given string.
	 * 		If the given string is not present, it returns ""
	 * @param {Object} n
	 */
	rightOf:	function(str){
					var pos = this.indexOf(str);
					return (pos < 0) | (pos + str.length >= this.length) ? "" : this.substring( pos+str.length );
				},
	/**
	 * String.leftOfLast
	 * Purpose:
	 * 		Returns all of the characters to the left of the last occurance of the given string.
	 * 		If the given string is not present, it returns ""
	 * @param {Object} n
	 */
	leftOfLast: function(str){
					var pos = this.lastIndexOf(str);
					return pos <= 0 ? "" : this.left(pos);
				},
	/**
	 * String.rightOfLast
	 * Purpose:
	 * 		Returns all of the characters to the left of the last occurance of the given string.
	 * 		If the given string is not present, it returns ""
	 * @param {Object} n
	 */
	rightOfLast:	function(str){
					var pos = this.lastIndexOf(str);
					return (pos < 0) | (pos + str.length >= this.length) ? "" : this.substring( pos + str.length );
				},
	/**
	 * oSubstitute
	 * Purpose :
	 * 		This function acts like the MooTools String.substitute.
	 * 		HOWEVER, it allows the processing of objects with sub objects.
	 * 		For example.
	 * var o={
     *	a: {aa:'abcd', ab : 'were'},
     * 	b: {d:123, e : 'asd123'},
     *	c : 'whisper'
	 * };
	 *
	 * var s = " a.aa = {a.aa}, a.ab= {a.ab}, b.d = {b.d}, b.e = {b.e}, c = {c} ";
	 * var t = " aa = {aa}, ab= {ab}, b.d = {b.d}, b.e = {b.e}, c = {c} ";
	 * s.oSubstitute(o);
	 * gives " a.aa = abcd, a.ab= were, b.d = 123, b.e = asd123, c = whisper "
	 * t.oSubstitute(o.a);
	 * gives " aa = abcd, ab= were, b.d = , b.e = , c = "
	 *
	 * @param {Object} object
	 * @param {Object} regexp
	 */
	oSubstitute: function(object, regexp){
		return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
			if (match.charAt(0) == '\\') return match.slice(1);
			var obj = object;
			if(name.indexOf('.')>-1){
				// deal with names like a.b.c in case the object is nested.
				name=name.split('.');
				name.every(function(seg){
					//if(obj){
						if(obj[seg]){
							obj = obj[seg];
							return true;
						} else {
							obj = '';
							return false;
						}
					//}
				});
				return (obj != undefined) ? obj : '';
			}
			return (object[name] != undefined) ? object[name] : '';
		});
	},
	basicNumber : function(){
	 return this.replace(/[$%,]/g, '').toFloat();
	}
});

function completeCloneObj(o) {
	return $extend({}, o);
 }

function cloneObj2( o ){
	// used by editTable, and for ome reason, works better for that, rather than completeCloneObj.
  return JSON.decode(  JSON.encode( o ) );
}


 Number.implement({
   /*
  Property: numberFormat

  Format a number with grouped thousands.


  Arguments:
  	  decimals, optional - integer, number of decimal percision; default, 2
   		dec_point, optional - string, decimal point notation; default, '.'
  		thousands_sep, optional - string, grouped thousands notation; default, ','

  Returns:
			a formatted version of number.

  Example:
  	>(36432.556).numberFormat() // returns 36,432.56
  	>(36432.556).numberFormat(2, '.', ',') // returns 36,432.56

  See Also:
  		http://snipplr.com/view/3516/mootools--numberformat/

  */

numberFormat : function(decimals, dec_point, thousands_sep) {
	decimals = Math.abs(decimals) + 1 ? decimals : 2;
  	var num = (isNaN(this) ? 0 : this).round( decimals );
  	dec_point = dec_point || '.';
  	thousands_sep = thousands_sep || ',';

  	var matches = /(-)?(\d+)(\.\d+)?/.exec((isNaN(num) ? 0 : num) + ''); // returns matches[1] as sign, matches[2] as numbers and matches[3] as decimals
  	var remainder = matches[2].length > 3 ? matches[2].length % 3 : 0;

  	return (matches[1] ? matches[1] : '') + (remainder ? matches[2].substr(0, remainder) + thousands_sep : '') + matches[2].substr(remainder).replace(/(\d{3})(?=\d)/g, "$1" + thousands_sep) +
  	(decimals ? dec_point + (+matches[3] || 0).toFixed(decimals).substr(2) : '');
  },
  inRange : function( limita, limitb){
    // first, makesure that limita is smaller than limitb
    if( limita > limitb){
     var swap = limitb;
     limitb = limita;
     limita = swap;
    }
    return (limita <= this) && (this <= limitb );
  }
});
/**
 * Sets a Cookie with the given name and value.
 *
 * name       Name of the cookie
 * value      Value of the cookie
 * [expires]  Expiration date of the cookie (default: end of current session)
 * [path]     Path where the cookie is valid (default: path of calling document)
 * [domain]   Domain where the cookie is valid
 *              (default: domain of calling document)
 * [secure]   Boolean value indicating if the cookie transmission requires a
 *              secure transmission
 */
function setCookie(name, value, expires, path, domain, secure)
{
    document.cookie= name + "=" + escape(value) +
        ((expires) ? "; expires=" + expires.toGMTString() : "") +
        ((path) ? "; path=" + path : "") +
        ((domain) ? "; domain=" + domain : "") +
        ((secure) ? "; secure" : "");
}

/**
 * Gets the value of the specified cookie.
 *
 * name  Name of the desired cookie.
 *
 * Returns a string containing value of specified cookie,
 *   or null if cookie does not exist.
 */
function getCookie(name)
{
    var dc = document.cookie;
    var prefix = name + "=";
    var begin = dc.indexOf("; " + prefix);
    if (begin == -1)
    {
        begin = dc.indexOf(prefix);
        if (begin != 0) return null;
    }
    else
    {
        begin += 2;
    }
    var end = document.cookie.indexOf(";", begin);
    if (end == -1)
    {
        end = dc.length;
    }
    return unescape(dc.substring(begin + prefix.length, end));
}

/**
 * Deletes the specified cookie.
 *
 * name      name of the cookie
 * [path]    path of the cookie (must be same as path used to create cookie)
 * [domain]  domain of the cookie (must be same as domain used to create cookie)
 */
function deleteCookie(name, path, domain)
{
    if (getCookie(name))
    {
        document.cookie = name + "=" + 
            ((path) ? "; path=" + path : "") +
            ((domain) ? "; domain=" + domain : "") +
            "; expires=Thu, 01-Jan-70 00:00:01 GMT";
    }
}
// ===================================================================
// Author: Matt Kruse <matt@mattkruse.com>
// WWW: http://www.mattkruse.com/
//
// NOTICE: You may use this code for any purpose, commercial or
// private, without any further permission from the author. You may
// remove this notice from your final code if you wish, however it is
// appreciated by the author if at least my web site address is kept.
//
// You may *NOT* re-distribute this code in any way except through its
// use. That means, you can include it in your product, or your web
// site, or any other form where the code is actually being used. You
// may not put the plain javascript up on your site for download or
// include it in your javascript libraries for download. 
// If you wish to share this code with others, please just point them
// to the URL instead.
// Please DO NOT link directly to my .js files from your site. Copy
// the files to your server and use them there. Thank you.
// ===================================================================

// HISTORY
// ------------------------------------------------------------------
// May 17, 2003: Fixed bug in parseDate() for dates <1970
// March 11, 2003: Added parseDate() function
// March 11, 2003: Added "NNN" formatting option. Doesn't match up
//                 perfectly with SimpleDateFormat formats, but 
//                 backwards-compatability was required.

// ------------------------------------------------------------------
// These functions use the same 'format' strings as the 
// java.text.SimpleDateFormat class, with minor exceptions.
// The format string consists of the following abbreviations:
// 
// Field        | Full Form          | Short Form
// -------------+--------------------+-----------------------
// Year         | yyyy (4 digits)    | yy (2 digits), y (2 or 4 digits)
// Month        | MMM (name or abbr.)| MM (2 digits), M (1 or 2 digits)
//              | NNN (abbr.)        |
// Day of Month | dd (2 digits)      | d (1 or 2 digits)
// Day of Week  | EE (name)          | E (abbr)
// Hour (1-12)  | hh (2 digits)      | h (1 or 2 digits)
// Hour (0-23)  | HH (2 digits)      | H (1 or 2 digits)
// Hour (0-11)  | KK (2 digits)      | K (1 or 2 digits)
// Hour (1-24)  | kk (2 digits)      | k (1 or 2 digits)
// Minute       | mm (2 digits)      | m (1 or 2 digits)
// Second       | ss (2 digits)      | s (1 or 2 digits)
// AM/PM        | a                  |
//
// NOTE THE DIFFERENCE BETWEEN MM and mm! Month=MM, not mm!
// Examples:
//  "MMM d, y" matches: January 01, 2000
//                      Dec 1, 1900
//                      Nov 20, 00
//  "M/d/yy"   matches: 01/20/00
//                      9/2/00
//  "MMM dd, yyyy hh:mm:ssa" matches: "January 01, 2000 12:30:45AM"
// ------------------------------------------------------------------

var MONTH_NAMES=new Array('January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
var DAY_NAMES=new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat');
function LZ(x) {return(x<0||x>9?"":"0")+x}

// ------------------------------------------------------------------
// isDate ( date_string, format_string )
// Returns true if date string matches format of format string and
// is a valid date. Else returns false.
// It is recommended that you trim whitespace around the value before
// passing it to this function, as whitespace is NOT ignored!
// ------------------------------------------------------------------
function isDate(val,format) {
	var date=getDateFromFormat(val,format);
	if (date==0) { return false; }
	return true;
	}

// -------------------------------------------------------------------
// compareDates(date1,date1format,date2,date2format)
//   Compare two date strings to see which is greater.
//   Returns:
//   1 if date1 is greater than date2
//	changed 16/2/2011 - ngd - return -1,0,1 instead of just 0,1
//   0 if they are the same
//	-1 if date2 is greater than date1
//  -1 if either of the dates is in an invalid format
// -------------------------------------------------------------------
function compareDates(date1,dateformat1,date2,dateformat2) {
	var d1=getDateFromFormat(date1,dateformat1);
	var d2=getDateFromFormat(date2,dateformat2);
	if (d1==0 || d2==0) {
		return -1;
		}
	else if (d1 > d2) {
		return 1;
		}
	else if (d1 < d2) {
		return -1;
		}
	return 0;
	}

// ------------------------------------------------------------------
// formatDate (date_object, format)
// Returns a date in the output format specified.
// The format string uses the same abbreviations as in getDateFromFormat()
// ------------------------------------------------------------------
function formatDate(date,format) {
	format=format+"";
	var result="";
	var i_format=0;
	var c="";
	var token="";
	var y=date.getYear()+"";
	var M=date.getMonth()+1;
	var d=date.getDate();
	var E=date.getDay();
	var H=date.getHours();
	var m=date.getMinutes();
	var s=date.getSeconds();
	var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
	// Convert real date parts into formatted versions
	var value=new Object();
	if (y.length < 4) {y=""+(y-0+1900);}
	value["y"]=""+y;
	value["yyyy"]=y;
	value["yy"]=y.substring(2,4);
	value["M"]=M;
	value["MM"]=LZ(M);
	value["MMM"]=MONTH_NAMES[M-1];
	value["NNN"]=MONTH_NAMES[M+11];
	value["d"]=d;
	value["dd"]=LZ(d);
	value["E"]=DAY_NAMES[E+7];
	value["EE"]=DAY_NAMES[E];
	value["H"]=H;
	value["HH"]=LZ(H);
	if (H==0){value["h"]=12;}
	else if (H>12){value["h"]=H-12;}
	else {value["h"]=H;}
	value["hh"]=LZ(value["h"]);
	if (H>11){value["K"]=H-12;} else {value["K"]=H;}
	value["k"]=H+1;
	value["KK"]=LZ(value["K"]);
	value["kk"]=LZ(value["k"]);
	if (H > 11) { value["a"]="PM"; }
	else { value["a"]="AM"; }
	value["m"]=m;
	value["mm"]=LZ(m);
	value["s"]=s;
	value["ss"]=LZ(s);
	while (i_format < format.length) {
		c=format.charAt(i_format);
		token="";
		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
			token += format.charAt(i_format++);
			}
		if (value[token] != null) { result=result + value[token]; }
		else { result=result + token; }
		}
	return result;
	}
	
// ------------------------------------------------------------------
// Utility functions for parsing in getDateFromFormat()
// ------------------------------------------------------------------
function _isInteger(val) {
	var digits="1234567890";
	for (var i=0; i < val.length; i++) {
		if (digits.indexOf(val.charAt(i))==-1) { return false; }
		}
	return true;
	}
function _getInt(str,i,minlength,maxlength) {
	for (var x=maxlength; x>=minlength; x--) {
		var token=str.substring(i,i+x);
		if (token.length < minlength) { return null; }
		if (_isInteger(token)) { return token; }
		}
	return null;
	}
	
// ------------------------------------------------------------------
// getDateFromFormat( date_string , format_string )
//
// This function takes a date string and a format string. It matches
// If the date string matches the format string, it returns the 
// getTime() of the date. If it does not match, it returns 0.
// ------------------------------------------------------------------
function getDateFromFormat(val,format) {
	val=val+"";
	format=format+"";
	var i_val=0;
	var i_format=0;
	var c="";
	var token="";
	var token2="";
	var x,y;
	var now=new Date();
	var year=now.getYear();
	var month=now.getMonth()+1;
	var date=1;
	var hh=now.getHours();
	var mm=now.getMinutes();
	var ss=now.getSeconds();
	var ampm="";
	
	while (i_format < format.length) {
		// Get next token from format string
		c=format.charAt(i_format);
		token="";
		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
			token += format.charAt(i_format++);
			}
		// Extract contents of value based on format token
		if (token=="yyyy" || token=="yy" || token=="y") {
			if (token=="yyyy") { x=4;y=4; }
			if (token=="yy")   { x=2;y=2; }
			if (token=="y")    { x=2;y=4; }
			year=_getInt(val,i_val,x,y);
			if (year==null) { return 0; }
			i_val += year.length;
			if (year.length==2) {
				if (year > 70) { year=1900+(year-0); }
				else { year=2000+(year-0); }
				}
			}
		else if (token=="MMM"||token=="NNN"){
			month=0;
			for (var i=0; i<MONTH_NAMES.length; i++) {
				var month_name=MONTH_NAMES[i];
				if (val.substring(i_val,i_val+month_name.length).toLowerCase()==month_name.toLowerCase()) {
					if (token=="MMM"||(token=="NNN"&&i>11)) {
						month=i+1;
						if (month>12) { month -= 12; }
						i_val += month_name.length;
						break;
						}
					}
				}
			if ((month < 1)||(month>12)){return 0;}
			}
		else if (token=="EE"||token=="E"){
			for (var i=0; i<DAY_NAMES.length; i++) {
				var day_name=DAY_NAMES[i];
				if (val.substring(i_val,i_val+day_name.length).toLowerCase()==day_name.toLowerCase()) {
					i_val += day_name.length;
					break;
					}
				}
			}
		else if (token=="MM"||token=="M") {
			month=_getInt(val,i_val,token.length,2);
			if(month==null||(month<1)||(month>12)){return 0;}
			i_val+=month.length;}
		else if (token=="dd"||token=="d") {
			date=_getInt(val,i_val,token.length,2);
			if(date==null||(date<1)||(date>31)){return 0;}
			i_val+=date.length;}
		else if (token=="hh"||token=="h") {
			hh=_getInt(val,i_val,token.length,2);
			if(hh==null||(hh<1)||(hh>12)){return 0;}
			i_val+=hh.length;}
		else if (token=="HH"||token=="H") {
			hh=_getInt(val,i_val,token.length,2);
			if(hh==null||(hh<0)||(hh>23)){return 0;}
			i_val+=hh.length;}
		else if (token=="KK"||token=="K") {
			hh=_getInt(val,i_val,token.length,2);
			if(hh==null||(hh<0)||(hh>11)){return 0;}
			i_val+=hh.length;}
		else if (token=="kk"||token=="k") {
			hh=_getInt(val,i_val,token.length,2);
			if(hh==null||(hh<1)||(hh>24)){return 0;}
			i_val+=hh.length;hh--;}
		else if (token=="mm"||token=="m") {
			mm=_getInt(val,i_val,token.length,2);
			if(mm==null||(mm<0)||(mm>59)){return 0;}
			i_val+=mm.length;}
		else if (token=="ss"||token=="s") {
			ss=_getInt(val,i_val,token.length,2);
			if(ss==null||(ss<0)||(ss>59)){return 0;}
			i_val+=ss.length;}
		else if (token=="a") {
			if (val.substring(i_val,i_val+2).toLowerCase()=="am") {ampm="AM";}
			else if (val.substring(i_val,i_val+2).toLowerCase()=="pm") {ampm="PM";}
			else {return 0;}
			i_val+=2;}
		else {
			if (val.substring(i_val,i_val+token.length)!=token) {return 0;}
			else {i_val+=token.length;}
			}
		}
	// If there are any trailing characters left in the value, it doesn't match
	if (i_val != val.length) { return 0; }
	// Is date valid for month?
	if (month==2) {
		// Check for leap year
		if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year
			if (date > 29){ return 0; }
			}
		else { if (date > 28) { return 0; } }
		}
	if ((month==4)||(month==6)||(month==9)||(month==11)) {
		if (date > 30) { return 0; }
		}
	// Correct hours value
	if (hh<12 && ampm=="PM") { hh=hh-0+12; }
	else if (hh>11 && ampm=="AM") { hh-=12; }
	var newdate=new Date(year,month-1,date,hh,mm,ss);
	return newdate.getTime();
	}

// ------------------------------------------------------------------
// parseDate( date_string [, prefer_euro_format] )
//
// This function takes a date string and tries to match it to a
// number of possible date formats to get the value. It will try to
// match against the following international formats, in this order:
// y-M-d   MMM d, y   MMM d,y   y-MMM-d   d-MMM-y  MMM d
// M/d/y   M-d-y      M.d.y     MMM-d     M/d      M-d
// d/M/y   d-M-y      d.M.y     d-MMM     d/M      d-M
// A second argument may be passed to instruct the method to search
// for formats like d/M/y (european format) before M/d/y (American).
// Returns a Date object or null if no patterns match.
// ------------------------------------------------------------------
function parseDate(val) {
	var preferEuro=(arguments.length==2)?arguments[1]:false;
	generalFormats=new Array('y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d');
	monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d');
	dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M');
	var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst');
	var d=null;
	for (var i=0; i<checkList.length; i++) {
		var l=window[checkList[i]];
		for (var j=0; j<l.length; j++) {
			d=getDateFromFormat(val,l[j]);
			if (d!=0) { return new Date(d); }
			}
		}
	return null;
	}
/* authentication cookie -> find out if we think we are logged in (although it may have expired) */
function isLoggedIn() {
	var cookieName = db.leftOf(".nsf");
	var slash = cookieName.lastIndexOf('/');
	if(!slash==-1) {
		cookieName = cookieName.substr(slash+1)
	}
	return Cookie.read(cookieName); // returns null if no cookie
}
