/* LICENSE

This work is licensed under the creative commons Attribution-Noncommercial-Share Alike 3.0 Unported License

http://creativecommons.org/licenses/by-nc-sa/3.0/

Attribution-Noncommercial-Share Alike 3.0 Unported
You are free:
    * to Share — to copy, distribute and transmit the work
    * to Remix — to adapt the work

Under the following conditions:
    * Attribution. You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work).
    * Noncommercial. You may not use this work for commercial purposes.
    * Share Alike. If you alter, transform, or build upon this work, you may distribute the resulting work only under the same or similar license to this one.

    * For any reuse or distribution, you must make clear to others the license terms of this work. The best way to do this is with a link to this web page.
    * Any of the above conditions can be waived if you get permission from the copyright holder.
    * Nothing in this license impairs or restricts the author's moral rights.

Rikkert Koppes (author)
http://www.rikkertkoppes.com/thoughts/wf2/

THIS LIBRARY IS STILL UNDER DEVELOPMENT

*/

/* goals
- parse attributes and give an interpretation to them
- validate on form submission
- implement DOM properties
- implement validity attribute
- implement checkValidity() method
- implement parsing of event attributes (new Function(...))
- implement event firing (start with dom1 like)
- implement form attribute (copy to hidden fields in the form)
- implement form replace attribute
- implement :invalid pseudoclass as .wf2-invalid class
- implement :valid pseudoclass as .wf2-valid class
- implement :read-only pseudoclass as .wf2-read-only class
- implement :read-write pseudoclass as .wf2-read-write class
- implement reset() method for controls and fieldsets (NOT WF2)
- implement valueAsDate attribute for date related controls (returns a timestamp)
*/

/*
done:
- autofocus attribute
- required attribute
- pattern attribute
- validity property
- :invalid pseudoclass as .wf2-invalid class
- valid, required, optional, read-only, read-write pseudoclass similarly
- oninvalid attribute becomes oninvalid event handler
- widget for date related controls (date, week, month) - check scrollwheel which is stylable with -wf2-calendar -wf2-calendar-month -wf2-calendar-year -wf2-calendar-head and -wf2-calendar-foot classes
- list attribute and datalist element
- scrollwheel and up and down keys in number field
- oninput "event" for number controls (executes an oninput function if available)
- min and max properties for number field
- dynamic including of a default stylesheet (wf2.0.css)
- reset method on inputs
- reset method on forms
- checkValidity method on forms

- reset method on fieldsets (THIS IS NOT A WF2 FEATURE!)
- clearable attribute (THIS IS NOT A WF2 ATTRIBUTE!)

repetition model
- repeat="template" attribute (makes templates)
- repeat-start attribute
- repeat-min attribute (only on template initialisation)
- add button type
- template attribute on add button
- remove button type
- move-up button type
- move-down button type
- repetitionType constants (REPETITION_NONE, REPETITION_TEMPLATE, REPETITION_BLOCK) on every element
- repetitionTemplate property
- addRepetitionBlock() method on templates
- addRepetitionBlockByIndex() method on templates
- removeRepetitionBlock() method on blocks
- moveRepetitionBlock() method on blocks

could be better:
- oninvalid event should be a real event (see webpage about custom events)
- oninput event should be a real event (see webpage about custom events)
- determination of select within the datalist attribute is a bit odd
*/

//handles input elements
function wf2(processElement) {
  var i,el;
  var allArr = [];
  if (!processElement) {processElement = document;}
  var all = processElement.getElementsByTagName('*');
  for (i=0; i<all.length; i++) {allArr.push(all[i]);}
  for (i=0; (el=allArr[i]); i++) {
    if (wf2.repetition) {wf2.repetition(el);}
    
    if (el.nodeName.toLowerCase()==='form') {
      if (!el.checkValidity) {
  			el.checkValidity = function() {
          var e;
  				var res = true;
  				var els = this.elements;
  				for (var j=0; (e = els[j]); j++) {
  					wf2.checkWillValidate(e);
  					if (e.checkValidity && e.willValidate===true) {
  						res = res && e.checkValidity();
  					}
  				}
  				return res;
  			};
  		}
      //catch change event and dispatch onformchange event
      wf2.addEventListener(el,'change',function(){wf2.formChange(this);},true);
      //catch keyup event and dispatch onform input event
  		if (!el.reset) {
  			el.reset = function() {
          var inp;
  				var inps = this.getElementsByTagName('input');
  				for (var i=0; (inp=inps[i]); i++) {inp.reset();}
  			};
  		}
    }
    //fieldset reset method NOT IN WF2 SPEC
    if (el.nodeName.toLowerCase()==='fieldset') {
      el.reset = function() {
        var inp;
  			var inps = this.getElementsByTagName('input');
  			for (var j=0; (inp=inps[j]); j++) {inp.reset();}
  		};
    }
    
    if (el.nodeName.toLowerCase()==='input') {
        if (!el.checkValidity) {
        wf2.minmaxstep(el);   //handles min max and step attributes
        wf2.type(el);					//handles type attribute
  			wf2.clearable(el);				//handles clearable attribute (NO WF2)
  			wf2.autofocus(el);				//handles autofocus attribute
  			wf2.oninvalid(el);				//handles oninvalid attribute
  			wf2.readonly(el);				//handles readonly attribute
  			el.checkValidity = wf2.checkValidity;
  			//el.willValidate = true;		//should be adptive;
  			el.setCustomValidity = wf2.setCustomValidity;
  			el.checkValidity();
  			wf2.addEventListener(el,'change',function(){return this.checkValidity();},true);
  			wf2.addEventListener(el,'keyup',function(){return this.checkValidity();},true);
  			el.reset = function() {this.value = this.defaultValue; this.checkValidity();};
  		}
    }
    
    if (el.nodeName.toLowerCase()==='output') {
      if (!el.form) {   //check if it is implemented
        wf2.output(el);
      }
    }
  }

  //create Calendar widget
  if (wf2.calendar && !wf2.calendar.widget) {
    wf2.calendar.widget = wf2.calendar.createWidget();
  }
  if (wf2.clock && !wf2.clock.widget) {
    wf2.clock.widget = wf2.clock.createWidget();
  }
  
	//add wf2 stylesheet
	if (!document.getElementById('wf2-stylesheet')) {
		var l = document.createElement('link');
		l.setAttribute('rel','stylesheet');
		l.setAttribute('media','all');
		l.setAttribute('type','text/css');
		l.setAttribute('href',wf2.STYLESHEET);
		l.setAttribute('id','wf2-stylesheet');
		var h = document.getElementsByTagName('head')[0];
		h.insertBefore(l,h.firstChild);
	}
}
//validation messages
wf2.MSG_TYPEMISMATCH = 'type mismatch';
wf2.MSG_RANGEUNDERFLOW = 'value is too low';
wf2.MSG_RANGEOVERFLOW = 'value is too high';
wf2.MSG_STEPMISMATCH = 'value does not match step';
wf2.MSG_TOOLONG = 'value is too long';
wf2.MSG_PATTERNMISMATCH = 'value does not match pattern';
wf2.MSG_VALUEMISSING = 'value is required';
wf2.MSG_VALID = 'is valid';

wf2.STYLESHEET = 'wf2.0.css';
//wf2.STYLESHEET = 'wf2.0.css';
wf2.CLASS_INVALID = 'wf2-invalid';
wf2.CLASS_VALID = 'wf2-valid';
wf2.CLASS_REQUIRED = 'wf2-required';
wf2.CLASS_OPTIONAL = 'wf2-optional';
wf2.CLASS_READONLY = 'wf2-read-only';
wf2.CLASS_READWRITE = 'wf2-read-write';
wf2.CLASS_CLEARABLE = 'wf2-text-clearable';
wf2.CLASS_DATERELATED = 'wf2-daterelated';
wf2.CLASS_TIME = 'wf2-time';
wf2.CLASS_NUMBER = 'wf2-number';
wf2.CLASS_RANGE_HORIZONTAL = 'wf2-range-horizontal';
wf2.CLASS_RANGE_VERTICAL = 'wf2-range-vertical';
wf2.CLASS_OUTPUT = 'wf2-output';
wf2.CLASS_CALENDAR = 'wf2-calendar';
wf2.CLASS_CALENDAR_BUTTON = 'wf2-calendar-button';
wf2.CLASS_CALENDAR_HEAD = 'wf2-calendar-head';
wf2.CLASS_CALENDAR_MONTH = 'wf2-calendar-month';
wf2.CLASS_CALENDAR_YEAR = 'wf2-calendar-year';
wf2.CLASS_CALENDAR_BODY = 'wf2-calendar-body';
wf2.CLASS_CALENDAR_FOOT = 'wf2-calendar-foot';
wf2.CLASS_CLOCK = 'wf2-clock';
wf2.CLASS_CLOCK_HEAD = 'wf2-clock-head';
wf2.CLASS_CLOCK_FOOT = 'wf2-clock-foot';
wf2.TYPE_ATTRIBUTE = 'type';

//this method should be attached to all form controls
wf2.checkValidity = function() {
	var control = this;
	control.validity = {};
	function checkTypeMismatch(){
		if (control.value==='') {return false;}
		switch (wf2.getType(control)) {
			case 'number': return (control.value.match(/^-?\d+(\.\d+)?(e-?\d+)?$/)===null);
			case 'time': return (control.value.match(/^\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/)===null);
			case 'week': return (control.value.match(/^\d{4,}-W\d{2}$/)===null);
			case 'month': return (control.value.match(/^\d{4,}-\d{2}$/)===null);
			case 'date': return (control.value.match(/^\d{4,}-\d{2}-\d{2}$/)===null);
			case 'datetime-local': return (control.value.match(/^\d{4,}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/)===null);
			case 'datetime': return (control.value.match(/^\d{4,}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?Z$/)===null);
		}
	}
	function checkRangeUnderflow(){
		//control.min = control.getAttribute('min');
		//if (control.min=='') {control.min=null; return false};
    if (control.min===null) {return false;}
		if (control.value==='') {return false;}
		if (control.validity.typeMismatch) {return false;}
		//check type
		switch (wf2.getType(control)) {
			case 'number': return (parseFloat(control.value)<parseFloat(control.min));
			case 'date': return (new Date(control.value).getTime()>new Date(control.min).getTime());
			case 'time': return (new Date(control.value).getTime()>new Date(control.min).getTime());
			case 'datetime': return (new Date(control.value).getTime()>new Date(control.min).getTime());
			default: return false;
		}
	}
	function checkRangeOverflow(){
		//control.max = control.getAttribute('max');
		//if (control.max=='') {control.max=null; return false};
    if (control.max===null) {return false;}
		if (control.value==='') {return false;}
		if (control.validity.typeMismatch) {return false;}
		//check type
		switch (wf2.getType(control)) {
			case 'number': return (parseFloat(control.value)>parseFloat(control.max));
			case 'date': return (new Date(control.value).getTime()>new Date(control.max).getTime());
			case 'time': return (new Date(control.value).getTime()>new Date(control.max).getTime());
			case 'datetime': return (new Date(control.value).getTime()>new Date(control.max).getTime());
			default: return false;
		}
	}
	function checkStepMismatch(){
		//control.step = control.getAttribute('step');
		//if (control.step=='') {control.step=null; return false};
    if (control.step===null) {return false;}
	}
	function checkTooLong(){
		var ml = control.getAttribute('maxLength');
		if (!ml) {return;}
		return (control.value.length > ml);
	}
	function checkPatternMismatch(){
		var pattern = control.getAttribute('pattern');
		if (control.value==='') {return false;}
		if (!pattern) {return;}
		return (!control.value.match(new RegExp('^(?:'+pattern+')$','')));
	}
	function checkValueMissing(){
    var required = ((control.getAttribute('required')!==null && control.getAttribute('required')!==false)|| control.required===true);
    control.required = required;
		return (required && control.value==='');
	}
	function checkValid() {
		return(!(control.validity.typeMismatch ||
			control.validity.rangeUnderflow ||
			control.validity.rangeOverflow ||
			control.validity.stepMismatch ||
			control.validity.tooLong ||
			control.validity.patternMismatch ||
			control.validity.valueMissing ||
			control.validity.customError));
	}

	control.validationMessage = 'field "'+control.name+'": ';

	if (checkTypeMismatch()) {control.validity.typeMismatch = true; control.validationMessage+=wf2.MSG_TYPEMISMATCH+'; ';}
	if (checkRangeUnderflow()) {control.validity.rangeUnderflow = true; control.validationMessage+=wf2.MSG_RANGEUNDERFLOW+'; ';}
	if (checkRangeOverflow()) {control.validity.rangeOverflow = true; control.validationMessage+=wf2.MSG_RANGEOVERFLOW+'; ';}
	if (checkStepMismatch()) {control.validity.stepMismatch = true; control.validationMessage+=wf2.MSG_STEPMISMATCH+'; ';}
	if (checkTooLong()) {control.validity.tooLong = true; control.validationMessage+=wf2.MSG_TOOLONG+'; ';}
	if (checkPatternMismatch()) {control.validity.patternMismatch = true; control.validationMessage+=wf2.PATTERMISMATCH+'; ';}
	if (checkValueMissing()) {control.validity.valueMissing = true; control.validationMessage+=wf2.MSG_VALUEMISSING+'; ';}
	if (control.customValidationMessage) {control.validationMessage+=control.customValidationMessage+'; ';}
	
	//there is also a customError flag
	if (checkValid()) {control.validity.valid = true; control.validationMessage+=wf2.MSG_VALID+'; ';}
	control.validationMessage += '\n';
	
	if (!control.validity.valid && control.oninvalid && control.oninvalid()===false) {return;}
	
	wf2.asignClasses(control);
	//window.status = (control.validity.toSource());
	return (control.validity.valid)?true:false;
};

wf2.checkWillValidate = function(control) {
	//see http://www.whatwg.org/specs/web-forms/current-work/#willvalidate
	/*
		    *  The control is associated with a form (or several forms).
    * The control does not have a repetition template as an ancestor.
    * The control does not have a datalist element as an ancestor.
    * The control has a name.
    * The control is not disabled and not readonly.
    * The control is not of type hidden, button, reset, add, remove, move-up, or move-down.
    * The control is not an output element. 
	*/
	control.willValidate = (
		control.form &&
		control.parentNode.nodeName.toLowerCase!='datalist' &&
		control.name &&
		!control.disabled &&
		!control.readonly &&
		control.type!='hidden' &&
		control.type!='button' &&
		control.type!='reset' &&
		control.type!='add' &&
		control.type!='remove' &&
		control.type!='move-up' &&
		control.type!='move-down' &&
		control.nodeName.toLowerCase()!='output');
	return control.willValidate;
};

wf2.asignClasses = function(control) {
	(control.validity.valid!==true)?wf2.addClassName(control,wf2.CLASS_INVALID):wf2.removeClassName(control,wf2.CLASS_INVALID);
	(control.validity.valid===true)?wf2.addClassName(control,wf2.CLASS_VALID):wf2.removeClassName(control,wf2.CLASS_VALID);
	(control.required===true)?wf2.addClassName(control,wf2.CLASS_REQUIRED):wf2.removeClassName(control,wf2.CLASS_REQUIRED);
	(control.required===false)?wf2.addClassName(control,wf2.CLASS_OPTIONAL):wf2.removeClassName(control,wf2.CLASS_OPTIONAL);
};

wf2.setCustomValidity = function(customValidationMessage) {
	this.validity.customError = !!(customValidationMessage);
	this.customValidationMessage = customValidationMessage;
};

wf2.autofocus = function(control) {
	if (control.getAttribute('autofocus')!==null) {control.focus();}
};

wf2.oninvalid = function(control) {
	if (control.getAttribute('oninvalid')!==null) {control.oninvalid = new Function('',control.getAttribute('oninvalid'));}
};

wf2.readonly = function(control) {
	var c;
	if (control.readOnly) {
		c = wf2.CLASS_READONLY;
	} else {
		c = wf2.CLASS_READWRITE;
	}
	wf2.addClassName(control,c);
};

/* THIS IS NOT WF2 FUNCTIONALITY */
wf2.clearable = function(control) {
	if (control.getAttribute('clearable')===null) {return;}
	wf2.addClassName(control,wf2.CLASS_CLEARABLE);
	//control.style.backgroundRepeat = 'no-repeat';
	//control.style.backgroundPosition = 'right center';
	control.style.paddingRight = '20px';
	control.style.width = Math.max((control.clientWidth-40),0)+'px';
	wf2.addEventListener(control,'click',function(e) {
		if (!e) {e=event;}
		var onbutton = (e.offsetX)?((this.offsetWidth-event.offsetX) <20):(e.target == e.originalTarget);
		if (onbutton) {
			control.value = '';
			if (control.oninput) {control.oninput();}
			control.focus();
			control.checkValidity();
		}
	},true);
	wf2.addEventListener(control,'mousemove',function(e) {
		if (!e) {e=event;}
		var onbutton = (e.offsetX)?((this.offsetWidth-event.offsetX) <20):(e.target == e.originalTarget);
		this.style.cursor = (onbutton)?'default':'';
	},true);
};

wf2.minmaxstep = function(control) {
  control.min = control.getAttribute('min');
	if (!control.min) {control.min=null;}
  control.max = control.getAttribute('max');
	if (!control.max) {control.max=null;}
  control.step = control.getAttribute('step');
	if (!control.step) {control.step=null;}
};

wf2.formChange = function(form) {
  var i,el,ofc;
  if (!form) {return;}
  if (form.onformchange) {form.onformchange();}
  for (i=0; (el=form.elements[i]); i++) {
    if ((ofc = el.getAttribute('onformchange')) && (typeof ofc == 'string')) {el.onformchange = new Function('',ofc);}
    if (el.onformchange) {el.onformchange();}
  }
};
wf2.formInput = function(form) {
  var i,el,ofi;
  if (!form) {return;}
  if (form.onforminput) {form.onforminput();}
  for (i=0; (el=form.elements[i]); i++) {
    if ((ofi = el.getAttribute('onforminput')) && (typeof ofi == 'string')) {el.onforminput = new Function('',ofi);}
    if (el.onforminput) {el.onforminput();}
  }
};

wf2.output = function(el) {
  var inp = document.createElement('input');
  for (var i=0; i<el.attributes.length; i++) {
    inp.setAttribute(el.attributes[i].nodeName,el.attributes[i].nodeValue);
  }
  inp.readOnly = true;
  wf2.addClassName(inp,wf2.CLASS_OUTPUT);
  el.parentNode.replaceChild(inp,el);
};

//namespace for functionality related to type attribute
wf2.type = function(control) {
	var type = wf2.getType(control);
	//alert(type);
	//don't apply on implemented types, except for text and range (because safari's range control sucks; no vertical, no formchange etc)
	if (wf2.type[type] && (type!=control.type || type=='text' || type=='range' || type=='checkbox')) {wf2.type[type](control);}
};

//handles input elements with type=text
wf2.type.text = function(HtmlInputElement) {
	var i = HtmlInputElement;
	if ((i.getAttribute('list'))!=='' && i.getAttribute('list')!==null) {
		wf2.type.text.list(i);
	}
};

//handles input elements with type=text and list attribute
wf2.type.text.list = function(control,explicitList) {
	var lid,dl,i,o;
	if (!explicitList) {
		lid = control.getAttribute('list');
		dl = document.getElementById(lid);
		if (!(dl && (dl.nodeName.toLowerCase()=='select' || dl.nodeName.toLowerCase()=='datalist'))) {return;}
	} else {
		dl = explicitList;
	}
	
	var options = dl.getElementsByTagName('option');
	dl.style.display = 'none';
	
	var list = document.createElement('table');
	list.className = 'wf2-datalist';
	list.style.position = 'absolute';
  var tb = list.appendChild(document.createElement('tbody'));
	
	for (i=0; i<options.length; i++) {
		//o = list.appendChild(options[i].cloneNode(true));
		//if (o.getAttribute(';abel')!==null) {o.appendChild(document.createTextNode(o.getAttribute('label')));}
		var r = tb.appendChild(document.createElement('tr'));
		r.appendChild(document.createElement('td').appendChild(document.createTextNode(options[i].value)).parentNode).className = 'wf2-datalist-value';
		r.appendChild(document.createElement('td').appendChild(document.createTextNode(options[i].label)).parentNode).className = 'wf2-datalist-label';
		r.onclick = function() {
			control.value = this.firstChild.firstChild.nodeValue;
			hideList();
		}
	}
	
	function showList() {
		list.style.left = wf2.globalLeft(control)+'px';
		list.style.top = (wf2.globalTop(control)+control.offsetHeight)+'px';
		document.body.appendChild(list);
	}
	function hideList() {
		if (list.parentNode) {list.parentNode.removeChild(list);}
	}
	
	wf2.addEventListener(control,'click',function(e) {
		if (!e) {e=event;}
		showList();
		e.cancelBubble = true;
		return false;
	},false);
	wf2.addEventListener(list,'click',function() {
		control.value = this.options[this.selectedIndex].value;
		control.focus();
		window.setTimeout(hideList,1);
	},false);
	 wf2.addEventListener(document,'click',function() {
    hideList();
  },false);
};

wf2.parseFloat = function(str) {
	if (!str) return NaN;
	var res = str.match(/^(-?)(\d+)(\.(\d+))?(e((-?)(\d+)))?$/);
	if (res===null) return NaN;
	var data = {
		isNegative: (res[1]==='-'),
		integer: res[2],
		hasDecimals: (res[3]!==''),
		numberOfDecimals: (res[4])?res[4].length:0,
		decimals: res[4],
		hasExponent: (res[5]!==''),
		exponent: parseInt(res[6],10),
		exponentNegative: (res[7]==='-'),
		exponentInteger: res[8]||0,
		precision: (((res[4])?res[4].length:0) - (parseInt(res[6],10)||0))
	};
	data.valueOf = function(){return parseFloat(str);};
	data.toString = function(){return str;};
	return data;
};

//alert(wf2.parseFloat('-45.345e-56').precision);

wf2.type.number = function(control) {
  wf2.addClassName(control,wf2.CLASS_NUMBER);
	control.style.paddingRight = '20px';
  control.style.width = Math.max((control.clientWidth-40),0)+'px';
  
  control.stepUp = function(n) {
		var newValue,base,floor,ceil;
		if (n===0) {
			throw 'INDEX_SIZE_ERR';
			return;
		}
		var step = wf2.parseFloat(this.step)||1;
		var min = wf2.parseFloat(this.min)||null;
		var max = wf2.parseFloat(this.max)||null;
		var value = wf2.parseFloat(this.value)||0;
		//var precision = Math.max((step)?step.precision:0,(min)?min.precision:0,(max)?max.precision:0);
		//window.status = precision;
		n = Math.round(n);
		base = min||max||value;
		stepsFromBase = (value-base)/step;
		//round to nearest step
		var rnd = Math.round(stepsFromBase);
		/*
		floor = Math.floor(stepsFromBase);
		ceil = Math.ceil(stepsFromBase);
		window.status = base+' '+floor+' '+stepsFromBase+' '+ceil+' '+(stepsFromBase%1);
		if (floor==ceil) {
			floor--;
			ceil++;
		}
		window.status += ' '+floor+' '+ceil;
		*/
		//newValue = base + ((n>0)?ceil:floor)*step;
		//newValue += ((n>0)?(n-1):(n+1))*parseFloat(step);
		
		newValue = base + rnd*step;
		newValue += n*step;
		//newValue.toFixed(-precision);
		if ((max===null || newValue <= max) && (min===null || newValue >= min)) {
			this.value = newValue;//.toFixed(precision);
		} else {
			throw 'INVALID_MODIFICATION_ERR';
		}
  };
	
	/*
	control.stepUp = function(n) {
		//temporary, should be replaces by something better
		if (n===0) {
			throw 'INDEX_SIZE_ERR';
			return;
		}
		var step = wf2.parseFloat(this.step)||1;
		var min = wf2.parseFloat(this.min)||null;
		var max = wf2.parseFloat(this.max)||null;
		var value = wf2.parseFloat(this.value)||0;
		n = Math.round(n);
		if (isNaN(v)) {v=(this.min)?this.min:(this.max)?this.max:0;}
		if (this.min) {v = Math.max(this.min,v);}
		if (this.max) {v = Math.min(this.max,v);}
    //round to step
    v = 1*this.min + n*this.step*Math.round((v-this.min)/this.step);
		this.value = v;
	};
	*/
  control.stepDown = function(n) {
		this.stepUp(-n);
  };
	
  wf2.addEventListener(control,'mousewheel',function(e) {
		var dir;
		//this is somewhat odd in mac safari, (report by martin), might be due to mac os mousewheel acceleration :http://adomas.org/javascript-mouse-wheel/
		//this might cause e.detail to be 0 which causes devision by zero and unexpected results
		var d = (e && e.detail)?-e.detail:event.wheelDelta;
		dir = (d===0)?0:(d<0)?-1:1;
		/* was:
    if (e && e.detail) {
      dir = -e.detail/Math.abs(e.detail); 
    } else {
      dir = event.wheelDelta/Math.abs(event.wheelDelta); 
    }*/
		try {
			this.stepUp(dir);
			if (this.oninput) {this.oninput();}
			if (this.form) {wf2.formInput(this.form);}
			if (this.checkValidity) {this.checkValidity();}
		} catch(e) {}
    if (e) {
      e.preventDefault();
      e.cancelBubble = true;
    }
		return false;
  },false);
	wf2.addEventListener(control,'click',function(e) {
		//check which button is pressed
	},false);
	wf2.addEventListener(control,'mousemove',function(e) {
		if (!e) {e=event;}
		var onbutton = (e.offsetX)?((this.offsetWidth-event.offsetX) <20):(e.target == e.originalTarget);
		this.style.cursor = (onbutton)?'default':'';
	},false);
	control.onkeydown = function(e) {
		var key = (e)?e.which:event.keyCode;
		window.status = key;
		if (key==38 || key==40) {
			var dir = (key==38)?1:-1;
			try {
				this.stepUp(dir);
				if (this.form) {wf2.formInput(this.form);}
			} catch(e) {}
		}
		if (this.oninput) {this.oninput();}
		if (this.checkValidity) {this.checkValidity();}
	};
	/*
	control.onblur = function() {
		var v = parseFloat(this.value);
		if (isNaN(v)) {v=(this.min)?this.min:(this.max)?this.max:0;}
		if (this.min) {v = Math.max(this.min,v);}
		if (this.max) {v = Math.min(this.max,v);}
    //round to step
    v = 1*this.min + this.step*Math.round((v-this.min)/this.step);
		this.value = v;
		if (this.checkValidity) {this.checkValidity();}
    if (this.form) {wf2.formInput(this.form);}
	};
	*/
};

wf2.type.range = function(control) {
	var w,h,verticalMode;
	//alert('range');
	w = control.clientWidth;
	h = control.clientHeight;
	//force it to override standard safari sliders, they suck
	control.type = 'text';
	verticalMode = (h>w);
	(verticalMode)?wf2.addClassName(control,wf2.CLASS_RANGE_VERTICAL):wf2.addClassName(control,wf2.CLASS_RANGE_HORIZONTAL);
	control.style.width = w+'px';
	control.style.height = h+'px';
	control.style.fontSize = '0px';
  control.min = parseInt(control.min,10)||0;
  control.max = parseInt(control.max,10)||100;
  control.step = parseInt(control.step,10)||1;
  control.value = parseInt(control.value,10)||1;
  control.value = Math.min(Math.max(control.value,control.min),control.max);
	control.unselectable = true;
	control.style.MozUserSelect = 'none';
  
  function drag(target,slider,mouseX,mouseY) {
		//if (target!=control) {return;}
    var min = control.min;
    var max = control.max;
    var step = control.step;
    var w = slider.clientWidth;
    var h = slider.clientHeight;
    var verticalMode = (h>w);
    var size = (verticalMode)?h:w;
    var left = wf2.globalLeft(slider);
    var top = wf2.globalTop(slider);
		//var mouse = (verticalMode)?mouseY:mouseX;
    var mouse = (verticalMode)?mouseY-top:mouseX-left;
    var pointerPos = Math.min(Math.max(mouse,6),size-7);
    var relval = (max-min)*(pointerPos-6)/(size-13);
    //round to nearest step stop  
    var val = min+step*Math.round(relval/step);
    slider.style.backgroundPosition = (verticalMode)?'center '+(pointerPos-6)+'px':(pointerPos-6)+'px center';
		slider.valueAsNumber = val;
    slider.value = val;
    if (slider.form) {wf2.formInput(slider.form);}
  }
  
  control.onmousedown = function(e) {
    if (!e) {e=event;}
    if (!wf2.type.range.slideObj) {
      wf2.type.range.slideObj = this;
    }
  };
  wf2.addEventListener(document,'mouseup',function() {
    if (wf2.type.range.slideObj) {delete wf2.type.range.slideObj;}
  },false);
  wf2.addEventListener(document,'mousemove',function(e) {
    if (wf2.type.range.slideObj) {
			var target = (e)?e.target:event.srcElement;
      //var x = (e)?e.layerX:event.offsetX;
      //var y = (e)?e.layerY:event.offsetY;
      var d = document.documentElement, b = document.body;
      var x = (e)?e.pageX:(event.clientX + (d.scrollLeft || b.scrollLeft));
      var y = (e)?e.pageY:(event.clientY + (d.scrollTop || b.scrollTop));
			x -= (wf2.getStyleAsNumber(b,'marginLeft')+wf2.getStyleAsNumber(b,'borderLeftWidth'));
			y -= (wf2.getStyleAsNumber(b,'marginTop')+wf2.getStyleAsNumber(b,'borderTopWidth'));
      //take body margin into account
      drag(target,wf2.type.range.slideObj,x,y);
    }
  },false);
};

/*
wf2.type.checkbox = function(control) {
	wf2.addClassName(control,'wf2-checkbox');
	w = control.clientWidth;
	h = control.clientHeight;
	//force it to override standard
	//control.type = 'text';
	//control.setAttribute('type','text');
	control.unselectable = true;
	control.style.MozUserSelect = 'none';
	control.readonly = true;
	
	control.style.width = w+'px';
	control.style.height = h+'px';
	control.style.fontSize = '0px';
	if (control.checked) {wf2.addClassName(control,'wf2-checked');}
	
	wf2.addEventListener(control,'click',function() {
		this.checked = !this.checked;
		(this.checked)?this.setAttribute('checked','checked'):this.removeAttribute('checked');
		(this.checked)?wf2.addClassName(control,'wf2-checked'):wf2.removeClassName(control,'wf2-checked');
	},false);
}
*/

wf2.type.date = function(control) {
	if (wf2.type.dateRelated) {
    wf2.type.dateRelated(control,wf2.calendar.M_DATE);
  }
};

wf2.type.week = function(control) {
	if (wf2.type.dateRelated) {
    wf2.type.dateRelated(control,wf2.calendar.M_WEEK);
  }
};

wf2.type.month = function(control) {
	if (wf2.type.dateRelated) {
    wf2.type.dateRelated(control,wf2.calendar.M_MONTH);
  }
};



//helper functions
wf2.getType = function(control) {
//in Fx  original type can be retrieved by getNamedItem, in IE by getAttribute
	if (control.getAttribute(wf2.TYPE_ATTRIBUTE)) {
		return control.getAttribute(wf2.TYPE_ATTRIBUTE);
	}
	//return control.getAttribute(wf2.TYPE_ATTRIBUTE) || 'text';
	return 'text';
};

wf2.hasClassName = function(control, className) {
	return (control.className.length > 0 && (control.className == className || control.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))));
};

wf2.addClassName = function(control, className) {
	if (!control || !className || wf2.hasClassName(control, className)) {return;}
	control.className += ((control.className==='')?"" : " ") + className;
};

wf2.removeClassName = function(control, className) {
	control.className = control.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').replace(/^\s+/, '').replace(/\s+$/, '');
};

wf2.getStyle = function(element, property) {
	var ccProp = property.replace(/-([a-z])/g,function(match,char) {
		return char.toUpperCase();
	});
	var hyProp = property.replace(/([A-Z])/g,function(match,char) { 
    return "-" + char.toLowerCase(); 
  });
	if(document.defaultView && document.defaultView.getComputedStyle) {
		var value = document.defaultView.getComputedStyle(element,'').getPropertyValue(hyProp);
  } else if (element.currentStyle) {
		var value = element.currentStyle[ccProp];
	} else {
		var value = false;
	}
  return value;
}
wf2.getStyleAsNumber = function(element,property) {
	return parseInt(wf2.getStyle(element,property),10)||0;
}


wf2.addEventListener = function(obj,e,func,useCapture) {
	if (obj.addEventListener) {
		if (e=='mousewheel') {obj.addEventListener('DOMMouseScroll',func,useCapture);}
		obj.addEventListener(e,func,useCapture);
	} else if (obj.attachEvent) {
		obj.attachEvent('on'+e,function() {
      func.apply(event.srcElement);
    });
	}
};

wf2.globalLeft = function(el) {
  var x = 0;
  while(el.offsetParent) {
    x += el.offsetLeft;
    el = el.offsetParent;
  }
  return x;
};

wf2.globalTop = function(el) {
  var y = 0;
  while(el.offsetParent) {
    y += el.offsetTop;
    el = el.offsetParent;
  }
  return y;
};

//from http://www.atan.cn/article.asp?id=517 and http://dean.edwards.name/weblog/2006/06/again/
wf2.onDomLoaded = function(func) {
  function init() {
		var script;
    if (arguments.callee.done) return;
    arguments.callee.done = true;
    if (_timer) clearInterval(_timer);
    //do stuff
    func();
		//clean
		//delete com.netwerkvsp.onDomLoaded;
		if (script = document.getElementById("wf2_iedomloader")) {
			script.parentNode.removeChild(script);
		}
  };
  if (document.addEventListener) {
    document.addEventListener("DOMContentLoaded", init, false);
  }
  /*@cc_on @*/
  /*@if (@_win32)
    document.write('<script id="wf2_iedomloader" defer src="javascript:void(0)"><\/script>');
    var script = document.getElementById("wf2_iedomloader");
    script.onreadystatechange = function() {
      if (this.readyState == "complete") {
        init(); // call the onload handler
      }
    };
  /*@end @*/
  if (/WebKit/i.test(navigator.userAgent)) {
    var _timer = setInterval(function() {
      if (/loaded|complete/.test(document.readyState)) {
        init();
      }
    }, 10);
  }
  //window.onload = init;
};

/*
wf2.addEventListener(window,'load',function() {
	wf2();
},false);
*/

wf2.onDomLoaded(wf2);
/*
  This is part of the wf2 library and depends on it
*/

wf2.type.dateRelated = function(control,mode) {
	if (!mode) {mode = wf2.calendar.M_DATE;}
	wf2.addClassName(control,wf2.CLASS_DATERELATED);
	control.style.paddingRight = '20px';
	control.style.width = Math.max((control.clientWidth-40),0)+'px';
	
	wf2.addEventListener(control,'click',function(e) {
		if (this.readOnly) {return;}
		if (!e) {e=event;}
		var onbutton = (e.offsetX)?((this.offsetWidth-event.offsetX) <20):(e.target == e.explicitOriginalTarget && e.target==e.originalTarget);
		if (onbutton) {
      wf2.calendar.showWidget(control,mode);
      e.cancelBubble = true;
      return false;
		}
	},false);
	wf2.addEventListener(control,'mousemove',function(e) {
		if (!e) {e=event;}
		var onbutton = (e.offsetX)?((this.offsetWidth-event.offsetX) <20):(e.target == e.originalTarget);
		this.style.cursor = (onbutton)?'default':'';
	},false);
  wf2.addEventListener(control,'keyup',function(e) {
		if (!e) {e=event;}
		var date = wf2.date.parseDate(this.value);
		this.valueAsDate = (date instanceof Date)?date.getTime():date;
	},false);
  wf2.addEventListener(document,'click',function() {
    wf2.calendar.hideWidget();
  },false);
};

wf2.calendar = function() {

	var M_DATE = 0;
	var M_WEEK = 1;
	var M_MONTH = 2;
	var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
	var monthNames = ['January','February','March','April','May','June','July','August','September','October','November','December'];
	var dayNames = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];

	var createWidget = function() {
	  var cal = document.createElement('div');
	  cal.className = wf2.CLASS_CALENDAR;
		//cal.onclick = function(){wf2.calendar.hideWidget()}
	  cal.date = new Date();  //initialize on today;
		//header
		var d = cal.appendChild(document.createElement('div'));
	  cal.calendarHead = d;
		d.className = wf2.CLASS_CALENDAR_HEAD;
		var prevMonth = d.appendChild(document.createElement('button').appendChild(document.createTextNode('<')).parentNode);
		var monthName = d.appendChild(document.createElement('span').appendChild(document.createTextNode(' '+monthNames[cal.date.getMonth()]+' ')).parentNode);
		var nextMonth = d.appendChild(document.createElement('button').appendChild(document.createTextNode('>')).parentNode);
		var prevYear = d.appendChild(document.createElement('button').appendChild(document.createTextNode('<')).parentNode);
		var yearNumber = d.appendChild(document.createElement('span').appendChild(document.createTextNode(' '+cal.date.getFullYear()+' ')).parentNode);
		var nextYear = d.appendChild(document.createElement('button').appendChild(document.createTextNode('>')).parentNode);
		monthName.className = wf2.CLASS_CALENDAR_MONTH;
		yearNumber.className = wf2.CLASS_CALENDAR_YEAR;
		prevMonth.className = wf2.CLASS_CALENDAR_BUTTON;
		prevMonth.className = wf2.CLASS_CALENDAR_BUTTON;
		nextMonth.className = wf2.CLASS_CALENDAR_BUTTON;
		prevYear.className = wf2.CLASS_CALENDAR_BUTTON;
		nextYear.className = wf2.CLASS_CALENDAR_BUTTON;
		
	  prevMonth.onclick = function(e){setWidget(wf2.date.setPartOffset(cal.date,'Month',-1)); (e)?e.stopPropagation():event.cancelBubble = true; return false;};
	  nextMonth.onclick = function(e){setWidget(wf2.date.setPartOffset(cal.date,'Month',1)); (e)?e.stopPropagation():event.cancelBubble = true; return false;};
	  prevYear.onclick = function(e){setWidget(wf2.date.setPartOffset(cal.date,'Year',-1)); (e)?e.stopPropagation():event.cancelBubble = true; return false;};
	  nextYear.onclick = function(e){setWidget(wf2.date.setPartOffset(cal.date,'Year',1)); (e)?e.stopPropagation():event.cancelBubble = true; return false;};
		
		wf2.addEventListener(monthName,'mousewheel',function(e){
			var dir;
			if (e && e.detail) {
				dir = e.detail/Math.abs(e.detail); 
			} else {
				dir = -event.wheelDelta/Math.abs(event.wheelDelta); 
			}
			wf2.calendar.setWidget(wf2.date.setPartOffset(cal.date,'Month',dir));
			if (e) {
	      e.preventDefault();
	      e.cancelBubble = true;
	    }
			return false;
		},false);
		wf2.addEventListener(yearNumber,'mousewheel',function(e){
			var dir;
			if (e && e.detail) {
				dir = e.detail/Math.abs(e.detail); 
			} else {
				dir = -event.wheelDelta/Math.abs(event.wheelDelta); 
			}
			wf2.calendar.setWidget(wf2.date.setPartOffset(cal.date,'Year',dir));
			if (e) {
	      e.preventDefault();
	      e.cancelBubble = true;
	    }
			return false;
		},false);
		
		//if (mode == wf2.calendar.M_MONTH) d.onclick = function(){control.value = wf2.calendar.formatDate(date,mode); control.checkValidity(); cal.parentNode.style.display = 'none'}
		
		//calendar table 
	  var t = cal.appendChild(document.createElement('table'));
	  cal.calendarTable = t;
	  
	  wf2.addEventListener(t,'mousewheel',function(e){
			var dir;
	    if (e && e.detail) {
	      dir = e.detail/Math.abs(e.detail); 
	    } else {
	      dir = -event.wheelDelta/Math.abs(event.wheelDelta); 
	    }
	    wf2.calendar.setWidget(wf2.date.setPartOffset(cal.date,'Month',dir));
	    if (e) {
	      e.preventDefault();
	      e.cancelBubble = true;
	    }
	    return false;
	  },false);
	  
	  //table head
	  t.createTHead();
		var thr = t.tHead.insertRow(0);
		thr.appendChild(document.createElement('th').appendChild(document.createTextNode('Week')).parentNode);
		for (var i=0; i<7; i++) {thr.appendChild(document.createElement('th').appendChild(document.createTextNode(dayNames[i])).parentNode);}
	  
	  //t.onclick = function(){control.value = wf2.calendar.formatDate(date,mode); control.checkValidity()}
		
		//footer
		d = cal.appendChild(document.createElement('div'));
	  cal.calendarFoot = d;
		d.className = wf2.CLASS_CALENDAR_FOOT;
		var todayButton = d.appendChild(document.createElement('button').appendChild(document.createTextNode('Today')).parentNode);
		var noneButton = d.appendChild(document.createElement('button').appendChild(document.createTextNode('None')).parentNode);
		var cancelButton = d.appendChild(document.createElement('button').appendChild(document.createTextNode('Cancel')).parentNode);
		todayButton.className = wf2.CLASS_CALENDAR_BUTTON;
		noneButton.className = wf2.CLASS_CALENDAR_BUTTON;
		cancelButton.className = wf2.CLASS_CALENDAR_BUTTON;
		
		todayButton.onclick = function(){
	    var nd = new Date();
	    cal.currentControl.value = wf2.calendar.formatDate(nd,cal.mode);
	    cal.currentControl.valueAsDate = nd.getTime();
	    cal.currentControl.checkValidity(); 
	    wf2.calendar.hideWidget();
	  };
	  noneButton.onclick = function(){cal.currentControl.value = ''; wf2.calendar.hideWidget();};
	  cancelButton.onclick = function(){wf2.calendar.hideWidget();};
	  
		return cal;
	};

	var showWidget = function(control,mode) {
	  var w = wf2.calendar.widget;
	  w.style.position = 'absolute';
	  //w.style.left = control.offsetLeft+'px';
	  //w.style.top = (control.offsetTop + control.offsetHeight)+'px';
		var l = wf2.globalLeft(control);
		var t = (wf2.globalTop(control)+control.offsetHeight);
		w.style.left = l+'px';
		w.style.top = t+'px';
	  //w.style.left = wf2.globalLeft(control)+'px';
	  //w.style.top = (wf2.globalTop(control) + control.offsetHeight)+'px';
	  
	  //window.status = wf2.globalLeft(control) + ' '+wf2.globalTop(control);
	  
	  w.currentControl = control;
	  w.mode = mode;
	  //var date = (control.valueAsDate)?new Date(control.valueAsDate):new Date();
		var date = (wf2.date.parseDate(control.value))||new Date();
	  wf2.calendar.setWidget(date);
	  //control.parentNode.insertBefore(w,control.nextSibling);
	  //or maybe
	  //control.offsetParent.appendChild(w);
	  document.body.appendChild(w);
		w.style.left = (2*l)-wf2.globalLeft(w)+'px';
		w.style.top = (2*t)-wf2.globalTop(w)+'px';
	};

	var setWidget = function(date) {
		var nd;
	  var w = wf2.calendar.widget;
	  var mode = w.mode||M_DATE;
	  w.date = date;
	  //set header fields
	  var h = w.calendarHead;
	  var hspans = h.getElementsByTagName('span');
	  var monthName = hspans[0];
	  monthName.firstChild.nodeValue = ' '+monthNames[w.date.getMonth()]+' ';
	  var yearNumber = hspans[1];
	  yearNumber.firstChild.nodeValue = ' '+w.date.getFullYear()+' ';
	  //set calendar
	  var t = w.calendarTable;
	  while(t.lastChild!=t.firstChild) {t.removeChild(t.lastChild);}
	  
		//first day of month
		var mth1 = new Date(new Date(date).setDate(1));
		//first day of that week
		var monMth1 = wf2.date.setPartOffset(mth1,'Date',-((mth1.getDay()===0)?'6':mth1.getDay()-1));
		
	  //table body
	  var b = t.appendChild(document.createElement('tbody'));
	  for (var wk=0; wk<6; wk++) {
	    var r = b.insertRow(wk);
	    for (var i=0; i<7; i++) {
	      nd = wf2.date.setPartOffset(monMth1,'Date',7*wk+i);
	      if (i===0) {
	        r.appendChild(document.createElement('th').appendChild(document.createTextNode(wf2.date.getWeek(nd))).parentNode);
	      }
	      var c = r.appendChild(document.createElement('td').appendChild(document.createTextNode(nd.getDate())).parentNode);
				c.className = (nd.getMonth()==date.getMonth())?'wf2-current-month':'wf2-other-month';
	      if (mode==M_DATE) {
	        c.onmouseover = function(){this.style.backgroundColor = 'silver';};
	        c.onmouseout = function(){this.style.backgroundColor = '';};
					if (nd.getTime()==new Date(w.currentControl.valueAsDate).getTime()) {wf2.addClassName(c,'wf2-calendar-selected');}
	      }
	      c.date = nd;
	      c.onclick = function(){
	        var date = new Date(this.date); 
	        w.currentControl.value = formatDate(date,mode); 
	        w.currentControl.valueAsDate = date.getTime();
	        w.currentControl.checkValidity(); 
	        wf2.calendar.hideWidget();
	      };
	    }
	    if (mode==M_WEEK) {
	      r.onmouseover = function(){this.style.backgroundColor = 'silver';};
	      r.onmouseout = function(){this.style.backgroundColor = '';};
				if (wf2.date.getWeek(nd)==wf2.date.getWeek(new Date(w.currentControl.valueAsDate))) {wf2.addClassName(r,'wf2-calendar-selected');}
	    }
	  }
	  if (mode==M_MONTH) {
	    b.onmouseover = function(){
				var cs = this.getElementsByTagName('td'); 
				for (var i=0; (c=cs[i]); i++) {
					if (c.date.getMonth()==date.getMonth()) {
						c.style.backgroundColor = 'silver';
					}
				}
			};
	    b.onmouseout = function(){var cs = this.getElementsByTagName('td'); for (var i=0; (c=cs[i]); i++) {c.style.backgroundColor = '';}};
	  }
	};

	var hideWidget = function() {
	  var w = wf2.calendar.widget;
	  if (!w.parentNode) {return;}
	  w.parentNode.removeChild(w);
	};

	var formatDate = function(date,mode) {
		switch (mode) {
			case M_WEEK: return date.getFullYear()+'-W'+wf2.date.getWeek(date);
			case M_MONTH: return date.getFullYear()+'-'+wf2.date.getMonth(date);
			case M_DATE: return date.getFullYear()+'-'+wf2.date.getDate(date);
		}
	};
	
	return {
		M_WEEK: M_WEEK,
		M_MONTH: M_MONTH,
		M_DATE: M_DATE,
		createWidget: createWidget,
		showWidget: showWidget,
		setWidget: setWidget,
		hideWidget: hideWidget
	}
}();

//date related functions
wf2.date = function() {
	var getWeek = function(date) {
		//week 1 is the week of january 4th
		var jan4 = new Date(new Date(date).setMonth(0,4));
		//calculate the monday of week 1
		var monWk1 = setPartOffset(jan4,'Date',-1*((jan4.getDay()===0)?6:(jan4.getDay()-1)));
		//calculate weeks since monday of week 1 and add 1 to get week number
		var msecDiff = date.getTime() - monWk1.getTime();
		var dayDiff = Math.round((msecDiff)/(24*60*60*1000));	//round this to whole days (which it should be, but floating point precision could cause some errors)
		var wk = 1+Math.floor(dayDiff/7);
		return (wk<10)?'0'+wk:''+wk;
	};
	var getMonth = function(date) {
		var m = date.getMonth()+1;
		return (m<10)?'0'+m:''+m;
	};
	var getDay = function(date) {
		var d = date.getDate();
		return (d<10)?'0'+d:''+d;
	};
	var getDate = function(date) {
		return getMonth(date)+'-'+getDay(date);
	};
	var getHours = function(date) {
		var h = date.getHours();
		return (h<10)?'0'+h:''+h;
	}
	var getMinutes = function(date) {
		var m = date.getMinutes();
		return (m<10)?'0'+m:''+m;
	}
	var getSeconds = function(date) {
		var s = date.getSeconds();
		return (s<10)?'0'+s:''+s;
	}
	var getMilliseconds = function(date) {
		var m = String(date.getMilliseconds());
		while (m.length<3) {m = '0'+m;}
		return m;
	}
	var setPartOffset = function(date,part,offset) {
		var d = new Date(date);
		if (part=='Year') {part='FullYear';}
		d['set'+part](date['get'+part]()+offset);
		return d;
	};
	var parseDate = function(dateStr) {
		var res;
		if (res = dateStr.match(/^(\d{4,})-(\d{2})-(\d{2})$/)) {	//date widget
			return new Date(res[1],res[2]-1,res[3]);
		}
		if (res = dateStr.match(/^(\d{4,})-(\d{2})$/)) {				//month
			return new Date(res[1],res[2]-1,1);
		}
		if (res = dateStr.match(/^\d{4,}-W\d{2}$/)) {				//week
		}
		return null;
	};
	
	return {
		getWeek: getWeek,
		getMonth: getMonth,
		getDay: getDay,
		getDate: getDate,
		getHours: getHours,
		getMinutes: getMinutes,
		getSeconds: getSeconds,
		getMilliseconds: getMilliseconds,
		setPartOffset: setPartOffset,
		parseDate: parseDate
	}
}();
/*
  This is part of the wf2 library and depends on it
*/

wf2.repetition = function(el) {
  if (!document.body.addRepetitionBlock) {
    //RepetitionElement constants
    el.REPETITION_NONE = 0;
    el.REPETITION_TEMPLATE = 1;
    el.REPETITION_BLOCK = 2;
    //RepetitionElement vars
    el.repetitionType = el.REPETITION_NONE;
    //el.removeRepetitionBlock = wf2.repetition.removeRepetitionBlock;
    //el.moveRepetitionBlock = wf2.repetition.moveRepetitionBlock;
     
    //repetition templates
    if (el.getAttribute('repeat')==='template') {
      wf2.repetition.template(el);
    }
    if (!isNaN(parseInt(el.getAttribute('repeat'),10))) {
      wf2.repetition.block(el);
    }
    //add, remove, move-up, move-down buttons, the input element equivalent is not implemented
    if (el.nodeName.toLowerCase()==='button') {
      wf2.repetition.button(el);
    }
  }
};

wf2.repetition.button = function(control) {
  var type = wf2.getType(control);
  switch (type) {
    case 'add': wf2.repetition.add(control); break;
    case 'remove': wf2.repetition.remove(control); break;
    case 'move-up': wf2.repetition.moveUp(control); break;
    case 'move-down': wf2.repetition.moveDown(control); break;
  }
};

wf2.repetition.getAncestorOfType = function(src,type) {
  var current = src;
  while (current.parentNode) {
    current = current.parentNode;
    if (current.repetitionType===type) {
      return current;
    }
  }
  return null;
};

wf2.repetition.add = function(control) {
  //find out template
  var t,te;
  control.htmlTemplate = ((t=control.getAttribute('template'))!==null && (te=document.getElementById(t)) && (te.repetitionType===te.REPETITION_TEMPLATE))?te:null;
  wf2.addEventListener(control,'click',function(e) {
    if (this.htmlTemplate!==null) {
      this.htmlTemplate.addRepetitionBlock(null);
    } else {
      var repetitionBlock = wf2.repetition.getAncestorOfType(this,this.REPETITION_BLOCK);
      if (repetitionBlock!==null) {
        repetitionBlock.repetitionTemplate.addRepetitionBlock(repetitionBlock);
      }
    }
    if (e) {
      e.cancelBubble = true;
      e.preventDefault();
    }
    return false;
  },true);
};

wf2.repetition.remove = function(control) {
  wf2.addEventListener(control,'click',function(e) {
    var repetitionBlock = wf2.repetition.getAncestorOfType(this,this.REPETITION_BLOCK);
    if (repetitionBlock===null) {
      return;
    }
    repetitionBlock.removeRepetitionBlock();
    if (e) {
      e.cancelBubble = true;
      e.preventDefault();
    }
    return false;
  },true);
};

wf2.repetition.moveUp = function(control) {
  wf2.addEventListener(control,'click',function(e) {
    var repetitionBlock = wf2.repetition.getAncestorOfType(this,this.REPETITION_BLOCK);
    if (repetitionBlock===null) {
      return;
    }
    repetitionBlock.moveRepetitionBlock(-1);
    if (e) {
      e.cancelBubble = true;
      e.preventDefault();
    }
    return false;
  },true);
};

wf2.repetition.moveDown = function(control) {
  wf2.addEventListener(control,'click',function(e) {
    var repetitionBlock = wf2.repetition.getAncestorOfType(this,this.REPETITION_BLOCK);
    if (repetitionBlock===null) {
      return;
    }
    repetitionBlock.moveRepetitionBlock(1);
    if (e) {
      e.cancelBubble = true;
      e.preventDefault();
    }
    return false;    
  },true);
};

wf2.repetition.template = function(template) {
  template.style.display = 'none';
  template.repetitionType = template.REPETITION_TEMPLATE;
  template.repetitionIndex = 0;
  template.addRepetitionBlock = function(refNode) {
    return wf2.repetition.addRepetitionBlock.apply(this,arguments);
  };
  template.addRepetitionBlockByIndex = function(refNode,index) {
    return wf2.repetition.addRepetitionBlock.apply(this,arguments);
  };
  //initiation - see 3.6.4
  var repeatStart = parseInt(template.getAttribute('repeat-start'),10);
	var repeatMin = parseInt(template.getAttribute('repeat-min'),10);
  if (isNaN(repeatStart) || repeatStart<0) {repeatStart = 1;}
	if (isNaN(repeatMin) || repeatMin<0) {repeatMin = 0;}
	var initialBlocks = Math.max(repeatStart,repeatMin);
  for (var i=0; i<initialBlocks; i++) {
    //template.addRepetitionBlock(null);
		template.addRepetitionBlock(template.previousSibling);
  }
};

wf2.repetition.block = function(block) {
  var rt,rte;
  block.repetitionType = block.REPETITION_BLOCK;
  block.moveRepetitionBlock = wf2.repetition.moveRepetitionBlock;
  block.removeRepetitionBlock = wf2.repetition.removeRepetitionBlock;
  if ((rt = block.getAttribute('repeat-template'))!==null && (rte = document.getElementById(rt)) && rte.repetitionType===rte.REPETITION_TEMPLATE) {
    block.repetitionTemplate = rte;
  } else {
    //try to find the repetition block among the nextsiblings - still fails
    var current = block.nextSibling;
    while (current.nextSibling) {
      if (current.repetitionType===current.REPETITION_TEMPLATE) {
        block.repetitionTemplate = current;
        break;
      }
    }
  }
  block.repetitionIndex = parseInt(block.getAttribute('repeat'),10);
};

wf2.repetition.addRepetitionBlock = function(refNode,index) {
  var template,templateName,clone;

  // for the purposes of step 11
  function replaceAttributes(node,templateName,repetitionIndex) {
    var i,att,val;
    if (node.attributes) {
			//alert(node.nodeName + ' '+node.children.length +' '+node.childNodes.length);  
      for (i=0; i<node.attributes.length; i++) {
        att = node.attributes.item(i);
				if (att.specified!==false) {
					val = att.nodeValue;
					if (val && (typeof val === 'string')) {
		        if (val.charAt(0)==='\uFEFF') {  //check for \uFEFF character
		        //if (false) {
		          val = val.substr(1);
		        } else {
		          val = val.replace(new RegExp('[\\u005B\\u02D1]'+templateName+'[\\u005D\\u00B7]','g'),repetitionIndex);
		        }
						node.setAttribute(att.nodeName,val);
					}
				}
      }
    }
    for (i=0; i<node.childNodes.length; i++) {
      replaceAttributes(node.childNodes[i],templateName,repetitionIndex);
    }
  }

  template = this;
  //step 1
  if (!template.parentNode) {   //check whether the parentnode is an element
    return;
  }
  //step 2
  var current = template;
  var numberOfPrecedingRepetitionBlocks = 0;
  while (current.previousSibling) {
    current = current.previousSibling;
    if (current.repetitionType === current.REPETITION_BLOCK && current.repetitionTemplate === template) {
      if (current.repetitionIndex >= template.repetitionIndex) {
        template.repetitionIndex = current.repetitionIndex+1;
      }
      numberOfPrecedingRepetitionBlocks++;
    }
  }
  //step 3
  if (template.getAttribute('repeat-max')!==null) {
    if (parseInt(template.getAttribute('repeat-max'),10)<=numberOfPrecedingRepetitionBlocks) {
      return null;
    }
  }
  //step 4
  if (index && index > template.repetitionIndex) {
    template.repetitionIndex = index;
  }
  //step 5
  clone = template.cloneNode(true);
  clone.style.display = '';
  clone.REPETITION_NONE = 0;
  clone.REPETITION_TEMPLATE = 1;
  clone.REPETITION_BLOCK = 2;
  clone.repetitionType = clone.REPETITION_BLOCK;
	clone.repetitionTemplate = template;
  //step 6
  clone.repetitionIndex = (index)?index:template.repetitionIndex;
  //step 7
  clone.setAttribute('repeat',clone.repetitionIndex);
  //step 8
  clone.removeAttribute('repeat-min');
  clone.removeAttribute('repeat-max');
  clone.removeAttribute('repeat-start');
  //step 9
  if (clone.id) {
    templateName = clone.id;
  }
  //step 10
  if (templateName && !templateName.match(/[\u005B\u02D1\u005D\u00B7]/gi)) {
    //step 11
    replaceAttributes(clone,templateName,clone.repetitionIndex);
  }
  //step 12
  if (templateName) {
    clone.setAttribute('repeat-template',templateName);
  }
  //step 13
  clone.removeAttribute('id');
  //step 14
  if (refNode===null) {
    var target = template;
    while (target.previousSibling && target.previousSibling.repetitionType!==template.REPETITION_BLOCK) {
      target = target.previousSibling;
    }
    target.parentNode.insertBefore(clone,target); 
  } else {
  //step 15
    refNode.parentNode.insertBefore(clone,refNode.nextSibling);
  }
  //step 16
  template.repetitionIndex += 1;
  //step 17
  //fire added event
  //step 18
  wf2.repetition.block(clone);
  wf2(clone);
  return clone;
};

wf2.repetition.removeRepetitionBlock = function() {
  var repetitionBlock = this;
  if (repetitionBlock.repetitionType !== repetitionBlock.REPETITION_BLOCK) {
    return;
  }
  //step 1
  if (repetitionBlock.parentNode) {
    repetitionBlock.parentNode.removeChild(repetitionBlock);
  }
  //step 2
	//If the repetition block is not an orphan, a removed event with no namespace, which bubbles but is not cancelable and has no default action, must be fired on the element's repetition template, using the RepetitionEvent interface, with the repetition block's DOM node as the context information in the element attribute..
  //step 3
	//If the repetition block is not an orphan, then while the remaining number of repetition blocks associated with the original element's repetition template and with the same parent as the template is less than the template's repeat-min  attribute and less than its repeat-max attribute, the template's replication behaviour is invoked (specifically, its addRepetitionBlock() method is called).
};

wf2.repetition.moveRepetitionBlock = function(distance) {
  var ps, ns;
  var repetitionBlock = this;
  if (repetitionBlock.repetitionType !== repetitionBlock.REPETITION_BLOCK) {
    return;
  }
  //step 1
  if (distance===0 || repetitionBlock.parentNode===null) {
    return;
  }
  //step 2
  var target = repetitionBlock;
  //step 3
  if (distance<0) {
    while (distance!==0 && (ps=target.previousSibling) && ps.repetitionType!==ps.REPETITION_TEMPLATE) {
      target = ps;
      if (target.repetitionType===target.REPETITION_BLOCK) {distance++;}
    }
  } else {
  //step 4 - distance > 0
    while (distance!==0 && (ns=target.nextSibling) && ns.repetitionType!==ns.REPETITION_TEMPLATE) {
      target = ns;
      if (target.repetitionType===target.REPETITION_BLOCK) {distance--;}
    }
    target = target.nextSibling;
  }
  //step 5
  repetitionBlock.parentNode.insertBefore(repetitionBlock,target);
  //step 6
  //fire events
};
/*
	a scientific datatype that stores integerpart, decimal part and (base 10) exponent as strings
	hence always exact for base 10 representable numbers
*/

wf2.scientific = function(StringRepresentation) {
	if (!StringRepresentation) {
		throw 'at least one argument should be given';
		return NaN;
	}
	if (arguments.length==1) {
		var res = StringRepresentation.match(/^(-?)(\d+)(\.(\d+))?(e(([+-]?)(\d+)))?$/i);
		if (res===null) {
			throw 'not a valid number representation';
			return NaN;
		}
		this.isNegative = (res[1]==='-');
		this.sign = (this.isNegative)?-1:1;
		var integerPart = res[2];
		var fractionPart = res[4]||'';
		var exponentPart = res[6]||'';
		this.isExponentNegative = (res[7]==='-');
		//remove leading zeros
		while (integerPart.charAt(0)==='0' && integerPart.length>1) {
			integerPart = integerPart.substr(1);
		}
		this.significance = integerPart.length + fractionPart.length;
		this.precision = Number(exponentPart) - fractionPart.length;
		this.integer = res[1]+integerPart;
		this.fraction = fractionPart;
		this.exponent = exponentPart;
	} else {
	}
}

wf2.scientific.preserveSignificance = false;

/* suppose we have 12.3456e7
	toRepresentation(null,null,null) would yield toRepresentation(1,null,null)
	toRepresentation(1,null,null) would yield		1.23456e8			(this is the default scientific representation
	toRepresentation(null,0,null) would yield		123456e3
	toRepresentation(null,null,5) would yield		1234.56e5
	toRepresentation(null,null,9) would yield		0.123456e9		(leading zeros are added)
	toRepresentation(null,null,1) would yield		12345600e1		(trailing zeros are added - this modifies apparent significance)
	toRepresentation(null,null,0) would yield		123456000			(trailing zeros are added, exponent is omitted, since it is 0 - this modifies apparent significance)

	toRepresentation(2,3,null) would yield			12.346e7			(decimals are rounded)
	toRepresentation(2,6,null) would yield			12.345600e7 	(trailing zeros are added - this modifies apparent significance)
	toRepresentation(7,2,null) would yield			1234560.00e2	(trailing zeros are added - this modifies apparent significance)
	toRepresentation(1,0,null) would yield			1e8						(fraction is omitted)
	
	toRepresentation(3,null,3) would yield			123456e3			(desired number of integers is overruled - more integer digits are needed for this exponent)
	toRepresentation(3,null,7) would yield			012.3456e7		(leading zeros are added)
	
	toRepresentation(null,3,3) would yield			123456.000e3	(trailing zeros are added)
	toRepresentation(null,3,7) would yield			12.346e7			(decimals are rounded)
	toRepresentation(null,0,0) would yield			123456000			(will round to integer)
	
	toRepresentation(3,2,3) would yield					123456.00e3		(desired number of integers is overruled, trailing zeros are added)
	toRepresentation(3,2,7) would yield					012.35e7			(leading zeros are added)
	
	there are in fact 3 operation possible
	- dot shifting, which affects all three components
	- addition of leading zeros, which is sometimes needed for dot shifting or to fit number of integers
	- addition of trailing zeros, which is sometimes needed for dot shifting or to fit number of integers or decimals
*/

wf2.scientific.prototype.toRepresentation = function(numberOfIntegers,numberOfDecimals,desiredExponent) {
	var i,f,e,str;
	/* represents the number as a string with 
		[numberOfIntegers] number of digits before the decimal point
		[numberOfDecimals] number of digits after the decimal point
		[desiredExponent] as the exponent
		
		zero or more of the arguments may be null, which means "don't care"
		if all arguments are null, standard scientific representation is given, i.e. numberOfIntegers is assumed 1
	*/
	var self = this;
	
	function dotShift(numberOfPlaces) {
		var i,f,e,l,r;
		i = self.integer;
		f = self.fraction;
		e = self.exponent;
		if (numberOfPlaces>0) {
			while (numberOfPlaces--) {		//shift to right
				if (f.length>0) {
					i += f.charAt(0);
					f = f.substr(1);
				} else {
					i += '0';
				}
				e = String(Number(e)-1);
			}
		} else {
			while (numberOfPlaces++) {		//shift to left
				f = i.charAt(i.length-1) + f;
				i = i.substr(0,i.length-1);
				if (i==='' || i==='-') i+='0';
				e = String(Number(e)+1);
			}
		}
		return [i,f,e];
	}
	
	i = this.integer;
	f = this.fraction;
	e = this.exponent;
	if (numberOfIntegers===null && numberOfDecimals===null && desiredExponent===null) {numberOfIntegers=1;}
	//set the exponent to the desired value
	if (desiredExponent!==null) {
		r = dotShift(parseInt(e,10) - desiredExponent);
		i = r[0]; f = r[1]; e = r[2];
	}
	if (numberOfIntegers!==null) {
		//shift digits from integer to fraction ore vice versa if exponent not fixed
		l = i.length-((this.isNegative)?1:0);
		if (desiredExponent===null) {
			r = dotShift(numberOfIntegers - l);
			i = r[0]; f = r[1]; e = r[2];
		}
		//add leading zeros if required
		l = i.length-((this.isNegative)?1:0);
		if (numberOfIntegers>l) {
			d = numberOfIntegers-l;
			while (d--) i = '0'+i;
		}
	}
	if (numberOfDecimals!==null) {
		//dotshift
		if (desiredExponent===null && numberOfIntegers===null) {
			r = dotShift(f.length - numberOfDecimals);
			i = r[0]; f = r[1]; e = r[2];
		}
		//round number of decimals or add trailing zeros
		d = numberOfDecimals-f.length;
		if (d>0) {
			while (d--) f += '0';
		} else if (d<0) {
			var last = 1*f[numberOfDecimals-1] + Math.round(f[numberOfDecimals]/10);
			if (numberOfDecimals==0) last = '';
			f = f.substr(0,numberOfDecimals-1)+last;
		}
	}
	str = i;
	str += (f==='')?'':'.'+f;
	str += (e==='0')?'':'e'+e;
	return new wf2.scientific(str);
}
wf2.scientific.prototype.toScientific = function() {
	return this.toRepresentation(1,null,null);
}
wf2.scientific.prototype.toExponential = function(fractionDigits) {
	return this.toRepresentation(1,fractionDigits||null,null);
}
wf2.scientific.prototype.toNormal = function(fractionDigits) {
	return this.toRepresentation(null,fractionDigits||null,0);
}
wf2.scientific.prototype.toFixed = function(fractionDigits) {
	return this.toRepresentation(null,fractionDigits||null,0);
}
wf2.scientific.prototype.toString = function() {
	var str = this.integer;
	str += (this.fraction==='')?'':'.'+this.fraction;
	str += (this.exponent==='0')?'':'e'+this.exponent;
	str += ' ['+this.significance+']';
	return str;
}
/*
wf2.scientific.prototype.valueOf = function() {
	return parseFloat(this.toString());
}*/
wf2.scientific.prototype.add = function(that) {
	//perform this + that
	var p = Math.min(this.precision,that.precision);
	var a = this.toRepresentation(null,null,p);
	var b = that.toRepresentation(null,null,p);
	if (this.isNegative) {a=a.substr(1);}
	if (that.isNegative) {b=b.substr(1);}
	a = a.split(/e/i)[0];
	b = b.split(/e/i)[0];
	var l = Math.max(a.length,b.length)+1;	//reserve space for carry
	var d;
	d=l-a.length; while (d--) {a = '0'+a;}	//pad a with zeros
	d=l-b.length; while (d--) {b = '0'+b;}	//pad b with zeros
	var carry = 0;
	var r;
	var result = '';
	while (l--) {
		r = this.sign*Number(a.charAt(l))+that.sign*Number(b.charAt(l))+carry;
		carry = Math.floor(r/10);
		r = r-(carry*10);
		result = r+result;
	}
	if (carry===0) {
		return new wf2.scientific(result+'e'+p);
	} else {
		throw 'unresolved problem with negative carry (subtraction)';
	}
	document.writeln(Math.floor(-3/10)+'<br>');
	document.writeln(a+' + <br>'+b+' = <br>'+result+' '+carry+'<br>');
}
wf2.scientific.prototype.negate = function() {
	//perform -1 * this
	return (this.isNegative)?new wf2.scientific(this.toString().substr(1)):new wf2.scientific('-'+this.toString());
}
wf2.scientific.prototype.multiplyBy = function(that) {
	//perform this * that
}
//aliases to the above
wf2.scientific.prototype.plus = function(that) {
	return this.add(that);
}
wf2.scientific.prototype.min = function(that) {
	return this.add(that.negate());
}
wf2.scientific.prototype.times = function(that) {
	return this.multiplyBy(that);
}


//unbounded long integer
//unduly large integer
//u'll love it
wf2.scientific.uli = function(str) {
	if (typeof str === 'number') {
		str = String(str);
	}
	if (!str.match(/^(-?)(\d+)$/)) {
		throw 'not an integer';
		return NaN;
	}
	
	/*
		integer maximum is 53 bits (double mantissa), which is         9.007.199.254.740.992
		therefore, I cut uli's into blocks of maximum 15 digits:         999.999.999.999.999
		(maybe make this 7, so that block multiplication never overflows)
		
		this will give you a capacity of 10^(15*2^53) which is in fact quite a lot (135 biliard digits, I won't write that out),
		somewhere around 10^(135e15)
		
		http://en.wikipedia.org/wiki/Long_and_short_scales for scale naming
	*/
	var blockLength = 15;
	
	function strPad(str) {
		while(str.length<blockLength) {
			str = '0'+str;
		}
		return str;
	}

	function build(str) {
		var res = str.match(/^(-?)(\d+)$/);
		var a = new Array();
		var abs = res[2];
		a.isNegative = (res[1]==='-');
		a.sign = (a.isNegative)?-1:1;
		var l = abs.length%blockLength;
		var i=0;
		while (abs.length>0) {
			a[i] = Number(abs.substr(0,l));
			abs = abs.substr(l);
			i++; l=blockLength;
		}
		if (a[0]===0 && a.length>1) a.shift();
		a[0] = a.sign*a[0];
		
		a.toString = function() {
			var s = String(this[0]);
			if (this.length>1) for (var i=1; i<this.length; i++) {
				s += strPad(String(this[i]));
			}
			return s;
		};
		a.add = function(uli,more) {
			if (typeof uli === 'number') {
				uli = build(String(uli));
			}
			var swapped = false;
			var IHaveTheLargestAbsoluteValue = (this.abs().gte(uli.abs()));
			var a = (IHaveTheLargestAbsoluteValue)?this.copy():uli.copy();
			var b = (IHaveTheLargestAbsoluteValue)?uli.copy():this.copy();
			if (a.isNegative) {
				a = a.negate();
				b = b.negate();
				swapped = true;
			}
			var signB = b.sign;
			a = a.abs(); b = b.abs();
			a.reverse(); b.reverse();
			var l = Math.max(a.length,b.length);
			var carry = 0;
			var result = [];
			var block = Math.pow(10,blockLength);
			var r;
			for (var i=0; i<l; i++) {
				r = a[i] + signB*(b[i]||0) + carry;
				carry = Math.floor(r/block);
				r = r-(carry*block);
				result[i] = r;
			}
			if (carry!==0) result.push(carry);
			result = build(this.toString.apply(result.reverse()));
			if (swapped) {
				result = result.negate();
			}
			var args = Array.prototype.slice.call(arguments);
			if (args.length>1) {		//more to add
				args.shift();
				result = result.add.apply(result,args);
			}
			return result;
		};
		a.copy = function() {
			return build(this.toString());
		};
		a.negate = function() {
			var a = this.copy();
			a.isNegative = !a.isNegative;
			a.sign = -a.sign;
			a[0] = -a[0];
			return a;
		};
		a.abs = function() {
			var a = this.copy();
			a.isNegative = false;
			a.sign = 1;
			a[0] = Math.abs(a[0]);
			return a;
		};
		a.pos = function() {return this.abs();};
		a.neg = function() {return this.abs().negate();};
		a.equals = function(uli) {
			return (this.toString()===uli.toString());
		};
		a.greaterThan = function(uli) {
			if (!!(this.isNegative^uli.isNegative)) {
				return uli.isNegative;
			} else {
				if (this.length==uli.length) {
					return (this[0]>uli[0]);
				} else {
					return (this.length>uli.length);
				}
			}
		};
		a.lessThan = function(uli) {
			if (!!(this.isNegative^uli.isNegative)) {
				return this.isNegative;
			} else {
				if (this.length==uli.length) {
					return (this[0]<uli[0]);
				} else {
					return (this.length<uli.length);
				}
			}
		};
		a.greaterThanOrEqual = function(uli) {return !this.lessThan(uli);};
		a.lessThanOrEqual = function(uli) {return !this.greaterThan(uli);};
		a.lt = function(uli) {return this.lessThan(uli);};
		a.gt = function(uli) {return this.greaterThan(uli);};
		a.lte = function(uli) {return this.lessThanOrEqual(uli);};
		a.gte = function(uli) {return this.greaterThanOrEqual(uli);};
		a.plus = function(uli) {return this.add(uli);};
		a.minus = function(uli) {
			if (typeof uli === 'number') {
				uli = build(String(uli));
			}
			return this.add(uli.negate());
		};
		a.inc = function() {return this.add(1);};
		a.dec = function() {return this.add(-1);};
		a.zero = function() {return build('0');};
		a.multiplyBy = function(uli) {
			//multiply last block by last block, store overflow
			//multiply second last block by last block, add overflow, store overflow
			//work to the first block, store the result
			//start the next result with an empty block
			//multiply last block by second to last block, store overflow
			//multiply second last block by second to last block, add overflow, store overflow
			//work to the first block, store, etc
		}
			
		return a;
	}
	return build(str);
}

var uli = wf2.scientific.uli;
/*
  This is part of the wf2 library and depends on it
*/

wf2.type.time = function(control) {
	wf2.addClassName(control,wf2.CLASS_TIME);
	control.style.paddingRight = '20px';
  control.style.width = Math.max((control.clientWidth-40),0)+'px';
	
	control.step = parseFloat(control.step,10)||60;
	
	wf2.addEventListener(control,'click',function(e) {
		if (this.readOnly) {return;}
		if (!e) {e=event;}
		var onbutton = (e.offsetX)?((this.offsetWidth-event.offsetX) <20):(e.target == e.explicitOriginalTarget && e.target==e.originalTarget);
		if (onbutton) {
      wf2.clock.showWidget(control,3);
      e.cancelBubble = true;
      return false;
		}
	},false);
	wf2.addEventListener(control,'mousemove',function(e) {
		if (!e) {e=event;}
		var onbutton = (e.offsetX)?((this.offsetWidth-event.offsetX) <20):(e.target == e.originalTarget);
		this.style.cursor = (onbutton)?'default':'';
	},false);
  
  wf2.addEventListener(document,'click',function() {
    wf2.clock.hideWidget();
  },false);
};

wf2.clock = function() {

	var createWidget = function() {
		var clock = document.createElement('div');
		clock.className = wf2.CLASS_CLOCK;
		clock.time = new Date();
		clock.time.setFullYear(1970,0,1);
		//head
		var d = clock.appendChild(document.createElement('div'));
	  clock.clockHead = d;
		d.className = wf2.CLASS_CLOCK_HEAD;
		//fields
		clock.hrs = d.appendChild(document.createElement('span'));
		clock.hrs.appendChild(document.createTextNode(wf2.date.getHours(clock.time)));
		//clock.hrs.style.width="4em";
		clock.hrs.maxLength=2;
		//clock.hrs.name = 'hrs';
		//wf2.type.number(clock.hrs);
		//clock.hrs.oninput = updateFunc;
		clock.mts = d.appendChild(document.createElement('span'));
		clock.mts.appendChild(document.createTextNode(':'+wf2.date.getMinutes(clock.time)));
		//clock.mts.style.width="4em";
		clock.mts.maxLength=2;
		//wf2.type.number(clock.mts);
		//clock.mts.oninput = updateFunc;
		clock.scs = d.appendChild(document.createElement('span'));
		clock.scs.appendChild(document.createTextNode(':'+wf2.date.getSeconds(clock.time)));
		//clock.scs.style.width="4em";
		clock.scs.maxLength=2;
		//wf2.type.number(clock.scs);
		//clock.scs.oninput = updateFunc;
		clock.mss = d.appendChild(document.createElement('span'));
		clock.mss.appendChild(document.createTextNode('.'+wf2.date.getMilliseconds(clock.time)));
		//clock.mss.style.width="5em";
		clock.mss.maxLength=4;
		//wf2.type.number(clock.tds);
		//clock.tds.oninput = updateFunc;
		
		try {
			clock.hands = clock.appendChild(createSVGHands(clock.time));
		} catch(e) {
			clock.hands = clock.appendChild(createIEHands(clock.time));
		}
		
		//footer
		d = clock.appendChild(document.createElement('div'));
	  clock.clockFoot = d;
		d.className = wf2.CLASS_CLOCK_FOOT;
		var nowButton = d.appendChild(document.createElement('button').appendChild(document.createTextNode('Now')).parentNode);
		var noneButton = d.appendChild(document.createElement('button').appendChild(document.createTextNode('None')).parentNode);
		var cancelButton = d.appendChild(document.createElement('button').appendChild(document.createTextNode('Cancel')).parentNode);
		nowButton.className = wf2.CLASS_CLOCK_BUTTON;
		noneButton.className = wf2.CLASS_CLOCK_BUTTON;
		cancelButton.className = wf2.CLASS_CLOCK_BUTTON;
		
		nowButton.onclick = function(){
	    var nd = new Date();
	    //cal.currentControl.value = wf2.calendar.formatDate(nd,cal.mode);
	    //cal.currentControl.valueAsDate = nd.getTime();
	    //cal.currentControl.checkValidity(); 
	    wf2.clock.hideWidget();
	  };
	  noneButton.onclick = function(){clock.currentControl.value = ''; wf2.clock.hideWidget();};
	  cancelButton.onclick = function(){wf2.clock.hideWidget();};
		
		return clock;
	};

	var showWidget = function(control,mode) {
	  var w = wf2.clock.widget;
	  w.style.position = 'absolute';
	  //w.style.left = control.offsetLeft+'px';
	  //w.style.top = (control.offsetTop + control.offsetHeight)+'px';
		w.style.left = wf2.globalLeft(control)+'px';
		w.style.top = (wf2.globalTop(control)+control.offsetHeight)+'px';
	  //w.style.left = wf2.globalLeft(control)+'px';
	  //w.style.top = (wf2.globalTop(control) + control.offsetHeight)+'px';
	  
	  //window.status = wf2.globalLeft(control) + ' '+wf2.globalTop(control);
	  
	  w.currentControl = control;
		w.mode = mode;
	  var time = (control.valueAsDate)?new Date(control.valueAsDate):new Date();
	  wf2.clock.setWidget(time);
		window.setInterval(function(){wf2.clock.setWidget(new Date());},100);
	  //control.parentNode.insertBefore(w,control.nextSibling);
	  //or maybe
	  //control.offsetParent.appendChild(w);
	  document.body.appendChild(w);
	};

	var setWidget = function(time) {
		var w = wf2.clock.widget;
		var mode = w.mode||2;		//default to minutes
		w.hands.setTime(time);
		w.hrs.firstChild.nodeValue = wf2.date.getHours(time);
		w.mts.firstChild.nodeValue = ':'+wf2.date.getMinutes(time);
		w.scs.firstChild.nodeValue = ':'+wf2.date.getSeconds(time);
		w.mss.firstChild.nodeValue = '.'+wf2.date.getMilliseconds(time);
		w.hrs.style.display = (mode > 0)?'':'none';
		w.mts.style.display = (mode > 1)?'':'none';
		w.scs.style.display = (mode > 2)?'':'none';
		w.mss.style.display = (mode > 4)?'':'none';
		//clock.hrs.style = (w.mode > 0)?:'':'none';
		//clock.mts.style = (w.mode > 1)?:'':'none';
		//clock.scs.style = (w.mode > 2)?:'':'none';
	}

	var hideWidget = function() {
	  var w = wf2.clock.widget;
	  if (!w.parentNode) {return;}
	  w.parentNode.removeChild(w);
	};

	var createIEHands = function(time) {
		var d = document.createElement('div');
		d.style.width = '150px';
		d.style.height = '150px';
		d.style.position = 'relative';
		d.className = 'wf2-dial';
		var hours = d.appendChild(document.createElement('div'));
		hours.style.width = '30px';
		hours.style.position = 'absolute';
		hours.className = 'wf2-clock-hours';
		hours.style.left = '75px';
		hours.style.top = '75px';
		hours.style.height = '1px';
		var minutes = d.appendChild(document.createElement('div'));
		minutes.style.width = '40px';
		minutes.style.position = 'absolute';
		minutes.className = 'wf2-clock-minutes';
		minutes.style.left = '75px';
		minutes.style.top = '75px';
		minutes.style.height = '1px';
		var seconds = d.appendChild(document.createElement('div'));
		seconds.style.width = '40px';
		seconds.style.position = 'absolute';
		seconds.className = 'wf2-clock-seconds';
		seconds.style.left = '75px';
		seconds.style.top = '75px';
		seconds.style.height = '1px';
		
		d.setTime = function(time) {
			var hrs = time.getHours();
			var mts = time.getMinutes();
			var scs = time.getSeconds();
			
			var ha = 2*Math.PI*((hrs + (mts/60)) % 12)/12;
			var ma = 2*Math.PI*(mts)/60;
			var sa = 2*Math.PI*(scs)/60;

			var cha = Math.cos(ha);
			var sha = Math.sin(ha);
			hours.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11="+sha+", M12="+(cha)+", M21="+(-cha)+", M22="+sha+",sizingMethod='auto expand')";
			hours.style.left = (75+Math.min(30*sha,0))+'px';
			hours.style.top = (75+Math.min(-30*cha,0))+'px';
			var cma = Math.cos(ma);
			var sma = Math.sin(ma);
			minutes.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11="+sma+", M12="+(cma)+", M21="+(-cma)+", M22="+sma+",sizingMethod='auto expand')";
			minutes.style.left = (75+Math.min(40*sma,0))+'px';
			minutes.style.top = (75+Math.min(-40*cma,0))+'px';
			var csa = Math.cos(sa);
			var ssa = Math.sin(sa);
			seconds.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11="+ssa+", M12="+(csa)+", M21="+(-csa)+", M22="+ssa+",sizingMethod='auto expand')";
			seconds.style.left = (75+Math.min(40*ssa,0))+'px';
			seconds.style.top = (75+Math.min(-40*csa,0))+'px';
		};
		d.setTime(time);
		return d;
	};
	var createSVGHands = function(time) {
		var svgns = 'http://www.w3.org/2000/svg';
		var d = document.createElement('div');
		d.style.width = '150px';
		d.style.height = '150px';
		d.style.position = 'relative';
		d.className = 'wf2-dial';
		var svg = d.appendChild(document.createElementNS(svgns,'svg'));
		svg.setAttribute('width',150);
		svg.setAttribute('height',150);
		var g = svg.appendChild(document.createElementNS(svgns,'g'));
		g.setAttribute('transform','translate(75,75)');
		var hours = g.appendChild(document.createElementNS(svgns,'path'));
		hours.setAttribute('d','M0,5 L0,-30');
		hours.setAttribute('class','wf2-clock-hours');
		var minutes = g.appendChild(document.createElementNS(svgns,'path'));
		minutes.setAttribute('d','M0,5 L0,-40');
		minutes.setAttribute('class','wf2-clock-minutes');
		var seconds = g.appendChild(document.createElementNS(svgns,'path'));
		seconds.setAttribute('d','M0,5 L0,-40');
		seconds.setAttribute('class','wf2-clock-seconds');
		
		d.setTime = function(time) {
			var hrs = time.getHours();
			var mts = time.getMinutes();
			var scs = time.getSeconds();
			
			var ha = 30*((hrs + (mts/60)) % 12);
			var ma = 360*(mts)/60;
			var sa = 360*(scs)/60;
			
			hours.setAttribute('transform','rotate('+ha+')');
			minutes.setAttribute('transform','rotate('+ma+')');
			seconds.setAttribute('transform','rotate('+sa+')');
		};
		d.setTime(time);
		return d;
	};

	return {
		createWidget: createWidget,
		showWidget: showWidget,
		setWidget: setWidget,
		hideWidget: hideWidget
	}
}();
