/* The classes in this file permit actions to be taken on events without the
 * event source object being aware of the workings of the receiving object, or
 * vice-versa, just like event handling in Java AWT/Swing.  Therefore, minimal
 * changes are necessary to the PHP rendering functions (e.g. classes/render.php)
 * in order to get any page element to "talk to" any other page element.  Form
 * validation is handled in a similar way.
 *
 * The file requires linkedlist.js to work.
 *
 * Example of usage:
 *
 * In this example, the receiving item, checkbox2 doesn't know that it reacts
 * to events and the event handler DoubleTickAction doesn't get involved with
 * the page construction process happening in the renderStuff() PHP function.
 *
 * Contents of the HTML template:
 *
 *	<script language="JavaScript">
 *		// Initialize the event system.
 *		var events=new Events();
 *
 *		//An event handler
 *		function DoubleTickAction() {
 *			DoubleTickAction.prototype.actionPerformed= function (event) {
 *				document.forms[0].checkbox2.checked=event.source.checked;
 *			}
 *		}
 *	</script>
 *	
 *	<?php renderStuff() ?>
 *	<input type="checkbox" name="checkbox2" />
 *	...
 *
 *	<script language="JavaScript">
 *		// Put this at the bottom of the HTML template. init() is called via the
 *		// onload event on the body tag.
 *		function init() {
 *			events.addListener("checkbox1","Action",new DoubleTickAction());
 *			<?php $eventsJS->doLoadEvents(); ?>
 *		}
 *	</script>
 *
 * A generic PHP component built elsewhere that renders some HTML elements:
 *	<?php
 *		require_once APP_WEBDIR . 'classes/Events.php';
 *
 *		function	renderStuff() {
 *			global $eventsJS;
 *			//Make a checkbox that can broadcast events.
 *			echo "<input type='checkbox' name='checkbox1' onclick=\"".$eventsJS->eventCode()."\" />";
 *			// This event will run after the page has fully loaded.
 * 			$eventsJS->postLoadEvent("new ActionObject(document.forms[0].checkbox1)");
 *		}
 *	?>
 *
 * Copyright Colours of Spain 2006
*/

function Events() {
	this.publishers=new Array()
	
	//Used by event sources to post an event.
	Events.prototype.postEvent= function(event) {
		var handlers=this.publishers[event.source];
		if (handlers!=null) {
			var subscribers=handlers[event.type];
			if (subscribers!=null) {
				var iter=subscribers.iterator();
				while (iter.hasNext()) {
					event.fire(iter.next());
				} // end while
			} //ei
		} // ei
	} // end postEvent method.

	//Used by objects interested in receiving events to register their interest.
	Events.prototype.addListener= function (source,type, listener) {
		if (this.publishers[source]==null) this.publishers[source]=new Array();
		if (this.publishers[source][type]==null) this.publishers[source][type]=new LinkedList();
		this.publishers[source][type].add(listener);
	}

}


//Base class for event objects.
function EventObject() {
	EventObject.prototype.init= function(type, source, owner) {
		this.type=type;
		this.source=source;
		this.owner=owner;
	}
		
	EventObject.prototype.fire= function (subscriber) {
		alert("Event.fire must be overriden for "+type+ " events!");
	}
}

//Base class for event data.
function Event() {
	Event.prototype.init= function(source) {
		this.source=source;	
	}
}

// Form validation
function validation(form) {
	if (form.bypassValidation!=true) {
		var i;
		for(i=0; i<form.elements.length; i++) {
			var control=form.elements[i];
			if (control.validate!=null) {
				var value=null;
				if (control.type=='radio') {
					var radios=form.elements[control.name];
					var j;
					for (j=0; j<radios.length; j++) {
						if (radios[j].checked) {
							value=radios[j].value;
							break;
						}
					}
				} else if (control.type=='select-one') {
					value=control.options[control.selectedIndex].value;
				} else {
					value=control.value;
				}
				if ((control.type=='radio' || control.disabled==false) && !control.validate(value)) {
					control.focus();
					return false;
				}
			}
		}
		if (form.validate!=null && !form.validate()) return false;
	} else {
		form.bypassValidation=false;
	}
	return true;
}


//Simple action event class.

//Data class
function ActionEvent(source) {
	this.init(source);
}
ActionEvent.prototype = new Event;

//Constructor
function ActionObject(owner,source) {
	if (source==null) {
		this.init("Action",owner.name,owner);
	} else {
		this.init("Action",source,owner);	
	}
}
ActionObject.prototype = new EventObject;

ActionObject.prototype.fire= function(subscriber) {
	var event=new ActionEvent(this.owner);
	subscriber.actionPerformed(event);
}

