The Gravy Framework

mvc.js

Summary

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:

Version: 1.0


Class Summary
AttributeModel This class encapsulates a "wrapper" data model for a specified attribute of a specified base object.
BoolModel This class encapsulates a Boolean scalar data model.
ButtonController This class is a Controller that
  (1) watches a BoolModel from which the "enable" state can
      be deduced (actually only uses BoolModel.isTrue()),
  (2) manages an image button view of the enable state,
  (3) defines a "pushed" event.
CmdButtonController This class is a ButtonController for "undo/redo" buttons.
Collection the Collection "interface" has no code, only an API followed by convention (ie DUCK-TYPING)
  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
Command This class acts as the abstract base class for each Command (ala Command design pattern).
Context This class encapsulates the criteria for building a View such that if it changes, the view needs rebuilding.
Controller This class acts as the base class for each controller (that supports an underlying View).
DequeModel This class encapsulates a data model for Stacks and Queues.
DollarEditController This class manages an editor of the specified Dollar ScalarModel.
DualController This abstract class manages dual views (viewer and editor) of a ScalarModel.
DualDollarController This class manages dual views (viewer and editor) of the specified Dollar Scalar Model.
DualMenuController This class manages dual views (viewer and popup menu) of the specified Models.
EditButtonController This class is a ButtonController for "edit" buttons which shouldnt be enabled while app is in "read only" mode.
FieldEditController This class manages a form field editor of the specified ScalarModel.
ListModel This class encapsulates a data model for a List of objects.
ListView This class is a View that expects to subscribe to a ListModel and will invoke itemHTMLstr() on each member of the list when buildHTMLstr() is called and itemPaint() on each member when paintHTML() is called.
Map This class encapsulates a Map of object/key pairs and implements the Collection virtual interface
MapModel This class encapsulates a data model for a Map of object/key pairs.
Model 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 Observer and Observable.
Mutex This class encapsulates a Map of mutual exclusion data; It self-registers instantiations into a static Map; This class implements the Wallace variation of Lamport's bakery algorithm 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.

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.

Observable the Observable "interface" has no code, only an API followed by convention (ie DUCK-TYPING)
  The required "interface" for "observables":
  (1) addObserver( observer ) - add given Observer to your list
Observer This class acts as the abstract base class for each "observer class" (ala Observer design pattern).
PopupMenuController This class manages a popup menu which watches a ListModel (specifying the menu items) embedded within a SelectionModel that reflects which item is/should-be currently selected.
ROAttributeModel This class encapsulates a "wrapper" data model for a specified attribute of a specified base object.
ScalarEditCmd This class implements a scalar edit command.
ScalarEditController This abstract class manages an editor of the specified ScalarModel.
ScalarModel This class encapsulates a Scalar data model with a default implementation of the scalar being implemented via a (bean-like) property.
ScalarView This class produces a view of the specified ScalarModel.
SelectionModel This class encapsulates the data model for a selector which indicates the currently selected item in a specified Collection data model.
UndoRedoModel This class encapsulates the data model for the Command Dequeue which supports deep undo and redo.
View This class acts as the base class for each (MVC) View.

Method Summary
static boolean DoCmd( command )
           Do the given command in "synchonized" mode (meaning that it will wait until commands that are already running/queued have finished).
static void DRAWVIEWS()
           static function to draw the root view (and hence all views)
static String EmbedAttributeViewer( <View> parentView, <String> attribute, <Function> optFormatter, <Object> optParam )
           Create and embed, as a subview, a ScalarView of the specified data model attribute.
static String EmbedDollarDualEditor( <View> parentView, <String> attribute, <String> className )
           Create and embed, as a subview, a DualDollarController of the specified data model attribute which is expected to be a dollar amount data element.
static DualMenuController EmbedDualMenu( <View> parentView, <String> attribute, <SelectionModel> menuModel, <String> className, <Function> optEvtHndlr )
           Create and embed, as a subview, a DualMenuController of the specified data model attribute
static void MUTEX_CPU_SLICE( <int> cmdID, <int> optStartID )
           static routine to give a slice of CPU to mutex with given ID
static void OnFieldEditFocus( <Element> field )
           handle "entering a text field" event per webreference tip)
static boolean OnFieldEditKey( <String> viewID )
           handle "key pressed" events in text fields
static Object OnGlobalKeyPress()
           pre-screen all keypress events for entire page This implementation handles ctrl-z and ctrl-y to invoke Undo and Redo respectively.
static void OnRedoBtnPressed()
           redo-button-pressed event handler
static boolean OnScalarEditUpdate( <String> viewID )
           ScalarEditController update-event handler
static void OnUndoBtnPressed()
           undo-button-pressed event handler

///////////////////////////////////////////////////////////////////////////
// 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) );
}

The Gravy Framework

Documentation generated by JSDoc on Fri Mar 17 06:40:21 2006