///////////////////////////////////////////////////////////////////////////
// This file uses JSDoc-friendly comments [ http://jsdoc.sourceforge.net/ ]
// (JSDoc tutorial in book: "Foundations of AJAX", Chap 5)
// TO BUILD DOCS: If ActivePerl and HTML::Templates are installed,
// and JSDoc is installed at c:\JSDoc-1.9.8.1\jsdoc.pl
// then execute buildDoc.bat and view jsdoc\index.html
///////////////////////////////////////////////////////////////////////////

/**
 * @file         mvc.js
 * @fileoverview Collection of model-view-controller support classes.
 * This library supports rich-internet-application code in the browser
 * using the Model-View-Controller design pattern (supported by Observer,
 * Command, Bean, Composite, etc. design patterns).
 * @author       Bruce Wallace  (PolyGlotInc.com)
 * @requires     utils.js
 * @version      1.0
 */


/**
 * @class the Collection "interface" has no code, only
 * an API followed by convention (ie <a target="_blank"
 * href="http://en.wikipedia.org/wiki/Duck_typing">DUCK-TYPING</a>)
 *<pre>
 *  The required "interface" for "collections":
 *  (1) getCount() - returns number of elements in collection
 *  (2) getItem( itemKey ) - return specified element
 *  (3) iterate( function(itemKey,itemObject) ) - calls
 *      specified function on each element in collection;
 *      If the function returns a true then the iteration
 *      stops right there instead of continuing thru rest
 *      of the elements in the collection
 *</pre>
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function Collection(){/* this function exists only for JsDoc purposes.*/}


Class(Map);
/**
 * @class This class encapsulates a Map of object/key pairs
 * and implements the {@link Collection} virtual interface
 * @extends OObject
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function Map()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName )
	{
		// init instance variables
		this.name = optName || "unnamed Map";
		this.map  = new Object();
		this.N    = 0;
	}

	/** return how many items are in map @type int */
	this.getCount   = function(   ){ return this.N; }
	
	/** return item as string associated with given key @type String */
	this.getItemStr = function( k ){ return this.map[k].toString(); }
	
	/** return item associated with given key @type Object */
	this.getItem    = function( k ){ return this.map[k]; }
	
	/** delete item associated with given key */
	this.delItem    = function( k ){ delete this.map[k];    --this.N;     }
	
	/** add given object and associate with given key */
	this.addItem    = function(k,o){ this.map[k] = o;       ++this.N;     }
	
	/** reset map to empty */
	this.reset      = function(   ){ this.map = new Object(); this.N = 0; }

	/** iterate thru items in map calling specified function
	 * @param {Function} f function that takes key and object as params
	 * and returns true if the iteration should be stopped before all
	 * items in map are processed.
	 */
	this.iterate = function( f )
	{ 
		var i = 0;
		for (k in this.map)
		  if ( f( k, this.getItem(k), i++ ) )
		    return; //early
	}

	/** Return the key that comes after the given key.
	 * If no key is specified, return the first key.
	 * If no key matches the specs above, return null.
	 */
	this.nextKey = function( k )
	{
		var nextKey = null;
		this.iterate(
			function(K,o,i){
				if (!k) return nextKey = K;/*TRICKY!*/
   				if (k==K) k=null;
   			}
		);
		return nextKey;
	}

	/** Return the object that comes AFTER the given key
	 * or, if no key specified, return the first object.
	 * If no object fits the specs above, return null.
	 */
	this.next = function( k )
	{
		var    n = this.nextKey(k);
		return n ? this.getItem(n) : null;
	}

	/** return the first object in this map or null if empty @type Object */
	this.first = function(){ return this.next(); }

	/** return the first key in this map or null if empty @type Object */
	this.firstKey = function(){ return this.nextKey(); }
}


Class(Context);
/**
 * @class This class encapsulates the criteria for building
 * a {@link View} such that if it changes, the view needs
 * rebuilding. This class is meant to be subclassed, however,
 * the default implementation implements a single-value context
 * where the value must be comparable via the "=" operator.
 * @extends OObject
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function Context()
{
	/** @param {anyComparableType} optContext optional value to save as view context */
	this.konstructor = function( optContext )
	{
		// init instance variables
		this.context = optContext;
	}

	/**
	 * @param {Context} lastBuildContext context to compare to THIS
	 * @return whether the current context is the same as the given one
	 * @type boolean
	 */
	this.sameAs = function( lastBuildContext )
	{
		if (lastBuildContext==null) return false;
		return this.context == lastBuildContext.context;
	}
	
	/** return THIS formatted as string @type String */
	this.toString = function(){ return "{"+this.context+"}"; }
}

// ------------------------
// --- OBSERVER PATTERN ---
// ------------------------

/**
 * @class the Observable "interface" has no code, only
 * an API followed by convention (ie <a target="_blank"
 * href="http://en.wikipedia.org/wiki/Duck_typing">DUCK-TYPING</a>)
 *<pre>
 *  The required "interface" for "observables":
 *  (1) addObserver( observer ) - add given {@link Observer} to your list
 *</pre>
 * @see Model
 */
function Observable(){/* this function exists only for JsDoc purposes.*/}


Class(Observer);
/**
 * @class This class acts as the abstract base class for each
 * "observer class" (ala Observer design pattern).
 *<pre>
 * Subclasses of Observer should define/override:
 *   (1) the {@link #update} method which accepts update events
 *       and takes one parameter which is the "observable"
 *       plus one optional adhoc parameter.
 *</pre>
 * @extends OObject
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function Observer()
{
	this.konstructor = function()
	{
		// init instance variables
		this.model = null;	//what do I watch (mostly, but not exclusively)
	}

	/**
	 * override this method with your logic to respond to an update
	 * event from one of the Observables you are subscribed to.
	 * @memberx Observer
	 * @param {Observable} observable the generator of this update event
	 * @param {Object} optAdhocObj optional adhoc object passed by sender
	 */
	this.update = function( observable, optAdhocObj ){ return; /*override me*/ }

	/**
	 * subscribe to (aka watch/monitor/observe) the given observable
	 * @memberx Observer
	 * @param {Observable} observable object to monitor
	 */
	this.subscribe = function( observable ) {
		this.model = observable;
		observable.addObserver( this );
	}
}

// -----------------------
// --- COMMAND PATTERN ---
// -----------------------

Class(Command);
/**
 * @class This class acts as the abstract base class for each
 * Command (ala <a target="_blank" 
 * href="http://en.wikipedia.org/wiki/Command_pattern">
 * Command design pattern</a>).
 *<pre>
 * Subclasses of Command should define/override:
 *  (1) the constructor to load the do/undo/redo context data
 *      which should set the "valid" attribute to a negative
 *      number if the command cant properly be initiated. 
 *  (2) the {@link #doit} method which executes the command
 *  (3) the {@link #undo} method which "rolls back" the command
 *  (4) the {@link #redo} method which "un-rolls-back" the command
 *  (5) canUndo property if this cmd is only meant for mutex
 *
 * FYI, REDO would be different than DO, for example, in the case
 * that DO had to do a database search to get a value, but REDO
 * could simply use that saved value without re-searching for it.
 *</pre>
 * @extends OObject
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function Command()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName )
	{
		// init static member variables
		if (!Command.NextID) Command.NextID = 1;

		// init instance variables
		if (optName) this.name = optName;
		this.state   = 0;	//-1=invalid,0=notDone,1=done,2=undone,3=redone
		this.canUndo = true;
		this.id      = Command.NextID++
	}

	/** "Do command" logic */
	this.doit      = function(){ alert("DOIT:"+this); /*override me*/ }
	
	/** "command UNDO" logic */
	this.undo      = function(){ alert("UNDO:"+this); /*override me*/ }
	
	/** "command REDO" logic. Default is to just call {@link #doit} */
	this.redo      = function(){ this.doit();         /*override me*/ }
	
	/** return details of this command for user viewing @type String */
	this.details   = function(){ return "";           /*override me*/ }

	/** return THIS formatted as string @type String */
	this.toString  = function(){ return this.name + ": " + this.details(); }

	/** return true iff this command should not even be started @type boolean */
	this.isInvalid = function(){ return this.state<0; }

	///////////////// "synchronized" API //////////////////

	/** synchronized this.DOIT() */
	this.syncDoIt = function(){ (new Mutex(this,"DOIT")); }
	
	/** synchronized this.UNDO() */
	this.syncUnDo = function(){ (new Mutex(this,"UNDO")); }
	
	/** synchronized this.REDO() */
	this.syncReDo = function(){ (new Mutex(this,"REDO")); }

	//////////////// "unsynchronized" API /////////////////

	/** "DO" this command if in the proper state. @throw error if in wrong state */
	this.DOIT = function()
	{
		if (this.state!=0) throw "Cant DO an invalid or already started command.";
		gRootView.block();
		this.doit();
		this.state = 1;
		gRootView.unblock();
	}

	/** "REDO" this command if in the proper state. @throw error if in wrong state */
	this.REDO = function()
	{
		if (this.state!=2) throw "Cant REDO a command that isnt undone.";
		gRootView.block();
		this.redo();
		this.state = 3;
		gRootView.unblock();
	}

	/** "UNDO" this command if in the proper state. @throw error if in wrong state */
	this.UNDO = function()
	{
		if (!this.canUndo) throw "UNDO not supported for this command.";
		switch( this.state )
		{
			case 1:
			case 3:
					gRootView.block();
					this.undo();
					this.state = 2;
					gRootView.unblock();
					break;
			default:
				throw "Cant UNDO a command that isnt done.";
		}
	}
}


Class(Mutex,["command object","method name"]);
/**
 * @class This class encapsulates a Map of mutual exclusion data;
 * It self-registers instantiations into a static Map;
 * This class implements the <a target="_blank"
 * href="http://www.polyglotinc.com/Mutex/">
 * Wallace variation of Lamport's bakery algorithm</a> for mutual exclusion;
 * It is used to execute Command objects while making sure
 * that no other Command objects (that are using Mutex)
 * are executed at the same time.<p>
 * NOTE: our main use for this is to keep background AJAX
 * processing from confusing foreground UI processing,
 * which can otherwise occur because both are making data
 * model changes simultaneously.
 * @extends OObject
 * @see #konstructor
 * @see GLOBALS#MUTEX_CPU_SLICE
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function Mutex()
{
	/** @param {Command} cmdObj Command object to be wrapped in Mutex
	 *  @param {String} methodName name of method being run on cmdObj
	 *  @param {boolean} optInhibitInvoke optional flag that if true
	 * will inhibit immediate launching of cmdObj by this constructor
	 */ 
	this.konstructor = function( cmdObj, methodName, optInhibitInvoke )
	{
		// init static member variables
		if (!Mutex.Map) Mutex.Map = new Map("global mutex map");

		// init instance variables
		this.cmd      = cmdObj;
		this.id       = cmdObj.id;
		this.name     = "Mutex for " + this.id;
		this.choosing = false;
		this.number   = 0;
		this.methodID = methodName;

		// auto-register "this"
		Mutex.Map.addItem( this.id, this );

		// auto start processing unless inhibited
		optInhibitInvoke || this.invoke();
	}

	/** launch the processing of "this" mutex/command object */
	this.invoke = function()
	{
		this.choosing = true;
		this.number   = timestamp();
		this.choosing = false;
		MUTEX_CPU_SLICE( this.id );
	}

	/** continue the processing of "this" mutex/command object
	 * @param {int} optStartID optional ID of last command we were waiting on;
	 * if not specified then start at top of list of all pending mutex/commands.
	 * @see GLOBALS#MUTEX_CPU_SLICE
	 */
	this.cpuSlice = function( optStartID )
	{
		var startID = optStartID ? optStartID : Mutex.Map.firstKey();
		for (var j=Mutex.Map.getItem(startID); j; j=Mutex.Map.next(j.id))
		{
		    if (
		    	// delay if thread j still receiving its #
		        j.choosing
			    // delay if threads with smaller numbers (or with same #,
			    // but with higher priority) still finishing their work
			    || (j.number
			    && (j.number <  this.number ||
			       (j.number == this.number && j.id < this.id) ) )
		    ){
				BusyDo( "MUTEX_CPU_SLICE", '('+ this.id +','+ j.id +')', 10 );
				return;//run away to fight another day (or millisecond)
			}
		}

		//by this point, we have exclusive access, so...

			// BEGIN CRITICAL SECTION...
			this.cmd[ this.methodID ]();
			//...END CRITICAL SECTION

		//end exclusive access
		this.number = 0;

		//since we are using cmd IDs instead of static thread numbers
		//(as is used in original bakery algorithm), we delete this
		//mutex to free memory.
		Mutex.Map.delItem( this.id );
	}
}

/** static routine to give a slice of CPU to mutex with given ID
 * @param {int} cmdID ID of command to resume
 * @param {int} optStartID optional ID of command on which we are waiting;
 * if not specified then start at top of list of all pending commands.
 * @see Mutex 
 */
function MUTEX_CPU_SLICE( cmdID, optStartID )
{
//	Break("slice id="+cmdID+" start="+optStartID);
	Mutex.Map.getItem(cmdID).cpuSlice(optStartID);
}

////////////////////////////////////////////
///////////////// MODELS ///////////////////
////////////////////////////////////////////

Class(Model).Extends(Observer);
/**
 * @class This abstract class acts as the base class for each
 * MVC data model; Since models can subscribe to other models,
 * they can act as both {@link Observer} and {@link Observable}.
 * This class implements the {@link Observable} interface.
 * @extends Observer
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function Model()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName )
	{
		this.Observer();	//super()

		// init instance variables
		if (optName) this.name = optName;
		this.hasChanged  = false; //only since last notifyObservers
		this.everChanged = false; //since last "neverChanged()"
		this.subscribers = new Array();
		this.autoCommit  = true;
		this.updateStamp();
	}

	/** debug method generating alert with subscriber list */
	this.dumpSubscribers = function()
	{
		var N = this.subscribers.length;
		var s = this.name + " observed by["+N+"]: ";
		for (var i=0; i<N; ++i)
		  s += (this.subscribers[i].name + "; ");
		Break(s);
	}

	/** return "this" formatted as string @type String */
	this.toString = function(){ return ObjectToShortInitializer(this); }

	/** set the "dirty" and "everChanged" flags to true */
	this.dirty = function(){ this.hasChanged = this.everChanged = true; }

	/** clear the "dirty" flag that says "there has been a change to
	 * this model since the last update event broadcase via publish()"
	 */
	this.clean = function(){ this.hasChanged  = false; }

	/** return current value of funky "everChanged" flag */
	this.neverChanged = function(){ this.everChanged = false; }

	/** tell observers that we have changed 
	 * @param {boolean} resetMark if true clear funky "everChanged" flag
	 * @param {Object} optAdhocObj optional adhoc object to pass to observers
	 */
	this.publish = function( resetMark, optAdhocObj )
	{
    	this.dirty();
    	if (resetMark) this.neverChanged();
    	if (this.autoCommit) this.notifyObservers( optAdhocObj );
	}

	/** generic "bean" property GETTER */
	this.GET  = function(property      ){ return this[property];  }

	/** generic "bean" property SETTER (w/o publish) */
	this._SET = function(property,value){ this[property] = value; }

	/** generic "bean" property SETTER (w/publish)
	 * @return flag saying if we published
	 * @type boolean
	 */
	this.SET  = function(property,value)
	{
		if (this.GET(property)==value) return false;
		this._SET(property,value);
		this.publish(false,property);
		return true;
	}

	/** update the timestamp on this model */
	this.updateStamp = function(){ this.timestamp = new Date().valueOf(); }

	/** inhibit publishing until matching EndTransaction() called */
	this.BeginTransaction = function()
	{
		if (!this.autoCommit) Error("already started transaction");
		this.autoCommit = false;
	}

	/** publish a "batch" of updates
	 * @param {boolean} totalReload clears "everChanged" flag iff true
	 */
	this.EndTransaction = function(totalReload)
	{
		if (this.autoCommit) Error("Not in transaction!");
		this.autoCommit  = true;
		if (totalReload) this.neverChanged();
		this.updateStamp();
		this.notifyObservers();
	}

	/** {@link Observable} API */
	this.addObserver = function( observer )
	{
		ValidateArgs(["observer"]);
    	this.subscribers.push( observer ); 
	}

	/** @deprecated @throws not implemented exception */
	this.delObserver = function( observer )
	{
		ValidateArgs(["observer"]);
		throw "Not Implemented Yet";
	    //delete this.subscribers[observer.getID()]; 
	}

	/** if "dirty" flag is set, Notify all subscribers of change
	 * to our state; When done, if we were the initiator of the
	 * cascade of update events (ie if global transaction depth
	 * is back to zero when we are done), then the views will be redrawn.
	 * @param {Object} optAdhocObj optional adhoc object passed to observers
	 */
	this.notifyObservers = function( optAdhocObj )
	{
		if (this.hasChanged)
		{
			gRootView.block();
			var N = this.subscribers.length;
			for (var i=0; i<N; ++i)
			{
//TraceMVC(this.name+"["+optAdhocObj+"] updates["+i+"] "+this.subscribers[i].name);
				this.subscribers[i].update( this, optAdhocObj );
			}
			gRootView.unblock();
		}
		this.clean();
	}
}


Class(ListModel).Extends(Model);
/**
 * @class This class encapsulates a data model for a List of objects.
 * This class implements the {@link Collection} interface.
 * @extends Model
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function ListModel()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName )
	{
		this.Model( optName );	//super()

		// init instance variables
		this._reset();
	}
	
	/** debug method to return this list as a string @type String */
	this.dump = function()
	{
		var N = this.list.length;
		var s = this.name + "===>";
		for (var i=0; i<N; ++i)
		  s += (this.list[i].dump() + "; ");
		return s;
	}

	/** pop top item off list but dont publish @return item @type Object */
	this._pop       = function( ){ return this.list.pop(); }
	
	/** push item onto list but dont publish @return item @type Object */
	this._push      = function(o){ this.list.push(o); return o; }
	
	/** clear list and update timestamp but dont publish */
	this._reset     = function( ){ this.list = new Array(); this.updateStamp(); }

	/** return count of items in list @type int */
	this.getCount   = function( ){ return this.list.length; }
	
	/** return item in list with given index @type Object */
	this.getItem    = function(i){ return this.list[i]; }

	/** return item in list with given index as formatted string @type String */
	this.getItemStr = function(i){ return this.list[i].toString(); }

	/** push given item onto list and publish @return item @type Object */
	this.addItem    = function(o){ this._push(o); this.publish(); return o; }
	
	/** clear list and publish */
	this.reset      = function( ){ this._reset(); this.publish(true); }

	/** iterate thru items in list calling specified function
	 * @param {Function} f function that takes index and object as params
	 * and returns true if the iteration should be stopped before all
	 * items in list are processed.
	 */
	this.iterate = function(f)
	{ 
		var N = this.getCount();
		for (var i=0; i<N; ++i)
		  if ( f( i, this.getItem(i), i ) )
		    return; //early
	}

	/** add the given object into the list just before the given zero-based-index */
	this.addBefore = function(i,o){ this.list.splice(i,0,o); this.publish(); }
}



Class(DequeModel).Extends(ListModel);
/**
 * @class This class encapsulates a data model for Stacks
 * and Queues. The "stack" is built within a dequeue
 * such that up/down do not change the queue itself.
 * @extends ListModel
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function DequeModel()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName ){
		this.ListModel( optName );	//super()
	}

	/** clear the queue and reset the pseudo-stack but dont publish */
	this._reset = function( ){
		this.list = new Array();
		this.tos  = -1; //position in deque of top of pseudo-stack
		this.updateStamp(); 
	}

	/** DEQUE API: add given object to back of the line @return item */
	this.addFirst = function(o){         this.list.unshift(o); /*this.publish();*/ return o; }
	/** DEQUE API: add given object to front of the line @return item */
	this.addLast  = function(o){         this._push       (o); /*this.publish();*/ return o; }
	/** DEQUE API: remove object from back of the line @return item */
	this.delFirst = function( ){ var o = this.list.shift  ( ); /*this.publish();*/ return o; }
	/** DEQUE API: remove object from front of the line @return item */
	this.delLast  = function( ){ var o = this._pop        ( ); /*this.publish();*/ return o; }

	/** QUEUE API: add given object to back of the line @return item */
	this.enqueue  = function(o){ return this.addFirst(o); }
	/** QUEUE API: remove object from front of the line @return item */
	this.dequeue  = function( ){ return this.delLast ( ); }

	/** STACK API: push given object to front of the line aka top of the stack @return item */
	this.push = function(o){ this.tos = this.getCount()  ; return this.addLast(o); }
	/** STACK API: remove given object from front of the line aka top of the stack @return item */
	this.pop  = function( ){ this.tos = this.getCount()-1; return this.delLast( ); }


	/** pseudo-Stack API: return the top of the pseudo-stack */
	this.top     = function(offset){
		if (offset) offset = parseInt(offset); else offset = 0;
		return this.getItem( offset+this.tos );
	}

	/** pseudo-Stack API: return index of next up iff we can go up */
	this.upIndex = function(){
		return ((this.getCount()-this.tos)<=1) ? null : this.tos+1;	
	}
	/** pseudo-Stack API: non-destructive push/get */
	this._up     = function(){
		++this.tos;
		return this.top();		
	}
	/** pseudo-Stack API: return index of next down iff we can go down */
	this.downIndex = function(){
		return (this.tos<0) ? null : this.tos;	
	}
	/** pseudo-Stack API: non-destructive pop */
	this._down   = function(){
		--this.tos;
		return this.top(1);	// return what we "popped"	
	}
	/** pseudo-Stack API: throw away items above top of pseudo-stack */
	this._cutback = function(){
	  while (this.getCount()>(this.tos+1)) this._pop();
	}
}


Class(MapModel).Extends(Model);
/**
 * @class This class encapsulates a data model for a Map of
 * object/key pairs. Its API is a wrapper for the {@link Map} API.
 * This class implements the {@link Collection} interface.
 * @extends Model
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function MapModel()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName )
	{
		this.Model( optName );	//super()

		// init instance variables
		this.map = new Map("Map for :"+this.name);
	}

	this.getCount   = function(   ){ return this.map.getCount(); }
	this.getItemStr = function(k  ){ return this.map.getItemStr(k); }
	this.getItem    = function(k  ){ return this.map.getItem(k); }
	this.delItem    = function(k)  { this.map.delItem(k);   this.publish(); }
	this.addItem    = function(k,o){ this.map.addItem(k,o); this.publish(); }
	this.reset      = function(   ){ this.map.reset();      this.publish(true); }
	this.iterate    = function(f)  { this.map.iterate(f); }
}
			
				
/** The suffix added to the basic name to get the validity attribute name. */
var kValidityAttributeSuffix = ".err";

Class(ScalarModel).Extends(Model);
/**
 * @class This class encapsulates a Scalar data model with a
 * default implementation of the scalar being implemented via
 * a (bean-like) property. The property name can optionally
 * be specified.<p>
 * Scalar models assume that a validity attribute of the
 * basic model value can also be set and it's member name
 * is based on the name of the basic value's member name.
 * By convention, the validity attribute consists of an error
 * message string if invalid or null if valid.
 * @extends Model
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function ScalarModel()
{
	/** @param {String} optPropName optional property name to use instead of default
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function( optPropName, optName  )
	{
		this.Model( optName );	//super()

		// init instance variables
		this.pname = optPropName || "scalarvalue";
	}

	/** set scalar to given value but dont publish */
	this._setValue = function(x){        this._SET(this.pname,x); }

	/** set scalar to given value and publish */
	this.setValue  = function(x){ return this. SET(this.pname,x); }

	/** get scalar value */
	this.getValue  = function( ){ return this. GET(this.pname  ); }

	/** set the validity attribute of this scalar value and dont publish
	 * @param {String} errMsg the validity attribute (as an error message)
	 */
	this._setValidity = function( errMsg ){
		       this._SET( this.pname+kValidityAttributeSuffix, errMsg );
	}

	/** return the validity attribute of this scalar value @type String */
	this.getValidity  = function(){
		return this. GET( this.pname+kValidityAttributeSuffix );
	}
}


Class(BoolModel).Extends(ScalarModel);
/**
 * @class This class encapsulates a Boolean scalar data model.
 * @extends ScalarModel
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function BoolModel()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName ){
		this.ScalarModel( "boolflag", optName );	//super()
	}

	/** return the current value of this model */
	this.isTrue  = function(){ return this.getValue(); }

	/** set model to given value and publish
	 * @param {boolean} b value to set model to
	 */
	this.setFlag = function(b){ this.setValue(b); }
}


Class(AttributeModel,["base object","attribute"]).Extends(ScalarModel);
/**
 * @class This class encapsulates a "wrapper" data model for a
 * specified attribute of a specified base object.
 * The specified attribute can NOT be a method call.
 * @extends ScalarModel
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function AttributeModel()
{
	/** @param {Object} baseObject object whose property we are wrappering
	 *  @param {String} attribute name of baseObject's member we are wrappering
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function( baseObject, attribute, optName )
	{
		this.ScalarModel( undefined, optName?optName:attribute );	//super()

		// init instance variables
		this.attribute = attribute;
		this.base      = baseObject;
	}

	/** set the value of base object attribute and publish */
	this.setValue = function(x)
	{
		this._setValue( x );
		this.publish();
	}

	/** set the value of base object attribute but dont publish */
	this._setValue = function(x){        this.base[ this.attribute ] = x; }

	/** return the value of base object attribute */
	this.getValue  = function( ){ return this.base[ this.attribute ];     }

	/** set the validity attribute of this attribute but dont publish
	 * @param {String} errMsg the validity attribute (as an error message)
	 */
	this._setValidity = function( errMsg ){
		       this.base[ this.attribute+kValidityAttributeSuffix ] = errMsg;
	}

	/** return the validity attribute of this attribute @type String */
	this.getValidity = function(){
		return this.base[ this.attribute+kValidityAttributeSuffix ];
	}
}


Class(ROAttributeModel,["base object","attribute"]).Extends(AttributeModel);
/**
 * @class This class encapsulates a "wrapper" data model for a
 * specified attribute of a specified base object.
 * The specified attribute can in fact be a method name and
 * it will be called as needed. This model is READ-ONLY.
 * @extends AttributeModel
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function ROAttributeModel()
{
	/** @param {Object} baseObject object whose property we are wrappering
	 *  @param {String} attribute name of baseObject's member we are wrappering
	 *  @param {String} optParam optional parameter to pass to attribute if
	 *                  it is a method
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function( baseObject, attribute, optParam, optName )
	{
		this.AttributeModel( baseObject, attribute, optName );	//super()

		// init instance variables
		this.optParam = optParam;
	}

	/** invoke our attribute as a method call and return result */
	this.invoke = function(f,optParam){ return f.call(this.base,optParam); }

	this._setValue    = function(){ Error("attempt to set a read-only model"); }
	this._setValidity = function(){ this._setValue(); }
	this.setValue     = function(){ this._setValue(); }

	/** return current value of base object attribute (even if it is a method). */
	this.getValue = function(){
		var x = this.base[ this.attribute ];
		if (x && x instanceof Function) return this.invoke(x,this.optParam);
		return x;
	}
}


var kPropertyIdSelect = 'selected';
var kNothingSelected  = -1;

Class(SelectionModel,["Collection Model"]).Extends(ScalarModel);
/**
 * @class This class encapsulates the data model for a selector
 * which indicates the currently selected item in a specified
 * Collection data model. This means that the range of legal values
 * for this model's value is [0..Collection.getCount()-1] plus
 * a "nothing selected" value.
 * @extends ScalarModel
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function SelectionModel()
{
	/** @param {Object} collModel Collection data model we select from
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function( collModel, optName )
	{
		this.ScalarModel( kPropertyIdSelect, optName );	//super()

		// init instance variables
		this._select( kNothingSelected );
		this.subscribe( collModel );
	}

	/** return our Collection model @type Collection */
	this.getList = function( ){ return this.model; }

	/** return the specified item in our Collection
	 * @param {Object} i item key
	 */
	this.getItem = function(i){ return this.getList().getItem(i); }
	
	/** return the size of our Collection model */
	this.getCount = function( ){ return this.getList().getCount(); }
	
	/** return the currently selected Collection item */
	this.getSelection = function( ){ return this.getItem( this.getValue() ); }
	
	/** return the currently selected Collection item formatted as string @type String */
	this.getSelectionStr = function( ){ return this.getSelection().toString(); }
	
	/**  return the currently selected Collection item formatted as description @type String */
	this.getDescription  = function(i){ return this.getItem(i).getDescription(); }

	/** select the specified Collection item but dont publish
	 * @param {Object} k key of item to select
	 */
	this._select = function(k){ this._setValue(k); }
	
	/** select the specified Collection item and publish
	 * @param {Object} k key of item to select
	 */
	this.select	= function(k){ this. setValue(k); }
	
	/** select the zero-th item (NOT "nothing selected") but dont publish */
	this._unselect = function( ){ this._select(0); }
	
	/** select the zero-th item (NOT "nothing selected") and publish */
	this.unselect = function( ){ this._unselect(); this.publish(true);}
	
	/** re-select the current value and publish */
	this.reselect = function( ){ this.publish(); }
	
	/** handle Collection model update event by "unselect"ing */
	this.update = function( ){
		if ( this.getList().hasChanged ) this.unselect();
//		TraceEvt("UnSelectUpdate["+this+"]");
	}

	/** return the index into the Collection model of the current selection
	 * @type int
	 */
	this.getIndex = function( )
	{ 
		var index = 0;
		var goal  = this.getValue();
		this.model.iterate(
		  function(key,o,i){ if (key==goal){ index = i; return true; } }
		);
		return index;
	}
}


Class(UndoRedoModel).Extends(DequeModel);
/**
 * @class This class encapsulates the data model for the
 * Command Dequeue which supports deep undo and redo.
 * NOTE: This logic invokes the "synchronized" version
 * of the Command API.
 * @extends DequeModel
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function UndoRedoModel()
{
	this.konstructor = function(){
		this.DequeModel( "undo/redo commands" );	//super()
	}

	/** Add a new command object to the top of the "stack" and "do" it. */
	this.newDo = function(c){ this._cutback();
							{this.push(c).syncDoIt(); this.publish();} }

	/** "pop the top" command object and "undo" it. */
	this.unDo  = function( ){ if (this.downIndex()!=null)
							{this._down().syncUnDo(); this.publish();} }

	/** "unpop the top" command object and "redo" it. */
	this.reDo  = function( ){ if (this.  upIndex()!=null)
							{this.  _up().syncReDo(); this.publish();} }

	/** iff command index is defined, return command description @type String */
	this.cmdDesc = function(i){
		return (i!=null) ? this.getItem(i).toString() : null;
	}

	/** iff there is another undo-able command return its description @type String */
	this.hasUnDo = function(){ return this.cmdDesc( this.downIndex() ); }

	/** iff there is another redo-able command return its description @type String */
	this.hasReDo = function(){ return this.cmdDesc( this.  upIndex() ); }
}

/** Global Undo Command Queue */
var gUndoCmds = new UndoRedoModel();

/** Do the given command in "synchonized" mode (meaning that
 * it will wait until commands that are already running/queued
 * have finished).  NOTE: BECAUSE OF THIS, YOU WILL DEADLOCK
 * IF ANY COMMAND INVOKES ANOTHER COMMAND!!!!!
 * @return false if the command was invalid and not run
 * @type boolean
 */
function DoCmd( command )
{
	if ( command.isInvalid() ) return false;
	if ( command.canUndo     ) gUndoCmds.newDo( command );
	else command.syncDoIt();
	return true;
}

////////////////////////////////////////////
///////////////// VIEWS  ///////////////////
////////////////////////////////////////////

Class(View);
/**
 * @class This class acts as the base class for each (MVC) View.
 *<p>
 * Views are responsible for keeping up-to-date the HTML
 * associated with a particular portion of the web page
 * identified via a "hook" (i.e. an HTML element ID).
 *<p>
 * The view should display the current state of the data
 * in the model(s) that it "watches".  [NOTE: Views do not
 * "subscribe" to Models and react to their individual update
 * events because all views need to draw in a coordinated
 * top-down fashion.]
 *<p>
 * Each View is also a container of subviews (as needed)
 * and coordinates their layout by managing some skeleton
 * framework HTML (e.g. tables/divs/spans/etc) to which
 * the subviews hook and manage [I.E. the GoF  <a target="_blank"
 * href="http://www.javaworld.com/javaworld/jw-09-2002/jw-0913-designpatterns_p.html">
 * Composite design pattern</a>].
 *<p>
 * Once set up, views just react to "draw" events where
 * they draw "this" view and then recurse thru any subviews
 * invoking their draw method. [This is so that any elements
 * of this view that are to be "hooks" for any subviews can
 * be generated by this view first.]
 *<p>
 * Whenever a {@link Controller} event causes some {@link Model}
 * to change, (and after all observing data models have finished
 * their updates), the global "root container" view will initiate
 * a single draw event cascade to update all Views on the page.
 *<p>
 * "Drawing" entails first looking at the appropriate data
 * model(s) for this view and deciding whether they require
 * "rebuilding" the HTML of this view, and if so, replacing
 * the current HTML with newly generated HTML [via the innerHTML
 * of the HTML Element ID associated with this view].
 * Then the {@link #paint} method is invoked to "decorate" the view
 * with the model(s) current data (i.e. set any HTML attributes
 * that need updating e.g. background color). Normally, HTML
 * need not be constantly rebuilt, only decorated.
 * A special case is where a container needs to rebuild its
 * HTML, all subviews are forced to as well (even if they
 * wouldnt normally based on the views they are watching).
 * <p>
 * The HTML (in string form) is generated by the abstract method
 * "buildHTMLstr". The abstract method "mustRebuild" decides
 * whether the draw event requires the HTML to be rebuilt before
 * calling "paintHTML" (which is called if "mustRepaint").
 *<p><pre>
 * Subclasses of View should define/override:
 *   (1) {@link #buildHTML} constructs this view's HTML
 * OR, use the default buildHTML which builds, via innerHTML, the
 * HTML string returned from buildHTMLstr(), hence you would
 * override instead:
 *   (1) {@link #buildHTMLstr} generates this view's HTML (as string)
 *
 *   (2) {@link #paintHTML} modifies/decorates existing HTML structures
 *   (3) {@link #mustRepaint} decides if this view's HTML needs repainting
 *   (4) {@link #mustRebuild} decides if this view's HTML needs rebuilding
 * The default implementation of mustRebuild() requires that
 * instead of overriding mustRebuild() instead override:
 *   (4) {@link #currentContext} returns a Context subclass object
 *       containing the driver information for building this view.
 *</pre>
 * @extends OObject
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function View()
{
	/** @param {boolean} optDisable optional "disable view" flag (default false)
	 *  @param {String} optName optional name of this instance
	 *  @param {String} optViewID optional ViewID of this instance.
	 *                  Note that while all views must have a view ID,
	 *                  they are usually assigned an ID at the point where
	 *                  a view is added as a subview to another view
	 * via {@link #addSubView}, {@link #embedView}, etc.
	 */
	this.konstructor = function( optName, optViewID, optDisable )
	{
		// init instance variables
	    if (optName) this.name = optName;

		this.disabled         = optDisable || false;
		this.visible          = true;
		this.model            = null;	//default (but not necessarily only) model
		this.subviews	      = new Map( this.name+" subviews" );
		this.parentView       = null;
		this.embeddedSubViews = false;	//if true subviews are built via buildHTMLstr()
		this.lastBuildContext = null;
		this.setViewID( optViewID );

		//init static member variables
		if (View.Depth==undefined) View.Depth = 0;
	}

	/** return an HTML string of the basic structure for this view.
	 * As a side effect, build/update subview list for this view.
	 * As a side effect, invoke this method on each nested subview.
	 * @return HTML (suitable for assigning to innerHTML)
	 * @type String
	 */
	this.buildHTMLstr = function(){ return ""; /*override me*/ }

	/** update/modify attributes of basic existing HTML for THIS view */
	this.paintHTML    = function(){ /*override me*/ }

	/** return true IFF model(s) require the HTML rebuilt for THIS view
	 * default implementation: If there is a difference in the current
	 * "build context" and the current one then rebuild else not.
	 * @type boolean
	 */
	this.mustRebuild  = function(){ return this.contextChanged(); }

	/** return true IFF model(s) require HTML repaint.
	 * NOTE: rebuilding view forces repaint of all subviews.
	 * NOTE: repainting THIS view does NOT force repaint of subviews.
	 * @type boolean
	 */
	this.mustRepaint  = function(){ return true; /*override me if needed*/ }

	/** return the current "context" of this view.
	 * Default is "no rebuild ever needed" (unless others force us to)
	 * @type Context
	 */
	this.currentContext = function(){ return new Context( 0 ); /*override me*/ }

	/** register the given model as our primary data model
	 * @param {Model} m the data model to "watch"
	 */
	this.watchModel = function(m ){ this.model  = m;    }
	
	/** (re)define the view ID for this view */
	this.setViewID  = function(ID){ this.viewID = ID;
	                                 this.hook   = null;
	                                 this.widget = null; }

	/** return the "inner" view ID for this view @type String */
	this.innerID    = function(){ return this.viewID + ".inner"; }

	/** Return the effective ID of the widget for this view.
	 * Finesse the fact that the viewID is sometimes
	 * the ID of a wrapper tag (e.g. span) and other
	 * times is the ID of the actual view's tag.
	 * We assume that views that are "embedded" have
	 * no wrapper tag, otherwise they do. In any event,
	 * the View subclass can say what the "inner" ID
	 * is when there is a wrapper.
	 * @type String
	 */ 
	this.getWidgetID = function()
	{
		var noWrapper = this.parentView && this.parentView.embeddedSubViews;
		return( noWrapper ? this.viewID : this.innerID() );
	}

	/** return the HTML element of this view's widget tag @type element */
	this.getWidget = function(forceReload) // cached lazy load
	{
		if (this.widget==null || forceReload)
			this.widget = getHook( this.getWidgetID() );
		return this.widget;
	}

	/** return the HTML element of this view's hook tag @type element */
	this.getHook = function(forceReload) // cached lazy load
	{
		if (this.hook==null || forceReload)
			this.hook = getHook( this.viewID );
		return this.hook;
	}

	/** Build the HTML for this view. This can be overrided to directly
	 * build HTML via DOM operations or use this default implementation
	 * that takes an HTML string from {@link #buildHTMLstr} and puts it
	 * into the innerHTML of this view's hook HTML element. Note that
	 * building the HTML for this view implies rebuilding the HTML for
	 * all "embedded" subviews.
	 */
	this.buildHTML = function()
	{
		var hook = this.getHook(true);
		if (hook!=null){
			if (this.embeddedSubViews) this.clearSubViews();
			hook.innerHTML = this.buildHTMLstr();
		}
	}

	/** update and save the current {@link Context} for this view */
	this.updateContext = function(){
		this.lastBuildContext = this.currentContext();
	}

	/** return true iff the view context has changed @type boolean */
	this.contextChanged = function(){
		var same = this.currentContext().sameAs( this.lastBuildContext );
		return ! same;
	}

	/** disable this view (and hence all subviews) */
	this.disable = function( ){ this.disabled = true;  }

	/** enable this view (thereby enabling all enabled subviews) */
	this.enable  = function( ){ this.disabled = false; }

	/** set this view as visible or not and manifest it via the HTML.
	 * If setting to invisible then we also set all subviews to invisible
	 * BUT NOT THE OTHER WAY ROUND!
	 */
	this.setVisible = function(isVisible)
	{
		setVisibility( this.getWidgetID(), this.visible=isVisible );//TRICKY
		if (!isVisible) this.setSubViewsVisible(false);
	}

	/** set all subview visiblility */
	this.setSubViewsVisible = function(v){
		this.subviews.iterate( function(viewID,aView){aView.setVisible(v)} );
	}

	/** return whether this view is visible @type boolean */
	this.isVisible = function(){ return this.visible; }

	/** if enabled, force a "draw" of this view (and all enabled subviews) */
	this.redraw	= function(){ this.draw(true); }

	/** if enabled, cause entire subview tree to be built/painted as needed */
	this.draw = function( optForceRebuild )
	{
		if (this.disabled) return;

	    //Recursively (re)build any view in the
	    //entire subview tree that needs building.
	    var rebuilt = this.build( optForceRebuild );

	    //subview tree should be stable now, so,
	    //recursively (re)paint entire subview tree
	    this.paint( rebuilt );
	}

	/** does this view or any embedded subview need rebuilding? @type boolean */
	this.rebuildAny = function()
	{
		var must = this.mustRebuild();
		if (!must) //no need to check if we already know we need to build
		 if (this.embeddedSubViews)
		  this.subviews.iterate(
		   function(vID,vw){ if (vw.rebuildAny()) return must = true;/*TRICKY!*/ }
							 );
		if (must) TraceMVC(this.name+" says must rebuild");
		return must;
	}

	/** Recursively build this view and entire subview tree.
	 * @return whether rebuild was done.
	 * @type boolean
	 */
	this.build = function( optForceRebuild )
	{
		if (this.disabled) return false;
	    var rebuild = optForceRebuild || this.rebuildAny();
//TraceEvt("BUILD: "+this.name+"[rebuild="+rebuild+"] ID="+this.viewID);
		if (rebuild) { this.updateContext(); this.buildHTML(); }
		if (!this.embeddedSubViews) this.buildsubviews( rebuild );
		return rebuild;
	}

	/** Recursively invoke build on entire subview tree.
	 * @return whether rebuild was done.
	 * @type boolean
	 */
	this.buildsubviews = function( optForceRebuild )
	{
	  this.subviews.iterate(
		function( viewID, aView ){

		  	aView.setViewID( viewID );
			aView.build( optForceRebuild );
		}
	  );
	}

	/** Recursively invoke paint on entire subview tree. */
	this.paint = function( optForceRepaint )
	{
		if (this.disabled) return;
		if (optForceRepaint || this.mustRepaint()) this.paintHTML();
		this.subviews.iterate( function( viewID, aView ){ aView.paint(); } );
	}

	/////////// CONTAINER INTERFACE //////////////

	// NOTE: Changing the subviews list does not cause a redraw
	// so changes will not appear until the next global draw.

	/** set the parent view of this view */
	this.setParentView  = function(v){ this.parentView = v;   }

	/** clear the list of subviews of this view */
	this.clearSubViews	= function( ){ this.subviews.reset(); }
	
	/** add the specified view/ID to our subview list */
	this.addSubView		= function( viewID, aView )
	{
		aView.setViewID( viewID );
		aView.setParentView( this );
		this.subviews.addItem( viewID, aView );
	}

	/** find the specified view in the tree of subviews @type View*/
	this.getSubView	= function( viewID )
	{
		var theView = this.subviews.getItem( viewID );
		if (!theView)
		 this.subviews.iterate(
		  function( aViewID, aView ){
		  	var v = aView.getSubView( viewID );
		  	if (v) {theView = v; return true;}
		  }
		 );
		return theView;
	}

	/** delete the specified view from our subview list */
	this.delSubView = function( viewID ){ this.subviews.delItem( viewID ); }

	/** add the specified view/ID as an "embedded" subview. Embedded subviews
	 * are those whose HTML string is embedded in the HTML string of its
	 * parent view. I.E. When {@link #buildHTMLstr} is called for a view,
	 * it is expected to return a string containing its HTML and all the
	 * HTML for its embedded subviews. NOTE: If one subview is embedded
	 * then ALL subviews of this view must be embedded.
	 * @return view
	 * @type View
	 */
	this.embedView = function( viewID, view )
	{
		this.embeddedSubViews = true;
		this.addSubView( viewID, view );
		       view.updateContext();
		return view;
	}

	/** same as {@link #embedView} but return the HTML string of the
	 * view rather than the View object.
	 * @return HTML string
	 * @type String
	 */
	this.embedHTML = function( viewID, view ){
		return this.embedView( viewID, view ).buildHTMLstr();
	}
	
	// NOTE: DONT! call busy() in block because it causes the screen
	// to flash on menu selections!?!  It took hours to track it down...
	// You are warned!

	/** API to block/unblock view updating (to stop redraw thrashing) */
	this.block = function(){ ++View.Depth; }

	/** API to block/unblock view updating (to stop redraw thrashing) */
	this.unblock = function(){ if (--View.Depth == 0) DRAWVIEWS(); }
}

/** Global Root Container View (initialized to empty container) */
var gRootView = new View( "root view", "fauxRootHook", true );

/** static function to draw the root view (and hence all views) */
function DRAWVIEWS(){ gRootView.draw(); }


Class(ListView).Extends(View);
/**
 * @class This class is a View that expects to subscribe to a
 * {@link ListModel} and will invoke {@link #itemHTMLstr} on
 * each member of the list when {@link #buildHTMLstr} is called
 * and {@link #itemPaint} on each member when {@link #paintHTML}
 * is called.
 *<p><pre>
 * Subclasses of ListView should define/override:
 * (A) {@link #itemHTMLstr} which creates HTML for specified item
 * (B) {@link #itemPaint} which decorates HTML for specified item
 *</pre>
 * @extends View
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function ListView()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName )
	{
		this.View( optName );	//super()

		// init instance variables

		//each list element view is effectively an implicit embedded view,
		//hence dont try to build them again via buildsubviews()!
	    this.embeddedSubViews = true;
	}

	/** method that should return HTML string for specified list item
	 * @param {int} index index into our ListModel
	 * @param {Object} item the actual item from our ListModel
	 * @param {String} itemID the view ID of the corresponding item subview
	 * @type String
	 */
	this.itemHTMLstr = function(index,item,itemID){ /* abstract */ }

	/** method that should decorate HTML for specified item
	 * @param {int} index index into our ListModel
	 * @param {Object} item the actual item from our ListModel
	 * @param {String} itemID the view ID of the corresponding item subview
	 */
	this.itemPaint   = function(index,item,itemID){ /* abstract */ }

	/** return the view ID for the item subview specified
	 * @param {int} index index into our ListModel/ListView
	 * @type String
	 */
	this.itemViewID	 = function(index){ return this.getWidgetID() + index; }

	/** invoke {@link #itemPaint} for each item in our list */
	this.paintHTML   = function()
	{
		var listView = this;
		this.model.iterate(
		  function( i, ithItem ){
		  	listView.itemPaint( i, ithItem, listView.itemViewID(i) );
		  }
		);
	}

	/** return the combined HTML string built from each {@link #itemHTMLstr}
	 * @type String
	 */
	this.listHTMLstr = function()
	{
		var HTML = new Array();
		var listView = this;
		this.model.iterate(
		  function( i, ithItem ){
		  	HTML[i] = listView.itemHTMLstr( i, ithItem, listView.itemViewID(i) );
		  }
		);
		return HTML.join('');
	}

	/** generate container/framework HTML @type String */
	this.buildHTMLstr = function()
	{
		var HTML = new Array();
		//HTML.push( '<table>' );
		//HTML.push( '<tbody>' );
		HTML.push( this.listHTMLstr() );
		//HTML.push( '</tbody>' );
		//HTML.push( '</table>' );
		return HTML.join('');
	}
}


Class(ScalarView,["Scalar Model"]).Extends(View);
/**
 * @class This class produces a view of the specified 
 * {@link ScalarModel}. It accepts an optional formatter
 * function specification that will transform the raw value
 * of the model into a desired format. This formatter
 * function should accept one parameter (the value) and
 * return a formatted string version of that value.
 * @extends View
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function ScalarView()
{
	/** @param {ScalarModel} xModel the data model to view
	 *  @param {Function} optFormatFunction optional formatter function
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xModel, optFormatFunction, optName )
	{
		this.View( optName );	//super()

		// init instance variables
		this.watchModel( xModel );
		this.formatter = optFormatFunction;
	}

	/** return the formatted version of the given value using our formatter
	 * function (where the raw value is returned if no formatter is registered)
	 * @type String
	 */
	this.formatted    = function(x){ return this.formatter ? this.formatter(x) : x; }

	/** return the current value of our model formatted with our formatter @type String */
	this.getValueStr  = function( ){ return this.formatted( this.model.getValue() ); }

	/** set the value of our display "widget" */
	this.setDisplay   = function(x){ this.getWidget().innerHTML = x; }
	
	/** update the display with the current model value */
	this.updateView   = function( ){ this.setDisplay( this.getValueStr() ); }

	this.buildHTMLstr = function( ){ return genHook( this.getWidgetID() ); }
	this.paintHTML    = function( )
	{
		var visible = this.visible;
		setElemVisibility( this.getWidget(), visible );
		if (visible) this.updateView();
	}
}

/**
 * Create and embed, as a subview, a {@link ScalarView} of the specified
 * data model attribute.
 * @param {View} parentView the view to embed the new view into
 * @param {String} attribute the identifier of the attribute of the
 * parent view's primary data model to view
 * @param {Function} optFormatter optional formatter function
 * @param {Object} optParam optional parameter to pass to model method
 * if the attribute actually refers to a method rather than a data element
 * @return the HTML string of the parentView (that includes the new embedded subview)
 * @type String
 */
function EmbedAttributeViewer( parentView, attribute, optFormatter, optParam )
{
	var viewID = parentView.getWidgetID() + "." + attribute;
	var model  = new ROAttributeModel( parentView.model, attribute, optParam );
 	return parentView.embedHTML( viewID, new ScalarView( model, optFormatter ) );
}


////////////////////////////////////////////
////////////// Controllers /////////////////
////////////////////////////////////////////

Class(Controller).Extends(View);
/**
 * @class This class acts as the base class for each controller
 * (that supports an underlying View). Subclasses of Controller
 * should define/override all overrides required by View.
 * @extends View
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function Controller()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName )
	{
		this.View( optName );	//super()
	}
}


Class(ButtonController,["button image filename", "descriptive text", "event handler"])
.Extends(Controller);
/**
 * @class This class is a Controller that<pre>
 *  (1) watches a {@link BoolModel} from which the "enable" state can
 *      be deduced (actually only uses {@link BoolModel#isTrue}),
 *  (2) manages an image button view of the enable state,
 *  (3) defines a "pushed" event.
 *</pre>
 * @extends Controller
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function ButtonController()
{
	/**
	 * @param {BoolModel} optEnableModel optional "is enabled" data model
	 * @param {String} evtHandlerName name of event handler function
	 * @param {String} optName optional name of this instance
	 * @param {String} imgFN filename of the (enabled) button image
	 * @param {String} desc tooltip description for this button
	 */
	this.konstructor = function( imgFN, desc, evtHandlerName, optEnableModel, optName )
	{
		this.Controller( "BUTTON:"+optName );	//super()

		// init instance variables
		this.imgFilename    = imgFN;
		this.altText        = desc;
		this.eventHandler   = eval( evtHandlerName );//lazy bind
		if (optEnableModel) this.watchModel( optEnableModel );
	}

	/** Return whether this button should be enabled.
	 * If no enable model was defined then we are always enabled.
	 * @type boolean
	 */
	this.isEnabled = function(){
		return this.model ? this.model.isTrue() : true;
	}
	
	/** return the alternate text given the current state of this view @type String */
	this.getAltText = function( enabled ){
		return (enabled?"":"disabled:")+ this.altText;
	}

	/** put the HTML in <a target="_blank"
	 * href="http://en.wikipedia.org/wiki/Canonical">canonical form</a>
	 * given the specified enable flag and this controller's state
	 */
	this.canonical = function( enableFlag )
	{
	  var imgBtn = this.getWidget();
	  if ( enableFlag )
	  {
	  	imgBtn.src          = kImgPath + this.imgFilename;
		imgBtn.onclick      = this.eventHandler;
	    imgBtn.alt          = this.getAltText( enableFlag );
	    imgBtn.style.cursor = "hand";
	  }
	  else
	  {
	  	imgBtn.src          = kImgPath + kDim_ + this.imgFilename;
	  	imgBtn.onclick      = null;
	    imgBtn.alt          = this.getAltText( enableFlag );
	  	imgBtn.style.cursor = "not-allowed";
	  }
	}

	this.buildHTMLstr = function(){
		return "<img align='middle' src='"+kImgPath+kImageSpacer
		        +"' name='"+this.getWidgetID()+"'>";
	}

	this.paintHTML = function(){ this.canonical( this.isEnabled() ); }
}


/** undo-button-pressed event handler */
function OnUndoBtnPressed(){ gUndoCmds.unDo(); }

/** redo-button-pressed event handler */
function OnRedoBtnPressed(){ gUndoCmds.reDo(); }

/** pre-screen all keypress events for entire page
 * This implementation handles ctrl-z and ctrl-y
 * to invoke Undo and Redo respectively.
 */
function OnGlobalKeyPress()
{
    if ( event.keyCode==26 ) { OnUndoBtnPressed(); return true; }
    if ( event.keyCode==25 ) { OnRedoBtnPressed(); return true; }
}


Class(CmdButtonController,["button image filename", "undo/redo flag"])
.Extends(ButtonController);
/**
 * @class This class is a ButtonController for "undo/redo" buttons.
 * @extends ButtonController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function CmdButtonController()
{
	/**
	 * @param {boolean} isUndo true if this is undo button else redo button
	 * @param {String} imgFN filename of the (enabled) button image
	 */
	this.konstructor = function( imgFN, isUndo )
	{
		this.ButtonController( imgFN, "foo",
			 isUndo?"OnUndoBtnPressed":"OnRedoBtnPressed", null,
			(isUndo?"undo":"redo")
		);	//super()
		this.isUndo  = isUndo;
	}

	/** return the alternate text for this button
	 * @param {String} enabled a string with the description of what
	 * is about to be undone/redone or null if button should not be enabled
	 * @type String
	 */
	this.getAltText = function( enabled ){
		return (this.isUndo ? "[ctrl-z] UNDO: " : "[ctrl-y] REDO: ")
		     + (enabled ? enabled : "No more to "+(this.isUndo?"undo":"redo"));
	}

	this.isEnabled = function(){
		return this.isUndo ? gUndoCmds.hasUnDo() : gUndoCmds.hasReDo();
	}
}


Class(EditButtonController,["button image filename", "descriptive text", "event handler","enable model"])
.Extends(ButtonController);
/**
 * @class This class is a ButtonController for "edit" buttons
 * which shouldnt be enabled while app is in "read only" mode.
 * @extends ButtonController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function EditButtonController()
{
	/**
	 * @param {BoolModel} enableModel "is enabled" data model
	 * @param {String} evtHandler name of event handler function
	 * @param {String} optName optional name of this instance
	 * @param {String} imgFN filename of the (enabled) button image
	 * @param {String} desc tooltip description for this button
	 */
	this.konstructor = function( imgFN, desc, evtHandler, enableModel, optName ){
		this.ButtonController  ( imgFN, desc, evtHandler, enableModel, optName );	//super()
	}

	this.paintHTML = function(){
		this.canonical( kReadOnly ? false : this.isEnabled() );
	}
}


Class(ScalarEditCmd,["view ID"]).Extends(Command);
/**
 * @class This class implements a scalar edit command.
 *<p>
 * This code should work with either standalone
 * scalar edit controllers, or, with scalar edit
 * controllers that are embedded within "dual"
 * controllers that contain editor and viewer
 * child controllers and therefore may have data
 * model(s) at the "dual" level to update that are
 * separate from the edit controller data model.
 * @extends Command
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function ScalarEditCmd()
{
	/** @param {String} viewID ID of controller generating this command */
	this.konstructor = function( viewID )
	{
		this.Command("edit");	//super()

		this.viewID   = viewID;
		this.editCtrl = gRootView.getSubView( viewID );		
		if ( this.editCtrl )
		{
			this.dual     = (this.editCtrl.parentView
				          && this.editCtrl.parentView instanceof DualController)
						  ?  this.editCtrl.parentView
						  :  null;

			this.newValue = this.editCtrl.getCtrlValue();
			this.oldValue = this.editCtrl.getModelValue();
			if (this.oldValue != this.newValue)
			{
				this.oldDesc = this.getFormattedValue( this.oldValue );
				this.newDesc = this.getFormattedValue( this.newValue );
			}
			else { this.state = -1; this.editCtrl.updateView(); }
		}
		else { this.state = -1; Error("missing "+viewID); }
	}

	/** return the formatted version of the given value using controller's formatter */
	this.getFormattedValue = function( value )
	{
		var x = this.dual ? this.dual.formatted(value) : value;
		return x=="&nbsp;" ? 0 : x; //Extreme Hack!!
	}

	/** stuff the given value into our controller's view */
	this.updateController = function(value){ this.editCtrl.forceValue( value ); }

	/** set our controller to the new value */
	this.doit     = function(){ this.updateController( this.newValue ); }
	
	/** set our controller to the old value */
	this.undo     = function(){ this.updateController( this.oldValue ); }

	this.details  = function(){ return this.dual.model.name
	                                 + " from " + this.oldDesc
								     +   " to " + this.newDesc;
							  }
}

/** ScalarEditController update-event handler
 * @param {String} viewID view ID of the {@link ScalarEditController}
 * generating this event.
 * @return event success?? flag
 * @type boolean
 */
function OnScalarEditUpdate( viewID )
{
	DoCmd( new ScalarEditCmd( viewID ) );
	return true;
}


Class(ScalarEditController,["Scalar Model"]).Extends(Controller);
/**
 * @class This abstract class manages an editor of the specified
 * {@link ScalarModel}. This controller edits a single scalar value.
 * @extends Controller
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function ScalarEditController()
{
	/**
	 * @param {ScalarModel} sModel scalar data model to edit
	 * @param {String} optEvtHndlr optional name of event handler function
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( sModel, optName, optEvtHndlr )
	{
		this.Controller( optName );	//super()

		// init instance variables
		this.watchModel( sModel );
		this.evtHndlrName = optEvtHndlr ? optEvtHndlr : "OnScalarEditUpdate";
	}

	/** set our controller widget to the given value */
	this.setCtrlValue  = function(x){        this.getWidget().value = x; }
	
	/** return the current value of the controller widget */
	this.getCtrlValue  = function( ){ return this.getWidget().value    ; }
	
	/** return a string with full invocation of our event handler @type String */
	this.getEvtHandler = function( ){ return this.evtHndlrName+"('"+this.viewID+"')"; }
	
	/** set our display to the given value */
	this.setDisplay    = function(x){ this.setCtrlValue(x); }
	
	/** return the current value of our data model */
	this.getModelValue = function( ){ return this.model.getValue( ); }
	
	/** set our data model to the given value */
	this.setModelValue = function(x){        this.model.setValue(x); }
	
	/** update our display to the current value of our data model */
	this.updateView    = function( ){ this.setDisplay( this.getModelValue() ); }
	
	/** force this controller to the given value */
	this.forceValue    = function(x){ this.setCtrlValue(x); this.setModels(x); }
	
	/** set our (and our parents if we are embedded within a
	 * {@link DualController}) data model to the given value
	 */
	this.setModels     = function(x){
		this.setModelValue( x );
		if (this.parentView && this.parentView.setModels)
			this.parentView.setModels( x );
	}

	this.paintHTML     = function( ){
		setElemVisibility( this.getWidget(), this.visible );
		if (this.visible) this.updateView();
	}
}


Class(PopupMenuController,["selection model"]).Extends(ScalarEditController);
/**
 * @class This class manages a popup menu which watches
 * a {@link ListModel} (specifying the menu items)
 * embedded within a {@link SelectionModel} that reflects
 * which item is/should-be currently selected. [The scalar value
 * we "edit" is the "select index" of the SelectionModel.]
 * @extends ScalarEditController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function PopupMenuController()
{
	/**
	 * @param {SelectionModel} sModel selection data model to control
	 * @param {String} optEvtHndlr optional name of event handler function
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( sModel, optName, optEvtHndlr ){
	  this.ScalarEditController( sModel, optName, optEvtHndlr ); //super()
	}

	this.buildHTMLstr = function()
	{
		var L    = this.model.getList();
		var HTML = new Array( L.getCount()+2 );
		HTML.push( '<select title="foo" onchange="'+this.getEvtHandler()
		             +'" class="entryfield" name="'+this.getWidgetID()+'">' );

	L.iterate( function(key,ithItem){
		HTML.push( '<option title="bar" value="'+key+'">'
		             + ithItem.getDescription() +'</option>' );
	} );

		HTML.push( '</select>' );
		return HTML.join('');
	}
}

/** handle "key pressed" events in text fields
 * @param {String} viewID view ID of the {@link FieldEditController}
 * generating this event.
 * @return false if this key should be suppressed
 * @type boolean
 */
function OnFieldEditKey( viewID )
{
	// browser handles ctrl-z ["undo"] in a text field.

	// If "enter" key pressed, cause update event [indirectly via blur]
	var char    = event.keyCode;
	var isEnter = (char==13 || char==3);
	if (isEnter) {
		event.srcElement.blur();
		event.srcElement.select();
		return false;
	}

	// otherwise validate key if user specified a keyFilter.
	var SEC = gRootView.getSubView( viewID );
	if (SEC.keyFilter) {
		 return SEC.keyFilter( char, SEC.getWidget().value );
	}

	return true;
}

/**
 * handle "entering a text field" event per <a target="_blank"
 * href="http://www.webreference.com/js/tips/000805.html">webreference tip</a>)
 * @param {Element} field HTML element of text field generating this event
 */
function OnFieldEditFocus( field )
{
//	field.focus();
//	field.blur();
	field.select();
}


Class(FieldEditController,["Scalar Model"]).Extends(ScalarEditController);
/**
 * @class This class manages a form field editor of the
 * specified {@link ScalarModel}.
 * @extends ScalarEditController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function FieldEditController()
{
	/**
	 * @param {ScalarModel} xModel scalar data model to edit
	 * @param {Function} optKeyFilter optional keypress filter function
	 * @param {String} optEvtHndlr optional name of event handler function
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xModel, optKeyFilter, optName, optEvtHndlr )
	{
		this.ScalarEditController( xModel, optName, optEvtHndlr );	//super()

		// init instance variables
		this.keyFilter = optKeyFilter;
	}

	/** Produce HTML version of a scalar editor.<p>
	 * ala {input class='entryfield' name="paylg1" size="15"
	 *             value="123456789.01" onchange="dirty('1');"
	 *             onblur="validateDollar('paylg1')"/}
	 */
	this.buildHTMLstr = function( )
	{
		var keybrdEvent = "OnFieldEditKey  ('"+this.viewID+"')";
		var  focusEvent = "OnFieldEditFocus(this)";
		return '<input onfocus="'+focusEvent
		+'" onblur="return '+this.getEvtHandler()
		+'" onkeypress="return '+keybrdEvent
		+'" NAME="'+this.getWidgetID()
		+'" class="entryfield" size="15" maxlength="20"/>';
	}
}


Class(DollarEditController,["Scalar Model"]).Extends(FieldEditController);
/**
 * @class This class manages an editor of the specified Dollar
 * {@link ScalarModel}.
 * @extends FieldEditController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function DollarEditController()
{
	/**
	 * @param {ScalarModel} xModel scalar data model to edit
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xModel, optName ){
		this.FieldEditController( xModel, dollarKeyFilter, optName );	//super()
	}

	this.setDisplay   = function(x){
		this.getWidget().value = parseFloat(x).toFixed(2);
	}

	this.getCtrlValue = function(){ 
		var s = dollarStrFilter( this.getWidget().value );
		if (       isEmpty(s)) return 0;
//		if (!isSignedFloat(s)) return 0;
		return  parseFloat(s);
	}
}

////////////////////////////////////////////////////////////////////////////////

// Constants defining Edit Rules for DualControllers
var kEditRulePos  = '+';	//positive or zero
var kEditRuleNeg  = '-';	//negative or zero
var kEditRuleNonZ = '#';	//non-zero
var kEditRuleZero = '0';	//R/O zero
var kEditRuleCopy = 'X';	//R/O copy of balance A
var kEditRuleRO   = 'R';	//R/O (current value)
var kEditRuleEdit = '?';	//editable - no validation


Class(DualController,["scalar model","css classname"]).Extends(Controller);
/**
 * @class This abstract class manages dual views (viewer and editor)
 * of a {@link ScalarModel}.
 * @extends Controller
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function DualController()
{
	/**
	 * @param {ScalarModel} sModel scalar data model to view/edit
	 * @param {String} className the CSS classname to use for formatting
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( sModel, className, optName )
	{
		this.Controller( optName );	//super()

		// init instance variables
		this.watchModel( sModel );
		this.editing   = false;
		this.viewer    = null;
		this.editor    = null;
		this.className = className;
		this.mode      = kEditRuleEdit;
	}
	
	/** validate data and return null if valid else error msg @type String */
	this.validator = function(){ return null; /*override me*/ }


	this.enableEdit = function( enable ){ this.editing = enable; }
	this.formatted  = function( value  ){ return this.viewer.formatted(value); }
	this.setModels  = function( value  ){
		this       .model._setValue( value );
		this.editor.model._setValue( value );
	}

	this.setEditMode = function( mode, inEdit )
	{
		switch( this.mode = mode )
		{
			case kEditRuleZero:
				this.setModels( 0 );
				//fall thru...
			case kEditRuleCopy:
			case kEditRuleRO:
				this.enableEdit( false );
				break;

			default: this.enableEdit( inEdit );
		}
	}

	/** update Validity attributes @return errmsg @type String */
	this.updateValidity = function()
	{
		var errMsg = this.validator();
		this.       model._setValidity( errMsg );
		this.editor.model._setValidity( errMsg );
		return errMsg;
	}

	this.paintHTML = function()
	{
		this.editor.setVisible( this.visible &&  this.editing );
		this.viewer.setVisible( this.visible && !this.editing );

		var errMsg = this.updateValidity();
		var widget = this.editor.getHook();//we want the wrapper HTML element!
		if (!widget || !widget.parentNode) return;
		var parent = widget.parentNode;
		    widget = this.editor.getWidget();
		if (widget==null){ Break("missing editor widget"); widget = parent; }//HACK!!
		if (errMsg) {
		  parent.title     = widget.title     = errMsg;
//dont style popup menus after all...
//		  parent.className = widget.className = this.className+'bad';
		  parent.className                    = this.className+'bad';
		  if (!(this.editor instanceof PopupMenuController))
		  	widget.className = parent.className;
		}
		else {
		  parent.title     = widget.title     = parent.parentNode.title;
//dont style popup menus after all...
//		  parent.className = widget.className = this.className;
		  parent.className                    = this.className;
		  if (!(this.editor instanceof PopupMenuController))
		  	widget.className = parent.className;
		}
	}

	this.buildHTMLstr = function()
	{
		var hookID = this.getWidgetID();
		var HTML   = new Array();
	 	HTML.push( this.embedHTML( hookID+".edit", this.editor ) );
		HTML.push( this.embedHTML( hookID+".view", this.viewer ) );
		return HTML.join('');
	}
}


Class(DualDollarController,["Scalar Model","css classname"])
.Extends(DualController);
/**
 * @class This class manages dual views (viewer and editor) of the
 * specified Dollar Scalar Model.
 * @extends DualController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function DualDollarController()
{
	/**
	 * @param {ScalarModel} xModel scalar data model to view/edit
	 * @param {String} className the CSS classname to use for formatting
	 * @param {String} optName optional name of this instance
	 * @param {Function} optFormatFunction optional formatter function
	 */
	this.konstructor = function( xModel, className, optFormatFunction, optName )
	{
		this.DualController( xModel, className, optName );	//super()

		// init instance variables
		var formatter = (optFormatFunction===undefined)
		              ? format_dollar_not : optFormatFunction;
		this.viewer = new ScalarView( this.model, formatter );
		this.editor = new DollarEditController( this.model );
	}

	/** Validate data based on STD edit rules:<pre>
	 * '0'	Read-Only; zero
	 * 'X'	Read-Only; copy of balance A
	 * 'R'	Read-Only; (current value)
	 * '+'	Read-Write; positive or zero required
	 * '-'	Read-Write; negative or zero required
	 * '#'	Read-Write; non-zero required
	 * '?'	Read-Write; no validation
	 *</pre>
	 * @return null if valid else error msg
	 * @type String
	 */
	this.validator = function()
	{
		var value = this.model.getValue();
		switch( this.mode )
		{
			case kEditRulePos:
				if (value>=0) break;
				return "Value ["+value+"] must be Positive.";

			case kEditRuleNeg:
				if (value<=0) break;
				return "Value ["+value+"] must be Negative.";

			case kEditRuleNonZ:
				if (value!=0) break;
				return "Value ["+value+"] must be Non-Zero.";

			case kEditRuleZero:
				this.setModels( 0 );
				break;

			default: break;
		}
		return null;
	}
}


Class(DualMenuController,["Menu Selection Model","scalar model","css classname"])
.Extends(DualController);
/**
 * @class This class manages dual views (viewer and popup menu) of the
 * specified Models.
 * @extends DualController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function DualMenuController()
{
	/**
	 * @param {SelectionModel} mModel selection data model for menu
	 * @param {ScalarModel} sModel scalar data model to view
	 * @param {String} className the CSS classname to use for formatting
	 * @param {String} optName optional name of this instance
	 * @param {String} optEvtHndlr optional name of menu event handler function
	 */
	this.konstructor = function( mModel, sModel, className, optName, optEvtHndlr )
	{
		this.DualController( sModel, className, null, optName );	//super()

		// init instance variables
		this.viewer = new ScalarView( sModel,
			/*formatter method (of ScalarView)*/ function( value )
			{ return this.parentView.editor.model.getDescription(value); }
		);
		this.editor = new PopupMenuController( mModel, "menu for "+optName, optEvtHndlr );

		// default data validation rule
		this.setEditMode( kEditRuleNonZ, false );
	}

	this.setModels = function( value ){ this.model._setValue( value ); }

	/** Validate data based on subset of STD edit rules:<pre>
	 * 'R'	Read-Only; (current value)
	 * '#'	Read-Write; non-zero required
	 * '?'	Read-Write; no validation
	 *</pre>
	 * @return null if valid else error msg
	 * @type String
	 */
	this.validator = function()
	{
		switch( this.mode )
		{
			case kEditRuleNonZ:
				if (this.model.getValue()==0)
					return "Must select a known value.";

			default: break;
		}
		return null;
	}
}

/**
 * Create and embed, as a subview, a {@link DualDollarController} of
 * the specified data model attribute which is expected to be a dollar
 * amount data element.
 * @param {View} parentView the view to embed the new controller into
 * @param {String} attribute the identifier of the attribute of the
 * parent view's primary data model to view/edit
 * @param {String} className the CSS classname to use for formatting
 * @return the HTML string of the parentView (that includes the new embedded subviews)
 * @type String
 */
function EmbedDollarDualEditor( parentView, attribute, className )
{
	var viewID = parentView.getWidgetID() + "." + attribute;
	var model  = new AttributeModel( parentView.model, attribute );
 	var view   = new DualDollarController( model, className );
 	       parentView[ attribute ] = view; //squirrel away reference to view
 	return parentView.embedHTML( viewID, view );
}

/**
 * Create and embed, as a subview, a {@link DualMenuController} of
 * the specified data model attribute
 * @param {View} parentView the view to embed the new controller into
 * @param {String} attribute the identifier of the attribute of the
 *                 parent view's primary data model to view/edit
 * @param {SelectionModel} menuModel data model for the popup menu
 * @param {String} className the CSS classname to use for formatting
 * @param {Function} optEvtHndlr optional edit event handler (default
 *                   is to launch a {@link ScalarEditCmd} command).
 * @return the newly created viewer/editor
 * @type DualMenuController
 */
function EmbedDualMenu( parentView, attribute, menuModel, className, optEvtHndlr )
{
	var dataModel = new AttributeModel( parentView.model, attribute );
	var viewID    = parentView.menuID();
 	return parentView.embedView( viewID, new DualMenuController(
 				menuModel,dataModel,className,viewID,optEvtHndlr) );
}

