The Gravey 2.5 Framework

grvMVC.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). A detailed explanation of the functionality of this library can be found in the AJAX from Scratch series of articles.

Version: 2.5

Requires:

Author: Bruce Wallace (PolyGlotInc.com)


Class Summary
MVCAJAXReplyCmd This class implements the "process AJAX reply" command.
MVCAttributeModel This class encapsulates a "wrapper" data model for a specified attribute of a specified base object.
MVCBoolEditController This class manages a scalar editor with a set of yes,no,unknown radio buttons attached to the specified Boolean (i.e.
MVCBoolModel This class encapsulates a Boolean scalar data model.
MVCButtonController This class is a Controller that
  (1) watches a MVCBoolModel from which the "enable" state can
      be deduced (actually only uses MVCBoolModel.isTrue()),
  (2) manages a button view using the enable state,
  (3) defines a "pushed" event.
MVCChkBoxEditController This class manages a form field "checkbox" editor of the specified MVCScalarModel.
MVCCmdButtonController This class is a Button Controller for "undo/redo" buttons.
MVCCmdImgButtonController This class is an Image Button Controller for "undo/redo" buttons.
MVCCollection the MVCCollection "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,itemIndex) ) - 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
  (4) getMemento() - return a "memento" that can be accepted later
  (5) setMemento(m) - update the current state based on given "memento"
  (6) reset() - empties collection of elements
  (7) dump() - returns debug string with contents of collection
  (8) iterate(func) - invokes func(itemKey,itemObject,itemIndex)
      on each item in collection
See Memento design pattern.
MVCCommand This class acts as the abstract base class for each Command (ala Command design pattern).
MVCContext This class encapsulates the criteria for building a MVCView such that if it changes, the view needs rebuilding.
MVCController This class acts as the base class for each controller (that supports an underlying MVCView).
MVCDateEditController This class manages a Date form field editor of the specified MVCScalarModel.
MVCDecode This class encapsulates a code/decode (aka picklist) item.
MVCDequeModel This class encapsulates a data model for Stacks and Queues.
MVCDollarEditController This class manages an editor of the specified Dollar MVCScalarModel.
MVCDualBoolController This class manages dual views (viewer and editor) of the specified (Boolean i.e.
MVCDualChkBoxController This class manages dual views (viewer and editor) of the specified (boolean) Scalar Model.
MVCDualController This abstract class manages dual views (viewer and editor) of a MVCScalarModel.
MVCDualDateController This class manages dual views (viewer and editor) of the specified Date Scalar Model.
MVCDualDollarController This class manages dual views (viewer and editor) of the specified Dollar Scalar Model.
MVCDualMenuController This class manages dual views (viewer and popup menu) of the specified Models.
MVCDualStringController This class manages dual views (viewer and editor) of the specified String Scalar Model.
MVCDualTextController This class manages dual views (viewer and editor) of the specified "Text" Scalar Model.
MVCEditRule This class encapsulates edit rules.
MVCFieldEditController This class manages a form field editor of the specified MVCScalarModel.
MVCFormButtonController This class is a Button Controller that manages a form button
MVCImgButtonController This class is a ButtonController that manages an image button
MVCListModel This class encapsulates a data model for a List of objects.
MVCListView This class is a MVCView that expects to subscribe to a MVCListModel and will invoke itemHTMLstr() on each member of the list when buildHTMLstr() is called and itemPaint() on each member when paintHTML() is called.
MVCMap This class encapsulates a Map of object/key pairs and implements the MVCCollection interface
MVCMapModel This class encapsulates a data model for a Map of object/key pairs.
MVCMenuItem the MVCMenuItem "interface" has no code, only an API followed by convention (ie DUCK-TYPING)
  The required "interface" for "menu items":
  (1) getDescription() - return "this" formatted for menu item
MVCModel 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 MVCObserver and MVCObservable.
MVCMutex This class encapsulates a Map of mutual exclusion data; It self-registers instantiations into a static Map; This class implements 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.

MVCObservable the MVCObservable "interface" has no code, only an API followed by convention (ie DUCK-TYPING)
  The required "interface" for "observables":
  (1) addObserver( observer ) - add given MVCObserver to your list
MVCObserver This class acts as the abstract base class for each "observer class" (ala Observer design pattern).
MVCPadScalarView This class produces a blank-padded view of the specified MVCScalarModel.
MVCPopupMenuController This class manages a popup menu which watches a MVCListModel (specifying the menu items) embedded within a MVCSelectionModel that reflects which item is/should-be currently selected.
MVCROAttributeModel This class encapsulates a "wrapper" data model for a specified attribute of a specified base object.
MVCScalarEditCmd This class implements a scalar edit command.
MVCScalarEditController This abstract class manages an editor of the specified MVCScalarModel.
MVCScalarModel This class encapsulates a Scalar data model with a default implementation of the scalar being implemented via a (bean-like) property.
MVCScalarView This class produces a view of the specified MVCScalarModel.
MVCSelectionModel This class encapsulates the data model for a selector which indicates the currently selected item in a specified MVCCollection data model.
MVCTextEditController This class manages a form field "textarea" editor of the specified MVCScalarModel.
MVCTxtButtonController This class is a Button Controller that manages a button that can display text based on a data model's current value
MVCUndoRedoModel This class encapsulates the data model for the Command Dequeue which supports deep undo and redo.
MVCView This class acts as the base class for each (MVC) View.

Method Summary
static boolean mvcDoCmd( command )
           Do the given command in "synchonized" mode (meaning that it will wait until commands that are already running/queued have finished).
static String mvcEmbedAttributeViewer( <MVCView> parentView, <String> attribute, <Function> optFormatter, <Object> optParam )
           Create and embed, as a subview, a MVCScalarView of the specified data model attribute.
static String mvcEmbedBoolDualEditor( <MVCView> parentView, <String> attribute, <int> min, <String> className )
           Create and embed, as a subview, a MVCDualBoolController of the specified data model attribute which is expected to be a (boolean) data element.
static String mvcEmbedChkBoxDualEditor( <MVCView> parentView, <String> attribute, <int> min, <String> className )
           Create and embed, as a subview, a MVCDualChkBoxController of the specified data model attribute which is expected to be a (boolean) data element.
static String mvcEmbedDateDualEditor( <MVCView> parentView, <String> attribute, <String> className )
           Create and embed, as a subview, a MVCDualDateController of the specified data model attribute which is expected to be a String data element containing a Date.
static String mvcEmbedDollarDualEditor( <MVCView> parentView, <String> attribute, <String> className, <Function> optFormatFunction )
           Create and embed, as a subview, a MVCDualDollarController of the specified data model attribute which is expected to be a dollar amount data element.
static String mvcEmbedDualMenu( <MVCView> parentView, <String> attribute, <MVCSelectionModel> menuModel, <String> className, <String> optEvtHndlrName, <String> optInitDesc )
           Create and embed, as a subview, a MVCDualMenuController of the specified data model attribute
static String mvcEmbedStringDualEditor( <MVCView> parentView, <String> attribute, <int> min, <int> max, <String> className, <Function> optKeyFilter )
           Create and embed, as a subview, a MVCDualStringController of the specified data model attribute which is expected to be a String data element.
static String mvcEmbedTextDualEditor( <MVCView> parentView, <String> attribute, <int> rows, <int> cols, <int> max, <String> className )
           Create and embed, as a subview, a MVCDualTextController of the specified data model attribute which is expected to be a String data element.
static Object mvcREDSPACER600()
          
static Object mvcREDSPACER768()
          
static Object mvcSECTIONBREAK()
          
static Object mvcSPACER()
          
static void onMVCFieldEditFocus( <event> e, <Element> field )
           handle "entering a text field" event per webreference tip)
static boolean onMVCFieldEditKey( <event> e, <String> viewID )
           handle "key pressed" events in text fields
static Object onMVCGlobalKeyPress(<event> e)
           pre-screen all keypress events for entire page This implementation handles ctrl-z and ctrl-y to invoke Undo and Redo respectively.
static void onMVCRedoBtnPressed()
           redo-button-pressed event handler
static boolean onMVCScalarEditUpdate( <event> e, <String> viewID )
           ScalarEditController update-event handler
static void onMVCUndoBtnPressed()
           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         grvMVC.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).  A detailed explanation
 * of the functionality of this library can be found in the <a target="_blank"
 * href="http://www.polyglotinc.com/AJAXscratch/">AJAX from Scratch</a>
 * series of articles.
 *
 * @author       Bruce Wallace  (PolyGlotInc.com)
 * @requires     grvUtils.js
 * @requires     grvValidate.js
 * @requires     grvClass.js
 * @version      2.5
 */

//////////////////////////////////////////////////////////////////
// GRAVEY LEXICAL CODING CONVENTIONS:
// (*) All private variables or functions start with "_"
// (*) All variables and functions start with a lowercase letter
// (*) All Classes start with an uppercase letter
// (*) All Class methods and instance variables start with lowercase
// (*) All Class "static" methods and variables start with uppercase
// (*) All constants start with "k"
// (*) All global variables start with "g"
// (*) All event handler functions start with "on"
//
// (*) All Gravey utility global variables start with "gGrv"
// (*) All Gravey MVC     global variables start with "gMVC"
// (*) All Gravey EDO     global variables start with "gEDO"
// (*) All Gravey utility functions start with "grv"
// (*) All Gravey MVC     functions start with "mvc"
// (*) All Gravey MVC event handler functions start with "onMVC"
// (*) All Gravey EDO event handler functions start with "onEDO"
// (*) All Gravey MVC classes start with "MVC"
// (*) All Gravey EDO classes start with "EDO"
//////////////////////////////////////////////////////////////////

grvTraceCmp("grvMVC.js: Begin");

// Override these constants as needed
var kMVCImgPath			= "/images/";		//path to Gravey graphic files
var kMVCDim_			= "DIM_";			//prefix to "dimmed" version of graphics
var kMVCImgSpacer		= 'spacer.gif';		//one pixel transparent
var kMVCImgRedSpacer	= 'redspacer.gif';	//one pixel red
var kMVCImgCalendar		= 'calendar.gif';	//calendar icon

function       mvcSPACER(){ return '<img src="'+kMVCImgPath+kMVCImgSpacer
		+'" height="1" width="5" alt="" align="middle"/>';
}
function mvcSECTIONBREAK(){ return '<img src="'+kMVCImgPath+kMVCImgSpacer
		+'" height="30" width="600" alt="" align="middle"/>';
}
function mvcREDSPACER600(){ return '<img src="'+kMVCImgPath+kMVCImgRedSpacer
		+'" height="20" width="600" alt="600 pixels wide" align="middle"/>';
}
function mvcREDSPACER768(){ return '<img src="'+kMVCImgPath+kMVCImgRedSpacer
		+'" height="20" width="768" alt="768 pixels wide" align="middle"/>';
}

/////////////////// GLOBAL VARIABLES /////////////////////

/** Global Undo Command Queue */
var gMVCUndoCmds;

/** Global Root Container View */
var gMVCRootView;

////////////////////////////////////////////
//////// PATTERN FOUNDATION CLASSES ////////
////////////////////////////////////////////

/**
 * @class the MVCCollection "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,itemIndex) ) - 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
 *  (4) getMemento() - return a "memento" that can be accepted later
 *  (5) setMemento(m) - update the current state based on given "memento"
 *  (6) reset() - empties collection of elements
 *  (7) dump() - returns debug string with contents of collection
 *  (8) iterate(func) - invokes func(itemKey,itemObject,itemIndex)
 *      on each item in collection
 *</pre>
 * See <a target="_blank" href="http://en.wikipedia.org/wiki/Mementto_pattern">
 * Memento design pattern</a>.
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCCollection(){/* this function exists only for JsDoc purposes.*/}


/**
 * @class the MVCMenuItem "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 "menu items":
 *  (1) getDescription() - return "this" formatted for menu item
 *</pre>
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCMenuItem(){/* this function exists only for JsDoc purposes.*/}


Class(MVCDecode,["Code Type","Code #","Code Description"]);
/**
 * @class This class encapsulates a code/decode (aka picklist) item.
 * This class implements the {@link MVCMenuItem} interface.
 * @extends GrvObject
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCDecode()
{
	/** @param {String} type type of code
	 *  @param {String} code value of code
	 *  @param {String} desc description of value
	 *  @param {String} aux  auxiliary text
	 */
	this.konstructor = function( type, code, desc, aux )
	{
		// define instance variables
		this.type = type;
		this.code = code;
		this.desc = desc;
		this.aux  = aux;
	}

	/** return the description of "this" @type String */	
	this.getDescription = function() {
		return /*this.code + "-" + */ this.desc;
	}

	/** return the debug details of "this" @type String */
	this.dump = function()
	{
		var		dStr = new Array();
				dStr.push( "Decode>>>[" );
				dStr.push( "type=" +(this.type?this.type:"null") );
				dStr.push( "code=" +(this.code?this.code:"null") );
				dStr.push( "desc=" +(this.desc?this.desc:"null") );
				dStr.push( " aux=" +(this.aix ?this.aux :"null") );
				dStr.push( "]" );
		return	dStr.join(" ");
	}
}


Class(MVCMap);
/**
 * @class This class encapsulates a Map of object/key pairs
 * and implements the {@link MVCCollection} interface
 * @extends GrvObject
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCMap()
{
	/** @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 a (deep) clone of "this" object. @type Map */
	this.clone = function()
	{
		var m = new MVCMap( this.name );
		this.iterate( function(K,o,i){ m.addItem(K,o); } );
		return m;
	}

	/** 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
	}
	
	/** debug method to return this list as a string @type String */
	this.dump = function()
	{
		var s = this.name + "=Map==>";
		this.iterate(
		  function( i, ithItem ){ s += "["+i+"]="+ ((ithItem.dump?ithItem.dump():ithItem.toString()) +"; "); }
		);
		return s;
	}

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

	/** return a memento of the current state of "this" @type Object */
	this.getMemento = function(){ return this.clone(); }

	/** update "this" based on the given memento
	 *  @param {Object} m the memento
	 */
	this.setMemento = function(m)
	{
		this.reset();
		var self = this;
		m.iterate( function(K,o,i){ self.addItem(K,o); } );
	}
}


Class(MVCContext);
/**
 * @class This class encapsulates the criteria for building
 * a {@link MVCView} 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 GrvObject
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCContext()
{
	/** @param {anyComparableType} optContext optional value to save as view context */
	this.konstructor = function( optContext )
	{
		// init instance variables
		this.context = optContext;
	}

	/**
	 * @param {MVCContext} 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 MVCObservable "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 MVCObserver} to your list
 *</pre>
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 * @see MVCModel
 */
function MVCObservable(){/* this function exists only for JsDoc purposes.*/}


Class(MVCObserver);
/**
 * @class This class acts as the abstract base class for each
 * "observer class" (ala Observer design pattern).
 *<pre>
 * Subclasses of MVCObserver 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 GrvObject
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCObserver()
{
	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.
	 * @param {MVCObservable} 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
	 * @param {MVCObservable} observable object to monitor
	 */
	this.subscribe = function( observable ) {
		this.model = observable;
		observable.addObserver( this );
	}
}

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

Class(MVCCommand);
/**
 * @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 MVCCommand 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 GrvObject
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCCommand()
{
	// init static member variables
	if (grvIsUndefined(MVCCommand.NextID)) MVCCommand.NextID = 1;

	/** @param {String} cmdDesc general description of this command
	 *  @param {String} optErrMsg if not null then cancel this cmd
	 */
	this.konstructor = function( optName, optErrMsg )
	{
		// init instance variables
		if (optErrMsg){ this.state = -1; alert(optErrMsg); return; }

		if (optName) this.name = optName;
		this.state   = 0;	//-1=invalid,0=notDone,1=done,2=undone,3=redone
		this.canUndo = true;
		this.id      = MVCCommand.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 call with global view blocking */
	this.syncDo = function( methodName )
	{
		gMVCRootView.block();//grvBreak("blocked to ["+View.Depth+"] for "+this);
		new MVCMutex( this, methodName );
		gMVCRootView.unblock();//grvBreak("unblocked to ["+View.Depth+"] after "+this);
	}

	/** synchronized this.DOIT() */
	this.syncDoIt = function(){ this.syncDo("DOIT"); }

	/** synchronized this.UNDO() */
	this.syncUnDo = function(){ this.syncDo("UNDO"); }

	/** synchronized this.REDO() */
	this.syncReDo = function(){ this.syncDo("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.";
		gMVCRootView.block();
		this.doit();
		this.state = 1;
		gMVCRootView.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.";
		gMVCRootView.block();
		this.redo();
		this.state = 3;
		gMVCRootView.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:
					gMVCRootView.block();
					this.undo();
					this.state = 2;
					gMVCRootView.unblock();
					break;
			default:
				throw "Cant UNDO a command that isnt done.";
		}
	}
}

/** 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
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function mvcDoCmd( command )
{
	if ( command.isInvalid() ) return false;
	if (!command.canUndo)
	{
	     gMVCUndoCmds.reset();
	     command.syncDoIt();
	}
	else gMVCUndoCmds.newDo( command );
	return true;
}


Class(MVCMutex,["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 <a target="_blank"
 * href="http://wikipedia.org/wiki/Lamport's_bakery_algorithm">
 * 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 GrvObject
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCMutex()
{
	// init "static" Class methods/elements
	if (grvIsUndefined(MVCMutex.CpuSlice)){//keep this line above jsdoc comments!

		/** Static method 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.
		 */
		MVCMutex.CpuSlice = function( cmdID, optStartID )
		{
		//	grvBreak("slice id="+cmdID+" start="+optStartID);
			MVCMutex.Map.getItem(cmdID).cpuSlice(optStartID);
		}

		// init static member variables
		MVCMutex.Map = new MVCMap("global mutex map");
	}

	/** @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 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"
		MVCMutex.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   = grvTimestamp();
		this.choosing = false;
		MVCMutex.CpuSlice( 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 #CpuSlice
	 */
	this.cpuSlice = function( optStartID )
	{
		var startID = optStartID ? optStartID : MVCMutex.Map.firstKey();
		for (var j=MVCMutex.Map.getItem(startID); j; j=MVCMutex.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) ) )
		    ){
				grvBusyDo( "MVCMutex.CpuSlice", '('+ 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.
		MVCMutex.Map.delItem( this.id );
	}
}


Class(MVCEditRule);
/**
 * @class This class encapsulates edit rules. STD edit rules:<pre>
 * MVCEditRule.kZero '0'  Read-Only; zero
 * MVCEditRule.kCopy 'X'  Read-Only; copy of legal balance (HACK!)
 * MVCEditRule.kRO   'R'  Read-Only; (current value)
 *
 * MVCEditRule.kEdit '?'  Read-Write; no validation
 * MVCEditRule.kReqd '*'  Read-Write; non-empty required
 * MVCEditRule.kNonZ '#'  Read-Write; non-zero required
 *
 * MVCEditRule.kPos  '+'  Read-Write; positive or zero
 * MVCEditRule.kPOS  '{'  Read-Write; positive or zero (but not empty)
 * MVCEditRule.kNeg  '-'  Read-Write; negative or zero
 * MVCEditRule.kNEG  '}'  Read-Write; negative or zero (but not empty)
 * MVCEditRule.kFone '('  Read-Write; phone number (or empty)
 * MVCEditRule.kFONE ')'  Read-Write; phone number (required)
 * MVCEditRule.kZip  'z'  Read-Write; zipcode (or empty)
 * MVCEditRule.kZIP  'Z'  Read-Write; zipcode (required)
 * MVCEditRule.kAlfa 'a'  Read-Write; letters[space] (or empty)
 * MVCEditRule.kALFA 'A'  Read-Write; letters[space] (required)
 * MVCEditRule.kDec  '.'  Read-Write; decimal number (or empty)
 * MVCEditRule.kDEC  ':'  Read-Write; decimal number (required)
 * MVCEditRule.kInt  '8'  Read-Write; 0-9 (or empty)
 * MVCEditRule.kINT  '9'  Read-Write; 0-9 (required)
 * MVCEditRule.kBool 'y'  Read-Write; Y,N,y,n,true,false (or empty)
 * MVCEditRule.kBOOL 'Y'  Read-Write; Y,N,y,n,true,false (required)
 * MVCEditRule.kDate 'd'  Read-Write; date (or empty)
 * MVCEditRule.kDATE 'D'  Read-Write; date (required)
 * MVCEditRule.kAlNo ';'  Read-Write; A-Za-z0-9 (or empty)
 * MVCEditRule.kALNO '|'  Read-Write; A-Za-z0-9 (required)
 *</pre>
 * @extends GrvObject
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCEditRule()
{
	// init "static" Class methods/elements
	if (grvIsUndefined(MVCEditRule.kEdit)){//keep this line above jsdoc comments!

		// init static member constants
		// NOTE: Dont EVER use blank as a rule! you are warned!
		// NOTE: Some of these constants must sync with GRAVE database!
		MVCEditRule.kZero = '0';	//R/O zero
		MVCEditRule.kCopy = 'X';	//R/O copy of legal balance
		MVCEditRule.kRO   = 'R';	//R/O (current value)

		MVCEditRule.kEdit = '?';	//editable - no validation
		MVCEditRule.kReqd = '*';	//non-empty required
		MVCEditRule.kNonZ = '#';	//non-zero required

		MVCEditRule.kPos  = '+';	//positive or zero
		MVCEditRule.kPOS  = '{';	//positive or zero (but not empty)
		MVCEditRule.kNeg  = '-';	//negative or zero
		MVCEditRule.kNEG  = '}';	//negative or zero (but not empty)
		MVCEditRule.kFone = '(';	//phone number (or empty)
		MVCEditRule.kFONE = ')';	//phone number (required)
		MVCEditRule.kZip  = 'z';	//zipcode (or empty)
		MVCEditRule.kZIP  = 'Z';	//zipcode (required)
		MVCEditRule.kInt  = '8';	//0-9 (or empty)
		MVCEditRule.kINT  = '9';	//0-9 (required)
		MVCEditRule.kDec  = '.';	//decimal number (or empty)
		MVCEditRule.kDEC  = ':';	//decimal number (required)
		MVCEditRule.kAlfa = 'a';	//A-Za-z[space] (or empty)
		MVCEditRule.kALFA = 'A';	//A-Za-z[space] (required)
		MVCEditRule.kBool = 'y';	//Y,N,y,n,true,false (or empty)
		MVCEditRule.kBOOL = 'Y';	//Y,N,y,n,true,false (required)
		MVCEditRule.kDate = 'd';	//date (or empty)
		MVCEditRule.kDATE = 'D';	//date (required)
		MVCEditRule.kAlNo = ';';	//A-Za-z0-9 (or empty)
		MVCEditRule.kALNO = '|';	//A-Za-z0-9 (required)

		/** Static method to return whether specified edit rule is readonly.
		 * @param {char} rule rule to use
		 * @type boolean
		 */
		MVCEditRule.IsReadOnly = function(rule)
		{
			switch( rule )
			{
				case MVCEditRule.kZero:
				case MVCEditRule.kCopy:
				case MVCEditRule.kRO:
					return true;
			}
			return false;
		}

		/** Static method to validate value with STD edit rule.
		 * @param {MVCScalarModel} model model whose current value to validate
		 * @param {char} rule rule to apply
		 * @return null if valid else error msg
		 * @type String
		 * @throw error if unknown rule is specified
		 */
		MVCEditRule.Validate = function( model, rule )
		{
			var value = model.getValue();
			if (value && value.toString().indexOf("~")>=0)
				return "The tilde (~) character is always illegal.";
			
			//pre-check required fields
			switch( rule )
			{
				case MVCEditRule.kReqd:
				case MVCEditRule.kDATE:
				case MVCEditRule.kFONE:
				case MVCEditRule.kALNO:
				case MVCEditRule.kALFA:
				case MVCEditRule.kBOOL:
				case MVCEditRule.kINT:
				case MVCEditRule.kDEC:
				case MVCEditRule.kPOS:
				case MVCEditRule.kNEG:
				case MVCEditRule.kZIP:
					if (grvIsEmpty(value))
					  return "Value ["+value+"] must not be empty";
			}

			//we assume at this point that missing required values are handled
			switch( rule )
			{
				case MVCEditRule.kCopy:
					//no validation here--caller must handle copying value
				case MVCEditRule.kReqd:
				case MVCEditRule.kRO:
				case MVCEditRule.kEdit:
					//no extra validation at this point
					break;

				case MVCEditRule.kFONE:
				case MVCEditRule.kFone:
					if (grvIsPhoneNum(value)) break;
					return "Value ["+value+"] must be a phone number.";

				case MVCEditRule.kBOOL:
				case MVCEditRule.kBool:
					if (grvIsEmpty(value) || grvBoolValue(value)!=null) break;
					return "Value ["+value+"] must be 'Y/N' or 'true/false'.";

				case MVCEditRule.kDATE:
				case MVCEditRule.kDate:
					var errMsg = grvDateParseErrorMsg(value);
					if (errMsg==null) break;
					return "Value ["+value+"] must be a date..."+errMsg;

				case MVCEditRule.kZIP:
				case MVCEditRule.kZip:
					if (grvIsZipCode(value)) break;
					return "Value ["+value+"] must be a zipcode.";

				case MVCEditRule.kALNO:
				case MVCEditRule.kAlNo:
					if (grvIsAlphaNum(value)) break;
					return "Value ["+value+"] must contain only letters/digits.";

				case MVCEditRule.kALFA:
				case MVCEditRule.kAlfa:
					if (grvIsAlphaBlank(value)) break;
					return "Value ["+value+"] must contain only letters.";

				case MVCEditRule.kZero:
					//initial validation only--caller must handle setting value
					if (value==0) break;
					return "Value ["+value+"] must be Zero.";

				case MVCEditRule.kINT:
				case MVCEditRule.kInt:
					if (grvIsInteger(value)) break;
					return "Value ["+value+"] must be an integer.";

				case MVCEditRule.kDEC:
				case MVCEditRule.kDec:
					if (grvIsSignedFloat(value)) break;
					return "Value ["+value+"] must be a number.";

				case MVCEditRule.kPOS:
				case MVCEditRule.kPos:
					if (value>=0) break;
					return "Value ["+value+"] must be Positive.";

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

				case MVCEditRule.kNonZ:
					if (value!=0) break;
					return "Must select a known (non-zero) value.";

				default:
					grvLoudThrow("Unknown MVCEditRule:["+rule+"]");
			}
			
			//do this last! Give custom validation a shot
			if (model instanceof MVCAttributeModel) return model.validate();

			//no errors found after all tests
			return null;
		}
	}

	/** no-op constructor...not used */ 
	this.konstructor = function(){}
}

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

Class(MVCModel).Extends(MVCObserver);
/**
 * @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 MVCObserver} and {@link MVCObservable}.
 * This class implements the {@link MVCObservable} interface.
 * @extends MVCObserver
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.1
 */
function MVCModel()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName )
	{
		this.souper();

		// init instance variables
		if (optName) this.name = optName;
		this.hasChanged  = false; //only since last notifyObservers
		this.subscribers = new Array();
		this.inXaction   = false;
		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 + "; ");
		grvBreak(s);
	}

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

	/** set the "dirty" flag to true */
	this.dirty = function(){
		this.hasChanged = true;
		this.updateStamp();
	}

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

	/** tell observers that we have changed 
	 * @param {Object} optAdhocObj optional adhoc object to pass to observers
	 */
	this.publish = function( optAdhocObj )
	{
    	this.dirty();
    	if (!this.inXaction) 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(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()
	{
		grvASSERT(!this.inXaction,"already started transaction");
//		gMVCRootView.block();
		this.inXaction = true;
	}

	/** publish a "batch" of updates */
	this.EndTransaction = function()
	{
		grvASSERT(this.inXaction,"Not in transaction!");
		this.inXaction = false;
		this.publish();
//		gMVCRootView.unblock();
	}

	/** {@link MVCObservable} API */
	this.addObserver = function( observer )
	{
		grvValidateArgs("MVCModel.addObserver",["observer"],arguments);
    	this.subscribers.push( observer ); 
	}

	/** @deprecated @throws not implemented exception */
	this.delObserver = function( observer )
	{
		grvValidateArgs  ( "MVCModel.delObserver", ["observer"], arguments );
		grvNotImplemented( "MVCModel.delObserver" );
	    //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)
		{
			gMVCRootView.block();
			var N = this.subscribers.length;
			for (var i=0; i<N; ++i)
			{
//grvTraceMVC(this.name+"["+optAdhocObj+"] updates["+i+"] "+this.subscribers[i].name);
				this.subscribers[i].update( this, optAdhocObj );
			}
			gMVCRootView.unblock();
		}
		this.clean();
	}
}


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

		// init instance variables
		this._reset();
	}

	/** return a (deep) clone of "this" object. @type MVCListModel */
	this.clone = function()
	{
		var m = new MVCMap( this.name );
		this.iterate( function(K,o,i){ m.addItem(K,o); } );
		return m;
	}

	/** debug method to return this list as a string @type String */
	this.dump = function()
	{
		var s = this.name + "=ListModel==>";
		this.list.iterate(
		  function( i, ithItem ){ s += ((ithItem.dump?ithItem.dump():ithItem.toString()) + "; "); }
		);
		return s;
	}

	/** provide a "unique" number based on the state of this list
	 * NOTE: the quality of this hash code depends on the quality
	 * of the hash code provided by each list item.
	 * @deprecated
	 * @type int
	 */
	this.hash = function()
	{	//this is a Q&D poor excuse for a hash algorithm
		var N = this.list.length;
		var h = 0;
		for (var i=0; i<N; ++i)
			h += (this.list[i].hash() * (2*(i+1)));
		return h;
	}

	/** 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 index @type int */
	this._push      = function(o){ this.list.push(o); return this.getCount()-1; }

	/** delete given index from list @return deleted item @type Object */
	this._del       = function(i){ var o=this.list[i]; this.list.splice(i,1); return o; }

	/** clear list and update timestamp but dont publish */
	this._reset     = function( ){ this.list = new Array(); this.updateStamp(); return this; }

	/** clear list and publish */
	this.reset      = function( ){ this._reset(); this.publish(); return this; }

	/** 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 index @type int */
	this.addItem    = function(o){ var i=this._push(o); this.publish(); return i; }

	/** delete given index from list and publish @return deleted item @type Object */
	this.delItem    = function(i){ var o=this._del(i); this.publish(); return o; }

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

	/** return a memento of the current state of "this" @type Object */
	this.getMemento = function()
	{
		var m = new Array();
		for (var i=0; i<this.list.length; ++i) m[i] = this.list[i];
		return m;
	}

	/** update "this" based on the given memento
	 *  @param {Object} m the memento
	 */
	this.setMemento = function(m)
	{
		this._reset();
		for (var i=0; i<m.length; ++i) this.list[i] = m[i];
		this.publish();
	}
}


Class(MVCDequeModel).Extends(MVCListModel);
/**
 * @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 MVCListModel
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCDequeModel()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName ){
		this.souper( optName );
	}

	/** 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(MVCMapModel).Extends(MVCModel);
/**
 * @class This class encapsulates a data model for a Map of
 * object/key pairs. Its API is a wrapper for the {@link MVCMap} API.
 * This class implements the {@link MVCCollection} interface.
 * @extends MVCModel
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCMapModel()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName )
	{
		this.souper( optName );

		// init instance variables
		this.map = new MVCMap("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(); }
	this.iterate    = function(f)  { this.map.iterate(f); }
	
	/** debug method to return this list as a string @type String */
	this.dump = function() {
		return this.name + "=MapModel==>" + this.map.dump();
	}

	/** return a memento of the current state of "this" @type Object */
	this.getMemento = function(){ return this.map.getMemento(); }

	/** update "this" based on the given memento
	 *  @param {Object} m the memento
	 */
	this.setMemento = function(m){ this.map.setMemento(m); this.publish(); }
}


Class(MVCScalarModel).Extends(MVCModel);
/**
 * @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 MVCModel
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCScalarModel()
{
	// init "static" Class methods/elements
	if (grvIsUndefined(MVCScalarModel.kValiditySuffix)){//keep this line above jsdoc comments!

		/** The suffix added to the basic name to get the validity attribute name. */
		MVCScalarModel.kValiditySuffix = ".err";
	}

	/** @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.souper( optName );

		// 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+MVCScalarModel.kValiditySuffix, errMsg );
	}

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


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

	/** 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(MVCAttributeModel,["base object","attribute"]).Extends(MVCScalarModel);
/**
 * @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 MVCScalarModel
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCAttributeModel()
{
	// init "static" Class methods/elements
	if (grvIsUndefined(MVCAttributeModel.kValidateSuffix)){//keep this line above jsdoc comments!

		/** The suffix added to the basic name to get the validate function name. */
		MVCAttributeModel.kValidateSuffix = ".vfunc";
	}

	/** @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.souper( undefined, optName?optName:attribute );

		// 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+MVCScalarModel.kValiditySuffix ] = errMsg;
	}

	/** return the validity attribute of this attribute @type String */
	this.getValidity = function(){
		return this.base[ this.attribute+MVCScalarModel.kValiditySuffix ];
	}
	
	/** return error message from custom validation method or null for no error */
	this.validate = function()
	{
		if (this.base && this.base[ this.attribute+MVCAttributeModel.kValidateSuffix ])
		{
			 return this.base[ this.attribute+MVCAttributeModel.kValidateSuffix ]( this.attribute );
		}
		else return null;
	}
}


Class(MVCROAttributeModel,["base object","attribute"]).Extends(MVCAttributeModel);
/**
 * @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 MVCAttributeModel
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCROAttributeModel()
{
	/** @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.souper( baseObject, attribute, optName );

		// 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(){ grvError("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;
	}
}


Class(MVCSelectionModel,["Collection Model"]).Extends(MVCScalarModel);
/**
 * @class This class encapsulates the data model for a selector
 * which indicates the currently selected item in a specified
 * MVCCollection 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 MVCScalarModel
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCSelectionModel()
{
	// init "static" Class methods/elements
	if (grvIsUndefined(MVCSelectionModel.kNothingSelected)){

		// init static member variables
		MVCSelectionModel.kNothingSelected  = -1;
		MVCSelectionModel.kPropertyIdSelect = 'selected';
	}

	/** @param {Object} collModel Collection data model we select from
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function( collModel, optName )
	{
		this.souper( MVCSelectionModel.kPropertyIdSelect, optName );

		// init instance variables
		this._selectNothing();
		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 MVCCollection model */
	this.getCount = function( ){ return this.getList().getCount(); }

	/** return whether there is currently a selection @type boolean */
	this.hasSelection = function( ){ return this.getValue()!=MVCSelectionModel.kNothingSelected; }

	/** return the currently selected MVCCollection item or undefined if nothing selected */
	this.getSelection = function( ){ return this.getItem( this.getValue() ); }
	
	/** return the currently selected MVCCollection item formatted as string @type String */
	this.getSelectionStr = function( ){ return this.getSelection().toString(); }
	
	/**  return the specified MVCCollection item formatted as description @type String */
	this.getDescription  = function(i){
		var item = this.getItem(i);
		return item ? item.getDescription() : 'Unknown Value "'+i+'"';
	}

	/** select "nothing" but dont publish */
	this._selectNothing = function() { this._setValue( MVCSelectionModel.kNothingSelected ); }

	/** select "nothing" and publish */
	this.selectNothing = function(){
		this._selectNothing();
		this.publish();
	}

	/** select the specified Collection item but dont publish
	 * @param {Object} k key of item to select
	 * @param {boolean} optLenient optional flag to allow out of range to selectNothing
	 */
	this._select = function(k,optLenient)
	{
		if ( k!=0 && this.getIndex(k)<0 ) {
			if (!optLenient) grvError("BAD SelectionModel KEY["+k+"]");
			this._selectNothing();
		}
		else this._setValue(k);
	}

	/** select the specified MVCCollection item and publish
	 * @param {Object} k key of item to select
	 * @param {boolean} optLenient optional flag to allow out of range to selectNothing
	 */
	this.select	= function(k,optLenient){ this._select(k,optLenient); this.publish(); }

	/** 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();}
	
	/** re-select the current value and publish */
	this.reselect = function( ){ this.publish(); }
	
	/** handle MVCCollection model update event by "unselect"ing */
	this.update = function( ){
		if ( this.getList().hasChanged ) this.unselect();
//grvTraceEvt("UnSelectUpdate["+this+"]");
	}

	/** return the index into the MVCCollection model of the specified key
	 * (or the current selection if no key specified)
	 * @return -1 if not found
	 * @type int
	 */
	this.getIndex = function( optKey )
	{ 
		var index = -1;
		var goal  = optKey ? optKey : this.getValue();
		this.model.iterate(
		  function(key,o,i){ if (key==goal){ index = i; return true; } }
		);
		return index;
	}

	/** return a memento of the current state of "this" @type Object */
	this.getMemento = function()
	{
		var  m = new Object();
		m.list = this.getList().getMemento();
		m.val  = this.getValue();
		return m;
	}

	/** update "this" based on the given memento
	 *  @param {Object} m the memento
	 */
	this.setMemento = function(m)
	{
		this.getList().setMemento( m.list );
		this.select( m.val, true );
	}
}


Class(MVCUndoRedoModel).Extends(MVCDequeModel);
/**
 * @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 MVCCommand API.
 * @extends MVCDequeModel
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCUndoRedoModel()
{
	this.konstructor = function(){
		this.souper( "undo/redo commands" );
	}

	/** 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 */
gMVCUndoCmds = new MVCUndoRedoModel();

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

Class(MVCView);
/**
 * @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 MVCController} event causes some {@link MVCModel}
 * 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 an MVCContext subclass object
 *       containing the driver information for building this view.
 *</pre>
 * @extends GrvObject
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCView()
{
	// init "static" Class methods/elements
	if ( grvIsUndefined(MVCView.Depth) ) {//keep this line above jsdoc comments!

		/** Static method to draw the root view (and hence all views) */
		MVCView.DrawRoot = function() { gMVCRootView.draw(); }

		//init static member variables
		MVCView.Depth = 0;
	}

	/** @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 MVCMap( this.name+" subviews" );
		this.parentView       = null;
		this.embeddedSubViews = false;	//if true subviews are built via buildHTMLstr()
		this.lastBuildContext = null;
		this.setViewID( optViewID );
	}

	/** 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 MVCContext
	 */
	this.currentContext = function(){ return new MVCContext( 0 ); /*override me*/ }

	/** register the given model as our primary data model
	 * @param {MVCModel} 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 MVCView subclass can say what the "inner" ID
	 * is when there is a wrapper.
	 * @type String
	 */ 
	this.getWidgetID = function( optAttributeID )
	{
		var noWrapper = this.parentView && this.parentView.embeddedSubViews;
		var id        = noWrapper ? this.viewID : this.innerID();
		return id + (optAttributeID ? "."+optAttributeID : "");
	}

	/** 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 = grvGetHook( 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 = grvGetHook( 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 MVCContext} for this view */
	this.updateContext = function(){
		this.lastBuildContext = this.currentContext();
	}

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

	/** 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)
	{	//fix problem where tooltips quit working switching to WinXP
		grvSetElemDisplay( this.getWidget  (), this.visible=isVisible );//TRICKY
//		grvSetVisibility ( 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;
//grvBreak("draw ["+this.name+"] depth="+MVCView.Depth);
	    //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) grvTraceMVC(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;
//grvBreak("build ["+this.name+"] depth="+MVCView.Depth);
	    var rebuild = optForceRebuild || this.rebuildAny();
//grvTraceEvt("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;
//grvBreak("paint ["+this.name+"] depth="+MVCView.Depth);
		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 MVCView*/
	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 MVCView
	 */
	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();
	}

	/** same as {@link #embedHTML} but auto-creates viewID and saves reference
	 * to view based on specified "attribute" ID.
	 * @param {MVCView} view the attribute's subview to embed
	 * @param {String} attrID the identifier of the attribute
	 * @return HTML string
	 * @type String
	 */
	this.embedAttr = function( attrID, view )
	{
		var viewID = this.getWidgetID(attrID);
 	    this[attrID] = view; //squirrel away reference to view
		return this.embedView( viewID, view ).buildHTMLstr();
	}
	
	// NOTE: DONT! call grvBusy() 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(){
		++MVCView.Depth; //grvBreak("++depth="+MVCView.Depth);
	}

	/** API to block/unblock view updating (to stop redraw thrashing) */
	this.unblock = function(){
		if (--MVCView.Depth <= 0) { //grvBreak("--depth="+MVCView.Depth);
			  MVCView.Depth = 0;
			  MVCView.DrawRoot(); //grvBreak("finished rootdraw depth="+MVCView.Depth);
		}
	}
}

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

Class(MVCListView).Extends(MVCView);
/**
 * @class This class is a MVCView that expects to subscribe to a
 * {@link MVCListModel} 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 MVCListView should define/override:
 * (A) {@link #itemHTMLstr} which creates HTML for specified item
 * (B) {@link #itemPaint} which decorates HTML for specified item
 * and optionally...
 * (C) {@link #headHTMLstr} which creates HTML for a header item
 * (D) {@link #headPaint} which decorates HTML for a header item
 *</pre>
 * @extends MVCView
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCListView()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName )
	{
		this.souper( optName );

		//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 MVCListModel
	 * @param {Object} item the actual item from our MVCListModel
	 * @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 MVCListModel
	 * @param {Object} item the actual item from our MVCListModel
	 * @param {String} itemID the view ID of the corresponding item subview
	 */
	this.itemPaint   = function(index,item,itemID){ /* abstract */ }

	/** method that should return HTML string for the header item
	 * @type String
	 */
	this.headHTMLstr = function(){ /* abstract */ return ""; }

	/** method that should decorate HTML for the header item
	 */
	this.headPaint   = function(){ /* abstract */ }

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

	/** invoke {@link #itemPaint} for each item in our list */
	this.paintHTML   = function()
	{
		this.headPaint();
		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();
		HTML.push( this.headHTMLstr() );
		var listView = this;
		this.model.iterate(
		  function( i, ithItem ){
		  	HTML.push( 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(MVCScalarView,["Scalar Model"]).Extends(MVCView);
/**
 * @class This class produces a view of the specified 
 * {@link MVCScalarModel}. 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 MVCView
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCScalarView()
{
	/** @param {MVCScalarModel} 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.souper( optName );

		// 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 grvGenHook( this.getWidgetID() ); }
	this.paintHTML    = function( )
	{
		var visible = this.visible;
		grvSetElemVisibility( this.getWidget(), visible );
		if (visible) this.updateView();
	}
}


Class(MVCPadScalarView,["Scalar Model"]).Extends(MVCScalarView);
/**
 * @class This class produces a blank-padded view of the specified 
 * {@link MVCScalarModel}. It transforms the raw value
 * of the model into a right-blank-padded-to-min-len format.
 * @extends MVCScalarView
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCPadScalarView()
{
	/** @param {MVCScalarModel} xModel the data model to view
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xModel, minLen, optName )
	{
		this.souper( xModel, null, optName );
		this.minLen = minLen;
	}

	/** update the display with the current model value */
	this.updateView = function( ){ this.setDisplay(
			 grvFormatPaddedStr( this.getValueStr(),this.minLen ) ); }
}

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

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


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

		// init instance variables
		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
	 * <p>ABSTRACT: sub classes of MVCButtonController should override this</p>
	 */
	this.canonical = function( enableFlag )
	{ grvMustOverride("MVCButtonController.canonical"); }

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


Class(MVCFormButtonController,["button label", "descriptive text", "event handler"])
.Extends(MVCButtonController);
/**
 * @class This class is a Button Controller that manages a form button
 * @extends MVCButtonController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.5
 */
function MVCFormButtonController()
{
	/**
	 * @param {String} label label of the button
	 * @param {MVCBoolModel} optEnableModel optional "is enabled" data model
	 * @param {String} evtHandlerName name of event handler function
	 * @param {String} optName optional name of this instance
	 * @param {String} desc tooltip description for this button
	 */
	this.konstructor = function( label, desc, evtHandlerName, optEnableModel, optName )
	{
		this.souper( desc, evtHandlerName, optEnableModel, optName );

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

	/** 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 txtBtn = this.getWidget();
	  if ( enableFlag )
	  {
		txtBtn.onclick      = this.eventHandler;
	    txtBtn.title        = this.getAltText( enableFlag );
	    txtBtn.readonly     = false;
	    txtBtn.className    = 'mvcFormBtn';
	  }
	  else
	  {
	  	txtBtn.onclick      = null;
	    txtBtn.title        = this.getAltText( enableFlag );
	    txtBtn.readonly     = true;
	    txtBtn.className    = 'mvcFormBtn mvcFormBtnRO';
	  }
	}

	this.buildHTMLstr = function(){
		return "<input type='button' value='"+this.label+"' id='"+this.getWidgetID()+"'></input>";
	}
}

Class(MVCImgButtonController,["button image filename", "descriptive text", "event handler"])
.Extends(MVCButtonController);
/**
 * @class This class is a ButtonController that manages an image button
 * @extends MVCButtonController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.5
 */
function MVCImgButtonController()
{
	/**
	 * @param {MVCBoolModel} 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.souper( desc, evtHandlerName, optEnableModel, optName );

		// init instance variables
		this.imgFilename = imgFN;
	}

	/** 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          = kMVCImgPath + this.imgFilename;
		imgBtn.onclick      = this.eventHandler;
	    imgBtn.title        = this.getAltText( enableFlag );
	    imgBtn.style.cursor = "pointer";
	  }
	  else
	  {
	  	imgBtn.src          = kMVCImgPath + kMVCDim_ + this.imgFilename;
	  	imgBtn.onclick      = null;
	    imgBtn.title        = this.getAltText( enableFlag );
	  	imgBtn.style.cursor = "not-allowed";
	  }
	}

	this.buildHTMLstr = function(){
		return "<img align='middle' src='"+kMVCImgPath+this.imgFilename//kMVCImgSpacer
		        +"' id='"+this.getWidgetID()+"'>";
	}
}


Class(MVCCmdButtonController,["undo/redo flag"])
.Extends(MVCFormButtonController);
/**
 * @class This class is a Button Controller for "undo/redo" buttons.
 * @extends MVCFormButtonController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCCmdButtonController()
{
	/**
	 * @param {boolean} isUndo true if this is undo button else redo button
	 */
	this.konstructor = function( isUndo )
	{
		this.souper(
			isUndo?"Undo":"Redo", "foo",
			isUndo?"onMVCUndoBtnPressed":"onMVCRedoBtnPressed", null,
			isUndo?"undo":"redo"
		);
		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 ? gMVCUndoCmds.hasUnDo() : gMVCUndoCmds.hasReDo();
	}
}

Class(MVCCmdImgButtonController,["button image filename", "undo/redo flag"])
.Extends(MVCImgButtonController);
/**
 * @class This class is an Image Button Controller for "undo/redo" buttons.
 * @extends MVCImgButtonController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCCmdImgButtonController()
{
	/**
	 * @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.souper( imgFN, "foo",
			 isUndo?"onMVCUndoBtnPressed":"onMVCRedoBtnPressed", null,
			(isUndo?"undo":"redo")
		);
		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 ? gMVCUndoCmds.hasUnDo() : gMVCUndoCmds.hasReDo();
	}
}


Class(MVCTxtButtonController,["button text model", "descriptive text", "event handler"])
.Extends(MVCButtonController);
/**
 * @class This class is a Button Controller that manages a button that can
 * display text based on a data model's current value
 * @extends MVCButtonController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.5
 */
function MVCTxtButtonController()
{
	/**
	 * @param {MVCScalarModel} labelModel data model with button label text
	 * @param {MVCBoolModel} optEnableModel optional "is enabled" data model
	 * @param {String} evtHandlerName name of event handler function
	 * @param {String} optName optional name of this instance
	 * @param {String} desc tooltip description for this button
	 */
	this.konstructor = function( labelModel, desc, evtHandlerName, optEnableModel, optName )
	{
		this.souper( desc, evtHandlerName, optEnableModel, optName );

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

	/** 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 txtBtn = this.getWidget();
	  grvSetButtonElemText( txtBtn, this.labelModel.getValue() );
	  if ( enableFlag )
	  {
		txtBtn.onclick      = this.eventHandler;
	    txtBtn.title        = this.getAltText( enableFlag );
	    txtBtn.style.cursor = "pointer";
	    txtBtn.disabled     = false;
	  }
	  else
	  {
	  	txtBtn.onclick      = null;
	    txtBtn.title        = this.getAltText( enableFlag );
	  	txtBtn.style.cursor = "not-allowed";
	    txtBtn.disabled     = true;
	  }
	}

	this.buildHTMLstr = function(){
		return "<button class='mvcFormBtn' type='button' id='"+this.getWidgetID()+"'></button>";
	}
}


Class(MVCScalarEditController,["Scalar Model"]).Extends(MVCController);
/**
 * @class This abstract class manages an editor of the specified
 * {@link MVCScalarModel}. This controller edits a single scalar value.
 * @extends MVCController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.5
 */
function MVCScalarEditController()
{
	/**
	 * @param {MVCScalarModel} sModel scalar data model to edit
	 * @param {String} optEvtHndlrName optional name of event handler function
	 * @param {Function} optPostEdit optional post edit value cleanup function
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( sModel, optName, optEvtHndlrName, optPostEdit )
	{
		this.souper( optName );

		// init instance variables
		this.watchModel( sModel );
		this.evtHndlrName = optEvtHndlrName ? optEvtHndlrName : "onMVCScalarEditUpdate";
		this.maxchars     = -1;//negative means no limit
		this.allowCR      = false;//default is "CR means accept value"
		this.postEditor   = optPostEdit;
	}

	/** return the current (post-edit) value of the controller widget */
	this.postEdit = function()
	{//alert("in "+this.name+".postEdit()");
		var theVal = this.getCtrlValue();
		if (this.postEditor) theVal = this.postEditor(theVal);
		else if (theVal instanceof String)
				theVal = grvTrimStr( theVal );//default policy
		return theVal;
	}

	/** 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.getEvtHandlerStr = function(){ return this.evtHndlrName+"(event,'"+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 MVCDualController}) 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( ){
		grvSetElemVisibility( this.getWidget(), this.visible );
		if (this.visible) this.updateView();
	}
}


Class(MVCPopupMenuController,["selection model"]).Extends(MVCScalarEditController);
/**
 * @class This class manages a popup menu which watches
 * a {@link MVCListModel} (specifying the menu items)
 * embedded within a {@link MVCSelectionModel} that reflects
 * which item is/should-be currently selected. [The scalar value
 * we "edit" is the "select index" of the MVCSelectionModel.]
 * Each item in the list should implement the {@link MVCMenuItem} interface.
 * @extends MVCScalarEditController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.5
 */
function MVCPopupMenuController()
{
	/**
	 * @param {MVCSelectionModel} sModel selection data model to control
	 * @param {String} optEvtHndlrName optional name of event handler function
	 * @param {String} optName optional name of this instance
	 * @param {String} optClassName optional CSS classname to use for formatting
	 */
	this.konstructor = function( sModel, optName, optEvtHndlrName, optClassName ){
	  this.souper( sModel, optName, optEvtHndlrName );
	  this.className = optClassName ? optClassName : "mvcPopupMenu";
	}

	this.currentContext = function(){
		return new MVCContext( this.model.model.timestamp );
	}

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

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

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


Class(MVCFieldEditController,["Scalar Model","min chars","max chars"])
.Extends(MVCScalarEditController);
/**
 * @class This class manages a form field editor of the
 * specified {@link MVCScalarModel}.
 * @extends MVCScalarEditController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.5
 */
function MVCFieldEditController()
{
	/**
	 * @param {MVCScalarModel} xModel scalar data model to edit
	 * @param {int} min minimum number of chars to support (i.e. display size)
	 * @param {int} max maximum number of chars to support
	 * @param {String} optClassName optional CSS classname to use for formatting
	 * @param {Function} optKeyFilter optional keypress filter function
	 * @param {String} optEvtHndlrName optional name of event handler function
	 * @param {Function} optPostEdit optional post edit value cleanup function
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xModel, min, max, optClassName, 
								optKeyFilter, optName, optEvtHndlrName, optPostEdit )
	{
		this.souper( xModel, optName, optEvtHndlrName, optPostEdit );

		// init instance variables
		this.minchars  = min;
		this.maxchars  = max;
		this.className = optClassName ? optClassName : "mvcEntryField";
		this.keyFilter = optKeyFilter;
	}

	/** Produce HTML version of a scalar editor.<p>
	 * ala {input class='className' name="paylg1" size="min"
	 *             maxlength="max" value="123456789.01" onchange="dirty('1');"
	 *             onblur="validateDollar('paylg1')"/}
	 */
	this.buildHTMLstr = function()
	{
		var keybrdEvtHandlerStr = "onMVCFieldEditKey  (event,'"+this.viewID+"')";
		var  focusEvtHandlerStr = "onMVCFieldEditFocus(event,this)";
		return '<input onfocus="'+focusEvtHandlerStr
		+'" onblur="return ' + this.getEvtHandlerStr()
		+'" onkeypress="return '+keybrdEvtHandlerStr
		+'" name="'     +this.getWidgetID()
		+'" id="'       +this.getWidgetID()
		+'" class="'    +this.className
		+'" size="'     +this.minchars
		+'" maxlength="'+this.maxchars
		+'"/>';
	}
}


Class(MVCDateEditController,["Date String Scalar Model","css classname"])
.Extends(MVCScalarEditController);
/**
 * @class This class manages a Date form field editor of the
 * specified {@link MVCScalarModel}.
 * @extends MVCScalarEditController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.5
 */
function MVCDateEditController()
{
	// init "static" Class methods/elements
	if (grvIsUndefined(MVCDateEditController.LaunchCalendar)){//keep this line above jsdoc comments!

		/** Static method to launch a calendar window with given ID
		 * @param {String} HTML ID of target form element
		 */
		MVCDateEditController.LaunchCalendar = function( fieldName ) {
			show_calendar( fieldName, "getElementById(self.opener.gVID)" );
		}
	}

	/**
	 * @param {MVCScalarModel} xModel scalar data model to edit
	 * @param {String} className the CSS classname to use for formatting
	 * @param {String} optEvtHndlrName optional name of event handler function
	 * @param {Function} optPostEdit optional post edit value cleanup function
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xModel, className, 
								optName, optEvtHndlrName, optPostEdit )
	{
		this.souper( xModel, optName, optEvtHndlrName, optPostEdit );

		// init instance variables
		this.className = className;
		this.keyFilter = grvDateKeyFilter;
	}

	this.calendarID = function(){ return this.getWidgetID()+".cal"; }

	/** TODO fix bug where being made invisible then visible again
	 * leaves calendar icon still invisible...probably need to make
	 * calendar its own Controller
	 */
	this.paintHTML = function( ){
		grvSetElemVisibility( this.getWidget(), this.visible );
		grvSetVisibility( this.calendarID(), this.visible);
		if (this.visible) this.updateView();
	}

	/** Produce HTML version of a Data string scalar editor. */
	this.buildHTMLstr = function()
	{
		var fieldName           = this.getWidgetID();
		var keybrdEvtHandlerStr = "onMVCFieldEditKey  (event,'"+this.viewID+"')";
		var  focusEvtHandlerStr = "onMVCFieldEditFocus(event,this)";
		var calHTML             = '<img alt="Launch calendar window" id="'
			+this.calendarID()+'" src="'
			+kMVCImgPath+kMVCImgCalendar
			+'" onclick="MVCDateEditController.LaunchCalendar(\''+fieldName+'\')" border="0"/>';

		return calHTML
		+'<input onfocus="'     +   focusEvtHandlerStr
		+'" onblur="return '    +this.getEvtHandlerStr()
		+'" onkeypress="return '+  keybrdEvtHandlerStr
		+'" name="' +fieldName
		+'" id="'   +fieldName
		+'" class="'+this.className
		+'" size="10" maxlength="10"/>';
	}
}


Class(MVCTextEditController,["Scalar Model","rows","cols","max chars","css classname"])
.Extends(MVCScalarEditController);
/**
 * @class This class manages a form field "textarea" editor of the
 * specified {@link MVCScalarModel}.
 * @extends MVCScalarEditController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.5
 */
function MVCTextEditController()
{
	/**
	 * @param {MVCScalarModel} xModel scalar data model to edit
	 * @param {int} rows number of rows to support
	 * @param {int} cols number of cols to support
	 * @param {int} max maximum number of chars to support
	 * @param {String} className the CSS classname to use for formatting
	 * @param {Function} optKeyFilter optional keypress filter function
	 * @param {String} optEvtHndlrName optional name of event handler function
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xModel, rows, cols, max, className, 
								optKeyFilter, optName, optEvtHndlrName )
	{
		this.souper( xModel, optName, optEvtHndlrName );

		// init instance variables
		this.rows      = rows;
		this.cols      = cols;
		this.maxchars  = max;
		this.allowCR   = true;
		this.className = className;
		this.keyFilter = optKeyFilter;
	}

	/** Produce HTML version of a textarea editor.<p>
	 * ala {textarea NAME="comments" cols=40 rows=6}{/textarea}
	 */
	this.buildHTMLstr = function()
	{
		var keybrdEvtHandlerStr = "onMVCFieldEditKey  (event,'"+this.viewID+"')";
		var  focusEvtHandlerStr = "onMVCFieldEditFocus(event,this)";
		return '<textarea onfocus="'+focusEvtHandlerStr
		+'" onblur="return '     +this.getEvtHandlerStr()
		+'" onkeypress="return ' +  keybrdEvtHandlerStr
		+'" name="' +this.getWidgetID()
		+'" id="'   +this.getWidgetID()
		+'" class="'+this.className
		+'" rows="' +this.rows
		+'" cols="' +this.cols
		+'"/></textarea>';
	}
}


Class(MVCChkBoxEditController,["Scalar Model","css classname"])
.Extends(MVCScalarEditController);
/**
 * @class This class manages a form field "checkbox" editor of the
 * specified {@link MVCScalarModel}.
 * @extends MVCScalarEditController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCChkBoxEditController()
{
	/**
	 * @param {MVCScalarModel} xModel scalar data model to edit
	 * @param {String} className the CSS classname to use for formatting
	 * @param {String} optEvtHndlrName optional name of event handler function
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xModel, className, optName, optEvtHndlrName )
	{
		this.souper( xModel, optName, optEvtHndlrName, null );

		// init instance variables
		this.className = className;
	}
	
	/** set our controller widget to the given value */
	this.setCtrlValue  = function(x){        this.getWidget().checked = grvBoolValue(x); }

	/** return the current value of the controller widget */
	this.getCtrlValue  = function( ){ return this.getWidget().checked;                   }

	/** Produce HTML version of a checkbox editor.<p>
	 * ala {INPUT type="checkbox" name="ACTIVE_ONLY" value="Y" readonly="readonly"/}
	 */
	this.buildHTMLstr = function()
	{
		//hack to fix safari/chrome onblur bug: send onchange events to the same handler
		return '<input type="checkbox"'
		+'" onchange="return '+this.getEvtHandlerStr()
		+'" onblur="return '  +this.getEvtHandlerStr()
		+'" name="' +this.getWidgetID()
		+'" id="'   +this.getWidgetID()
		+'" class="'+this.className
		+'"/>';
	}
}


Class(MVCBoolEditController,["Bool Model","css classname"])
.Extends(MVCScalarEditController);
/**
 * @class This class manages a scalar editor with a set of yes,no,unknown
 * radio buttons attached to the specified Boolean (i.e. can be null)
 * {@link MVCBoolModel}.
 * @extends MVCScalarEditController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCBoolEditController()
{
	/**
	 * @param {MVCBoolModel} xModel scalar data model to edit
	 * @param {String} className the CSS classname to use for formatting
	 * @param {String} optEvtHndlrName optional name of event handler function
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xModel, className, optName, optEvtHndlrName )
	{
		this.souper( xModel, optName, optEvtHndlrName, null );

		// init instance variables
		this.className = className;
		this.visible   = false;//HACK!!
		this.lastviz   = true;//HACK!!!
	}

	//BUG IN Internet Exploder causes radio buttons to not display
	//on being made visible after being invisible...
	//SO, rebuild rather than repaint HACK!!
	this.mustRebuild  = function()
	{
		var becameVisible = this.visible && !this.lastviz;
//if (becameVisible) grvBreak("must? rebuild= "+becameVisible+" me="+itsMe+" v="+this.visible+" l="+this.lastviz);
		this.lastviz = this.visible;
		return becameVisible;
	}

	this.btnID        = function(){ return this.getWidgetID() + ".rb"; }
	this.getButtons   = function(){ return document.getElementsByName( this.btnID() ); }

	this.setCtrlValue = function(x)
	{
		var buttons = this.getButtons();
		x = x ? x : ""; //null is represented by empty string
		for (var i=0; i<buttons.length; ++i)
		  buttons[i].checked = (x==buttons[i].value);
	}

	this.getCtrlValue = function()
	{
		var buttons = this.getButtons();
		for (var i=0; i<buttons.length; ++i)
		  if (buttons[i].checked) return buttons[i].value;
		return null;
	}

	this.radBtnHTMLstr = function( name, evtHndlrStr, code, desc, optChecked )
	{
		return '<input type="radio" value="'+code+'"'
		+'" onclick="return '+evtHndlrStr
		+'" NAME="'+name
		+'" class="'+this.className
		+'" STYLE="background-color:transparent"'
		+ (optChecked ? ' checked="checked"' : '')
		+'/>'+desc+'&nbsp;&nbsp;<br/>&nbsp;';
	}

	this.buildHTMLstr = function()
	{
		var wid  = this.getWidgetID();
		var name = this.btnID();
		var ehs  = this.getEvtHandlerStr();
		return grvGenHook( wid,
		       this.radBtnHTMLstr( name, ehs, 'Y', 'Yes' )
			 + this.radBtnHTMLstr( name, ehs, 'N', 'No'  )
			 + this.radBtnHTMLstr( name, ehs,  '', 'Unknown' )
			 			);
	}

	this.paintHTML = function()
	{
		var widget = this.getWidget();
		grvSetElemVisibility( widget, this.visible );
		//BUG IN Internet Exploder causes radio buttons to not display
		//on being made visible after being invisible...BTW, putting
		//an alert() here to debug it, makes the problem stop <sigh>
		if (this.visible) this.updateView();
		var buttons = this.getButtons();
		for (var i=0; i<buttons.length; ++i)
		  grvSetElemVisibility( buttons[i], this.visible );
	}
}


Class(MVCDollarEditController,["Scalar Model"]).Extends(MVCFieldEditController);
/**
 * @class This class manages an editor of the specified Dollar
 * {@link MVCScalarModel}.
 * @extends MVCFieldEditController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCDollarEditController()
{
	/**
	 * @param {MVCScalarModel} xModel scalar data model to edit
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xModel, optName ){
		this.souper( xModel, 15, 20, "mvcEntryfield", grvDollarKeyFilter, optName );
	}

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

	this.getCtrlValue = function(){ 
		var s = grvDollarStrFilter( this.getWidget().value );
		if (       grvIsEmpty(s)) return 0;
//		if (!grvIsSignedFloat(s)) return 0;
		return  parseFloat(s);
	}
}


Class(MVCDualController,["scalar model","css classname"]).Extends(MVCController);
/**
 * @class This abstract class manages dual views (viewer and editor)
 * of a {@link MVCScalarModel}.
 * @extends MVCController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCDualController()
{
	/**
	 * @param {MVCScalarModel} 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.souper( optName );

		// init instance variables
		this.watchModel( sModel );
		this.editing   = false;
		this.viewer    = null;
		this.editor    = null;
		this.className = className;
		this.mode      = MVCEditRule.kEdit;
	}
	
	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 )
	{
		this.mode = mode;
		if (mode==MVCEditRule.kZero) this.setModels( 0 );
		this.enableEdit( inEdit && !MVCEditRule.IsReadOnly(mode) );
	}

	/** validate data and return null if valid else error msg @type String */
	this.validator = function() {
		return MVCEditRule.Validate( this.model, this.mode );
	}

	/** 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){ grvBreak("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 MVCPopupMenuController))
		  	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 MVCPopupMenuController))
		  	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(MVCDualChkBoxController,["Scalar Model","min chars", "css classname"])
.Extends(MVCDualController);
/**
 * @class This class manages dual views (viewer and editor) of the
 * specified (boolean) Scalar Model.
 * @extends MVCDualController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCDualChkBoxController()
{
	/**
	 * @param {MVCScalarModel} xModel scalar data model to view/edit
	 * @param {int} min minimum number of chars to support (i.e. display size)
	 * @param {String} className the CSS classname to use for formatting
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xModel, min, className, optName )
	{
		this.souper( xModel, className, optName );

		// init instance variables
		this.viewer = new MVCPadScalarView( xModel, min );
		this.editor = new MVCChkBoxEditController( xModel, className );
	}
}


Class(MVCDualBoolController,["Bool Model","min chars", "css classname"])
.Extends(MVCDualController);
/**
 * @class This class manages dual views (viewer and editor) of the
 * specified (Boolean i.e. can be null) Scalar Model.
 * @extends MVCDualController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCDualBoolController()
{
	/**
	 * @param {MVCBoolModel} xModel scalar data model to view/edit
	 * @param {int} min minimum number of chars to support (i.e. display size)
	 * @param {String} className the CSS classname to use for formatting
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xModel, min, className, optName )
	{
		this.souper( xModel, className, optName );

		// init instance variables
		this.viewer = new MVCPadScalarView( xModel, min );
		this.editor = new MVCBoolEditController( xModel, className );
	}
}


Class(MVCDualDateController,["Date String Model", "css classname"])
.Extends(MVCDualController);
/**
 * @class This class manages dual views (viewer and editor) of the
 * specified Date Scalar Model.
 * @extends MVCDualController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCDualDateController()
{
	/**
	 * @param {MVCScalarModel} 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
	 */
	this.konstructor = function( xModel, className, optName )
	{
		this.souper( xModel, className, optName );

		// init instance variables
		this.viewer = new MVCScalarView( xModel );
		this.editor = new MVCDateEditController( xModel, className );
	}
}


Class(MVCDualStringController,["Scalar Model","min chars", "max chars", "css classname"])
.Extends(MVCDualController);
/**
 * @class This class manages dual views (viewer and editor) of the
 * specified String Scalar Model.
 * @extends MVCDualController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCDualStringController()
{
	/**
	 * @param {MVCScalarModel} xModel scalar data model to view/edit
	 * @param {int} min minimum number of chars to support (i.e. display size)
	 * @param {int} max maximum number of chars to support
	 * @param {String} className the CSS classname to use for formatting
	 * @param {String} optName optional name of this instance
	 * @param {Function} optKeyFilter optional keypress filter function
	 */
	this.konstructor = function( xModel, min, max, className, optName, optKeyFilter )
	{
		this.souper( xModel, className, optName );

		// init instance variables
		this.viewer = new MVCPadScalarView( xModel, min );
		this.editor = new MVCFieldEditController( xModel,min,max,className,optKeyFilter );
	}
}

Class(MVCDualTextController,["Scalar Model","rows","cols","max chars","css classname"])
.Extends(MVCDualController);
/**
 * @class This class manages dual views (viewer and editor) of the
 * specified "Text" Scalar Model.
 * @extends MVCDualController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCDualTextController()
{
	/**
	 * @param {MVCScalarModel} xModel scalar data model to view/edit
	 * @param {int} rows number of rows to support
	 * @param {int} cols number of cols to support
	 * @param {int} max maximum number of chars to support
	 * @param {String} className the CSS classname to use for formatting
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xModel, rows, cols, max, className, optName )
	{
		this.souper( xModel, className, optName );

		// init instance variables
		this.viewer = new MVCScalarView( xModel, grvFormatMultilineStr );
		this.editor = new MVCTextEditController( xModel,rows,cols,max,className );
	}
}

Class(MVCDualDollarController,["Scalar Model","css classname"])
.Extends(MVCDualController);
/**
 * @class This class manages dual views (viewer and editor) of the
 * specified Dollar Scalar Model.
 * @extends MVCDualController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCDualDollarController()
{
	/**
	 * @param {MVCScalarModel} 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.souper( xModel, className, optName );

		// init instance variables
		var formatter = grvIsUndefined( optFormatFunction )
		              ? grvFormatDollarNot
		              : optFormatFunction;
		this.viewer = new MVCScalarView( this.model, formatter );
		this.editor = new MVCDollarEditController( this.model );
	}

	this.validator = function() {
		if (this.mode==MVCEditRule.kZero) this.setModels( 0 );//@todo is this needed??
		return MVCEditRule.Validate( this.model, this.mode );
	}
}


Class(MVCDualMenuController,["Menu Selection Model","scalar model","css classname"])
.Extends(MVCDualController);
/**
 * @class This class manages dual views (viewer and popup menu) of the
 * specified Models.
 * @extends MVCDualController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCDualMenuController()
{
	/**
	 * @param {MVCSelectionModel} mModel selection data model for menu
	 * @param {MVCScalarModel} 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} optEvtHndlrName optional name of menu event handler function
	 * @param {String} optInitDesc optional initial description string
	 */
	this.konstructor = function( mModel, sModel, className, optName, optEvtHndlrName, optInitDesc )
	{
		this.souper( sModel, className, null, optName );

		// init instance variables
		this.initialVal  = sModel.getValue();
		this.initialDesc = mModel.getDescription( sModel.getValue() );
		if (optInitDesc && !mModel.loaded) this.initialDesc = optInitDesc;

		//logic changed to suit selection models that are not loaded yet
		this.viewer = new MVCScalarView( sModel,
			/*formatter method (of MVCScalarView)*/ function( value )
			{ 
				var s = (value==this.parentView.initialVal)
				      ? this.parentView.initialDesc
				      : this.parentView.editor.model.getDescription(value)
			
				return (gGrvTraceTODO ? value+":" : "") + s;
			}
		);

		this.editor = new MVCPopupMenuController( mModel, "menu for "+optName, optEvtHndlrName, className );

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

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


////////////////////////////////////////////
//////////////// Commands //////////////////
////////////////////////////////////////////

// ----------------------------------------------------------------------------
// Utility routines for making AJAX requests
// ----------------------------------------------------------------------------

Class(MVCAJAXReplyCmd,["xml request object","xsl DOM"]).Extends(MVCCommand);
/**
 * @class This class implements the "process AJAX reply" command.
 * NOTE: It isnt undoable (because we lose all edits!).
 * @extends MVCCommand
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCAJAXReplyCmd()
{
	/** @param {grvXMLreq} xmlReq  the XML request being replied to
	 *  @param {ActiveXObject} xslDOM  xsl DOM doc to transform reply XML
	 *  @param {String} optName  optional name of this instance
	 */
	this.konstructor = function( xmlReq, xslDOM, optName )
	{
		this.souper( optName );
		this.canUndo = false;

		//translate received XML into javascript (via XSL) and save
		try { this.script = xmlReq.xform2( xslDOM ); }
		catch(e) { this.state = -1; }
	}

	this.doit = function() {
		if (gGrvTraceEvt) grvDebugWindow( this.script );
		eval( this.script );
	}
}


Class(MVCScalarEditCmd,["view ID"]).Extends(MVCCommand);
/**
 * @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 MVCCommand
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function MVCScalarEditCmd()
{
	/** @param {String} viewID ID of controller generating this command */
	this.konstructor = function( viewID )
	{
		this.souper("edit");

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

//			this.newValue = this.editCtrl.getCtrlValue();
			this.oldValue = this.editCtrl.getModelValue();
			this.newValue = this.editCtrl.postEdit();

			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; grvError("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
								     +  "'";
							  }
}

////////////////////////////////////////////
////////// Static Event Handlers ///////////
////////////////////////////////////////////

/** undo-button-pressed event handler
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onMVCUndoBtnPressed(){ gMVCUndoCmds.unDo(); }

/** redo-button-pressed event handler
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onMVCRedoBtnPressed(){ gMVCUndoCmds.reDo(); }

/** pre-screen all keypress events for entire page
 * This implementation handles ctrl-z and ctrl-y
 * to invoke Undo and Redo respectively.
 * @param {event} e browser event
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.5
 */
function onMVCGlobalKeyPress(e)
{
	e = e || window.event;
	var code = e.keyCode || e.which;
    if ( code==26 || code==122 && e.ctrlKey) { onMVCUndoBtnPressed(); return true; }
    if ( code==25 || code==121 && e.ctrlKey) { onMVCRedoBtnPressed(); return true; }
}

/** ScalarEditController update-event handler
 * @param {event} e browser event
 * @param {String} viewID view ID of the {@link MVCScalarEditController}
 * generating this event.
 * @return event success?? flag
 * @type boolean
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.5
 */
function onMVCScalarEditUpdate( e, viewID )
{
	mvcDoCmd( new MVCScalarEditCmd( viewID ) );
	return true;
}

/** handle "key pressed" events in text fields
 * @param {event} e browser event
 * @param {String} viewID view ID of the {@link MVCFieldEditController}
 * generating this event.
 * @return false if this key should be suppressed
 * @type boolean
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.5
 */
function onMVCFieldEditKey( e, viewID )
{
	e = e || window.event;
	var code = e.keyCode || e.which;
	var targ = e.target  || e.srcElement;

	if (targ.nodeType == 3) // defeat Safari bug
		targ = targ.parentNode;
	// browser handles ctrl-z ["undo"] in a text field.

	var SEC = gMVCRootView.getSubView( viewID );

	// If "enter" key pressed, cause update event [indirectly via blur]
	var isEnter = (code==13 || code==3);
	if (isEnter && !SEC.allowCR) {
		targ.blur();
		targ.select();
		return false;
	}

	// otherwise validate value length
	// (and key if user specified a keyFilter).
	var currentValue = SEC.getWidget().value;
	if (SEC.maxchars>=0 && currentValue.length>SEC.maxchars)
		return false;

	// keyFilters should return the KEYCODE of the canonical new char
	// or null if the current char was not legal
	if (SEC.keyFilter)
	{
		code = SEC.keyFilter( code, currentValue );
		if (code!=null)
		{
			 if (e.keyCode) e.keyCode = code; else e.which = code;
			 return true;
		}
		else return false;
	}

	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 {event} e browser event
 * @param {Element} field HTML element of text field generating this event
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.5
 */
function onMVCFieldEditFocus( e, field )
{
//	field.focus();
//	field.blur();
	field.select();
}

////////////////////////////////////////////
//////////// Utility Functions /////////////
////////////////////////////////////////////

/**
 * Create and embed, as a subview, a {@link MVCScalarView} of the specified
 * data model attribute.
 * @param {MVCView} 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
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function mvcEmbedAttributeViewer( parentView, attribute, optFormatter, optParam )
{
	var model = new MVCROAttributeModel( parentView.model, attribute, optParam );
 	var view  = new MVCScalarView( model, optFormatter );
 	return parentView.embedAttr( attribute, view );
}

/**
 * Create and embed, as a subview, a {@link MVCDualDollarController} of
 * the specified data model attribute which is expected to be a dollar
 * amount data element.
 * @param {MVCView} 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
 * @param {Function} optFormatFunction optional formatter function
 * @return the HTML string of the parentView (that includes the new embedded subviews)
 * @type String
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function mvcEmbedDollarDualEditor( parentView, attribute, className, optFormatFunction )
{
	var model = new MVCAttributeModel( parentView.model, attribute );
 	var view  = new MVCDualDollarController( model, className, optFormatFunction );
 	return parentView.embedAttr( attribute, view );
}

/**
 * Create and embed, as a subview, a {@link MVCDualChkBoxController} of
 * the specified data model attribute which is expected to be a (boolean)
 * data element.
 * @param {MVCView} 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 {int} min minimum number of chars to support (i.e. display size)
 * @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
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function mvcEmbedChkBoxDualEditor( parentView, attribute, min, className )
{
	var model = new MVCAttributeModel( parentView.model, attribute );
 	var view  = new MVCDualChkBoxController( model, min, className );
 	return parentView.embedAttr( attribute, view );
}

/**
 * Create and embed, as a subview, a {@link MVCDualBoolController} of
 * the specified data model attribute which is expected to be a (boolean)
 * data element.
 * @param {MVCView} 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 {int} min minimum number of chars to support (i.e. display size)
 * @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
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function mvcEmbedBoolDualEditor( parentView, attribute, min, className )
{
	var model = new MVCAttributeModel( parentView.model, attribute );
 	var view  = new MVCDualBoolController( model, min, className );
 	return parentView.embedAttr( attribute, view );
}

/**
 * Create and embed, as a subview, a {@link MVCDualStringController} of
 * the specified data model attribute which is expected to be a String
 * data element.
 * @param {MVCView} 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 {int} min minimum number of chars to support (i.e. display size)
 * @param {int} max maximum number of chars to support
 * @param {String} className the CSS classname to use for formatting
 * @param {Function} optKeyFilter optional keypress filter function
 * @return the HTML string of the parentView (that includes the new embedded subviews)
 * @type String
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function mvcEmbedStringDualEditor( parentView, attribute, min, max, className, optKeyFilter )
{
	var model = new MVCAttributeModel( parentView.model, attribute );
 	var view  = new MVCDualStringController( model, min, max, className, "string dual", optKeyFilter );
 	return parentView.embedAttr( attribute, view );
}

/**
 * Create and embed, as a subview, a {@link MVCDualTextController} of
 * the specified data model attribute which is expected to be a String
 * data element.
 * @param {MVCView} 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 {int} rows number of rows to support
 * @param {int} cols number of cols to support
 * @param {int} max maximum number of chars to support
 * @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
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function mvcEmbedTextDualEditor( parentView, attribute, rows, cols, max, className )
{
	var model = new MVCAttributeModel( parentView.model, attribute );
 	var view  = new MVCDualTextController( model, rows, cols, max, className );
 	return parentView.embedAttr( attribute, view );
}

/**
 * Create and embed, as a subview, a {@link MVCDualDateController} of
 * the specified data model attribute which is expected to be a String
 * data element containing a Date.
 * @param {MVCView} 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
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function mvcEmbedDateDualEditor( parentView, attribute, className )
{
	var model = new MVCAttributeModel( parentView.model, attribute );
 	var view  = new MVCDualDateController( model, className, "date dual" );
 	return parentView.embedAttr( attribute, view );
}

/**
 * Create and embed, as a subview, a {@link MVCDualMenuController} of
 * the specified data model attribute
 * @param {MVCView} 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 {MVCSelectionModel} menuModel data model for the popup menu
 * @param {String} className the CSS classname to use for formatting
 * @param {String} optEvtHndlrName optional edit event handler (default
 *                   is to launch a {@link MVCScalarEditCmd} command).
 * @param {String} optInitDesc optional initial description string
 * @return the HTML string of the parentView (that includes the new embedded subviews)
 * @type String
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function mvcEmbedDualMenu( parentView, attribute, menuModel, className, optEvtHndlrName, optInitDesc )
{
	var model = new MVCAttributeModel( parentView.model, attribute );
 	var view  = new MVCDualMenuController(menuModel,model,className,"menu dual",optEvtHndlrName,optInitDesc);
 	return parentView.embedAttr( attribute, view );
}

grvTraceCmp("grvMVC.js: End");

The Gravey 2.5 Framework

Documentation generated by JSDoc on Thu Jan 6 12:47:09 2011