The Gravey 2.0 Framework

grvEDO.js

Summary

Collection of Editable Domain Object framework classes. This library builds on the MVC framework and adds support for EDOs (Editable Domain Objects) including undo-able select/edit/delete/insert operations.

Version: 2.0

Requires:

Author: Bruce Wallace (PolyGlotInc.com)


Class Summary
EDO This class defines a framework for Editable Domain Objects.

Note that subclasses MUST override the clone() method.

EDOAppPanelView This class produces the view of the entire application page.
EDOButtonController This class is a button controller for EDO "edit" buttons which shouldn't be enabled while app is in "read only" mode.
EDOCmd This is the abstract base class for EDO commands.
EDOControlPanelView This class produces the view of the top control panel.
EDOCountersView This class produces a view of the current selection and total size of a selection model (ie "I of N").
EDOCreateCmd This class implements the append and insert EDO cmds.
EDODeleteCmd This class implements the delete EDO cmd.
EDOEditCmd This class implements the edit EDO cmd.
EDOHolderCancelAllChangesCmd This class implements the "revert" command.
EDOHolderCloseWindowIfSavedCmd This class implements the "close this window if all EDOs in the global EDOHolder are saved" command.
EDOHolderLoadReplyCmd This Command processes the reply of the "load EDOHolder data" request.
EDOHolderModel This abstract class encapsulates the functionality of a data model managing a set of editable domain objects (EDOs).
EDOHolderSaveAllChangesCmd This class implements the "save all changes made to EDOs in the global EDOHolder" command.
EDOHolderSelectCmd This class implements the "select new EDO Holder" command.
EDOHolderSelectInitialCmd This class implements the "select initially specified EDOHolder" command.
EDOHolderSelectionItem This class defines the interface expected for items appearing in the list model watched by EDOHolderSelectionModel.
EDOHolderSelectionModel This class encapsulates the data model for "which EDOHolder is currently selected" from list of EDOHolderSelectionItem.
EDOHolderSelectMenuController This class manages a popup menu controller for the Selection List that chooses the current EDOHolder.
EDOHolderView This class produces the view of an EDOHolderModel.
EDOImgButtonController This class is an MVCImgButtonController for EDO "edit" buttons which shouldnt be enabled while app is in "read only" mode.
EDOIndex This simple class encapsulates an EDO index to allow more complex composite indexes to be defined as subclasses.
EDOListModel This class encapsulates a list model for EDOs.
EDOListView This class produces a view of a List of EDOs.
EDOSelectedModel This class encapsulates the data model for the flag indicating whether an editable domain object (EDO) is selected from a set of EDOs in an EDOHolderModel.
EDOSuperMenuCmd This abstract class is a base for menu commands that affect other EDO attributes.
EDOToggleExpandCmd This class implements the toggle EDO display cmd.
EDOToggleSelectCmd This class implements the toggle select EDO cmd.
EDOView This abstract class produces a view of an EDO.

Method Summary
static void EDOInitialize( <EDOHolderModel> edoHolder, <Function> edoIndexClass, <Function> edoHolderViewClass, optCounterViewClass, <PopupMenuController> optMenuCtrlr )
           Initialize EDO Framework global variables.
static void onEDOAddClick( <EDOIndex> xi )
           handle event for 'click on an EDO Add Button'
static void onEDOClick( <EDOIndex> xi )
           handle event for 'click on an EDO'
static void onEDODeleteBtnPressed()
           handle event for 'delete EDO button pressed'
static void onEDOEditBtnPressed()
           handle event for 'edit EDO button pressed'
static void onEDOHolderCancelBtnPressed()
           handle event for 'cancel button pressed'
static void onEDOHolderExitBtnPressed()
           handle event for 'exit button pressed'
static void onEDOHolderLoadReply( <XMLreq> xmlReq )
           handle event for 'reply received from "load EDOHolder data" server-request'
static void onEDOHolderPageBeforeUnLoad()
          
static void onEDOHolderPageLoad()
           handle event for 'entering EDOHolder web page'
static void onEDOHolderSaveBtnPressed()
           handle event for 'save button pressed'
static void onEDOHolderSaveReply( <XMLreq> xmlReq )
           handle event for 'reply received from "save EDOHolder data" server-request'
static void onEDOHolderSelect( <int> selectedHolderIndex )
           handle event for 'user selects from the select menu which loads a new EDOHolder'
static void onEDOInsertBtnPressed()
           handle event for 'insert EDO button pressed'
static void onEDOToggle( <EDOIndex> xi )
           handle event for 'toggle an EDO'

// This file uses JSDoc-friendly comments [ http://jsdoc.sourceforge.net/ ]

/**
 * @file         grvEDO.js
 * @fileoverview Collection of Editable Domain Object framework classes.
 * This library builds on the MVC framework and adds support for EDOs
 * (Editable Domain Objects) including undo-able select/edit/delete/insert
 * operations.
 *
 * @todo EDO and EDOHolderModel and EDOListModel should be combined
 * into an EDO that supports the Composite Design Pattern. Of course, to
 * do this, all of the MVC "Model" family should be reworked to do the same.
 * The MVC "View" family DOES support Composites but for some reason the
 * Models didnt start out that way. [...sigh...]
 *
 * @author       Bruce Wallace  (PolyGlotInc.com)
 * @requires     grvMVC.js
 * @version      2.0
 */

//////////////////////////////////////////////////////////////////
// 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"
//////////////////////////////////////////////////////////////////


////////////////////////////////////////////
/////////// Global Variables ///////////////
////////////////////////////////////////////

/** Global Server Error Messages Model */
var gEDOServerError = null;

/** Global EDO Selection Model */
var gEDOSelected = null; //new EDOSelectedModel(...);

/** Global EDO Holder Model */
var gEDOHolder = null; //new EDOHolderModel(...);

/** Global EDO Holder Selector Model */
var gEDOHolderSelected = null; //new EDOHolderSelectionModel(...);

/** Initialize EDO Framework global variables.
 * @param {EDOHolderModel} edoHolder wrapper model for set of EDOs
 * @param {Function} edoIndexClass class of index used with EDOHolder 
 * @param {Function} edoHolderViewClass "class" of EDOHolderView to use
 * @param {Function} counterViewClass "class" of EDOCountersView to use
 * @param {PopupMenuController} optMenuCtrlr optional menu override
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOInitialize
( edoHolder, edoIndexClass, edoHolderViewClass, optCounterViewClass, optMenuCtrlr  )
{
	gEDOHolder         = edoHolder;
	gEDOHolderSelected = edoHolder.model;

	// DATA MODEL: which "Editable Domain Object" is Selected
	gEDOSelected = new EDOSelectedModel( gEDOHolder, edoIndexClass );

	// DATA MODEL: Server Error Messages
	gEDOServerError = new MVCScalarModel();  gEDOServerError.setValue("");

	if (gGrvTraceMVC) gEDOHolderSelected.model.dumpSubscribers();
	if (gGrvTraceMVC) gEDOHolderSelected      .dumpSubscribers();
	if (gGrvTraceMVC) gEDOHolder              .dumpSubscribers();
	
	// DATA VIEW: EDO Framework Application Panel View.
	gMVCRootView.addSubView( "EDOAppPanel",
		new EDOAppPanelView( gEDOHolder,
			edoHolderViewClass, optCounterViewClass, optMenuCtrlr ) );
}


////////////////////////////////////////////
//////////// Domain Objects ////////////////
////////////////////////////////////////////

Class(EDOIndex,["index into EDO list"]);
/**
 * @class This simple class encapsulates an EDO index to allow more
 * complex composite indexes to be defined as subclasses.
 * @extends GrvObject
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOIndex()
{
	/** @param {int} optXI optional "index" of which EDO is selected.
	 * If not specified or if null then this represents "no selection".
	*/
	this.konstructor = function( optXI ){
		this.xi = optXI===undefined ? null : optXI;
	}

	/** return "this" as event handler arg list string @type String */	
	this.asEventArgs = function(){ return this.xi; }

	/** return "this" as a description string @type String */	
	this.asString = function(){ return "EDO["+this.xi+"]"; }

	/** return whether "this" contains a selection @type boolean */
	this.hasSelection = function(){ return this.xi!=null; }

	/** return whether "this" equals given EDOIndex @type boolean */
	this.equals = function(edoIndex){ return this.xi==edoIndex.xi; }
}


Class(EDO, [ "edo Class","parent holder model", "property values array",
			 "edit Date","edit User","unique Key" ]);
/**
 * @class This class defines a framework for Editable Domain Objects.<p>
 * Note that subclasses MUST override the {@link #clone} method.
 * @extends GrvObject
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDO()
{
	// init "static" Class methods/elements
	if (!EDO.kMsgSep){//keep this line above jsdoc comments!
		 EDO.kMsgSep = "~"; /** message separator character */
	}

	/** 
	 * @param {Function} CLAZZ this EDO's "class"
	 * @param {EDOHolderModel} parentHolder holder model containing this
	 * @param {array}  propValues array of property initial values
	 * @param {String} editDate date of last edit
	 * @param {String} editUser ID of last user to edit
	 * @param {int} theKey Oracle primary key of this record
	 * @param {int} optEditStatus optional edit status where the
	 *  status codes are: 0=no-change; 1=delete; 2=create; 3=hidden; 4=update
	 * @param {String} optName optional name of this instance
	 */
	this.konstructor = function
	(
		CLAZZ,
		parentHolder,
		propValues,
		editDate,
		editUser,
		theKey,
		optEditStatus,
		optName
	){
		// immutable instance variables
		this.clazz = CLAZZ;
		this.rulez = CLAZZ.EditRules ? CLAZZ.EditRules.join('') : null;//backwards compatibility w/RATS

		// init fixed instance variables
		this.holder= parentHolder;
		this.date  = editDate;
		this.whom  = editUser;
		this.key   = theKey;
		if (optName) this.name = optName;

		//0=no-change; 1=delete; 2=create; 3=hidden; 4=update
		this.editStatus = optEditStatus?optEditStatus:0;

		//display expanded/collapsed
		this.expanded = true;

		// define variable instance variables...
		// each of these has a "validity" attribute associated with it
		var propNames = CLAZZ.Properties;
		for (var i=0; i<propNames.length; ++i)
		  this[ propNames[i] ] = propValues ? propValues[i] : "";
	}

	/** return a clone of "this" object. 	 
	 * <p>ABSTRACT: sub classes of EDO should override this.</p>
	 *  @type EDO
	 */
	this.clone = function()
	{
		grvMustOverride("EDO.clone");
/*		return new <<your subclass name goes here>>(
		 this.propValues(), this.date, this.whom, this.key, this.editStatus );
*/	}

	/** push into given array the editable property values URI encoded
	 * @return resultant array
	 * @type Array
	 */
	this.pushEncodedValues = function( pArray )
	{
		var propz = this.clazz.Properties;
		for (var i=0; i<propz.length; ++i)
		  pArray.push( encodeURIComponent(this[ propz[i] ]) );
		return pArray;
	}
	/** push into given array the editable property values
	 * @return resultant array
	 * @type Array
	 */
	this.pushValues = function( pArray )
	{
		var propz = this.clazz.Properties;
		for (var i=0; i<propz.length; ++i)
		  pArray.push( this[ propz[i] ] );
		return pArray;
	}

	/** convert all property edit rules into "read-only" */
	this.makeReadOnly = function() {
		var N = this.rulez.length;
		this.rulez = "";
		for (var i=0; i<N; ++i)
		  this.rulez += EditRule.kRO;
	}
	
	/** generate array of editable property values @return value array @type Array */
	this.propValues = function() {
		return this.pushValues( new Array() );
	}

	/** return "this" as human-readable string @type String */
	this.toString = function() {
		return this.propValues().join("-");
	}

	/** return the DEFAULT tooltip for "this" @type String
	 * @param {EDOIndex} xi edo index
	 */
	this.edoToolTip = function( xi ){
		return xi.asString()+" \nState:"+this.state()+" Key="+this.key;
	}

	/** return the tooltip for "this" @type String
	 * @param {EDOIndex} xi edo index
	 */
	this.toolTip = function( xi ){
		return this.edoToolTip( xi );
	}

	/** return whether this EDO has validated data @type boolean */
	this.isValid = function()
	{
		//if I am deleted then I am not invalid.
		if ( !this.isActive() ) return true;

		var propz = this.clazz.Properties;
		for (var i=0; i<propz.length; ++i)
		  if ( this.getValidity( propz[i] ) )
		    return false;

		return true;
	}

	/** return the debug details of "this" @type String */
	this.dump = function()
	{
		var		dStr = new Array();
				dStr.push( this.clazz.UpdateID+">>>[" );
				dStr.push( "date="+this.date );
				dStr.push( "whom="+this.whom );
				dStr.push( "key ="+this.key  );
				dStr.push( "edit="+this.editStatus );

		var propz = this.clazz.Properties;
		for (var i=0; i<propz.length; ++i){
				var prop = propz[i];
				dStr.push( prop +"="+ this[prop] );
		}
				dStr.push( "]<<<"+this.name );
		return	dStr.join("\n");
	}

	/** return an UpdateItem Message (as a POST argument) 
	 *  for (just) this particular EDO formatted as:<pre>
	 * "UPDATES={class}~{key}~{action}~{content}~END_UPDATE"
	 * where the fields are defined as follows...
	 *     {class}  = the entity's type
	 *     {key}    = the entity's unique key number
	 *     {action} = the entity's action code
	 * (encode as strings 'C','D', and 'U' for values create, delete, and update.)
	 *     {content}= the EDO's specific content in EDO-specific-format
	 *</pre>
	 * @type String
	 */
	this.buildUpdateItemPost = function()
	{
		var uFields = new Array();
			uFields.push( "UPDATES=" + this.clazz.UpdateID   );
			uFields.push( this.key ? this.key : "0" );
			uFields.push( this.action()             );
			   this.pushUpdateFields( uFields );
			uFields.push( "END_UPDATE"              );
	 return uFields.join( EDO.kMsgSep );
	}

	/** push into given array all EDO-specific fields of the UpdateItem message.
	 * This default implementation generates content formatted as<pre>
	 * "{property1}~{property2}~...~{propertyN}~"
	 * where the fields are defined as follows...
	 *     {propertyI} = the i-th editable property of this EDO
	 *</pre>
	 * @param {Array} uFields  string array containing Update Msg fields
	 */
	this.pushUpdateFields = function( uFields ) {
		this.pushEncodedValues( uFields );
	}

	/** push into given array all AJAX SAVE Post params for this model
	 * @param {Array} posts  string array containing POST params
	 */
	this.pushPosts = function( posts ) {
		if (this.inEdit()) posts.push( this.buildUpdateItemPost() );
	}

	/** return the edit/validation rule for the specified property.
	 * If no property is specified then return entire set of rules
	 * for this EDO. Each Rule is one character long.
	 * @see MVCDualStringController
	 * @type String
	 * @param {String} optPropID optional property ID
	 */
	this.getEditRule = function( optPropID )
	{
		//if no rules specified then anything goes
		if (!this.rulez) return MVCEditRule.kEdit;

		//if optional property ID not specified then return all rules
		if (grvIsEmpty(optPropID)) return this.rulez;

		//optional property ID was specified so return specific rule
		var propz = this.clazz.Properties;
		for (var i=0; i<propz.length; ++i)
		  if (optPropID==propz[i]) return this.rulez.charAt(i);

		throw "bad Property ID["+optPropID+"] in getEditRule()";
	}

	/** set the validity code of the specified attribute
	 * @param {String} ID repayment attribute name
	 * @param {String} v validity "code" (actually an error message or null)
	 */
	this.setValidity = function(ID,v){ this[ID+MVCScalarModel.kValiditySuffix] = v; }
	
	/** return the validity "code" of the specified attribute
	 * @param {String} ID repayment attribute name
	 * @type String
	 */
	this.getValidity = function(ID){ return this[ID+MVCScalarModel.kValiditySuffix]; }

	/** Is it legal to delete "this"?
	 * @return errmsg or null
	 * @type String
	 */
	this.canDelete = function(){ 
		if ( this.isActive() ) return null;
		return "This is already deleted.";
	}

	/** mark this EDO as deleted */
	this.deleteMe = function(){
		this.preDeleteExpand = this.expanded;
		this.preDeleteStatus = this.editStatus;
		this.editStatus      = this.editStatus==2 ? 3 : 1;
		this.collapse();
	}
	
	/** restore this EDO to its pre-deleted state */
	this.undeleteMe = function(){
		this.editStatus = this.preDeleteStatus;
		if (this.preDeleteExpand) this.expand();
		delete this.preDeleteStatus;
		delete this.preDeleteExpand;
	}

	/** Is it legal to create a new one of "this/these"?
	 * @param {boolean} appendFlag if true then append else insert
	 * @return errmsg or null
	 * @type String
	 */
	this.canCreate = function(appendFlag){ return null; }//override me

	/** If created, can we undo creation? @type boolean
	 * @param {boolean} appendFlag if true then append else insert
	 */
	this.canUncreate = function(appendFlag){ return true; }//override me

	/** mark this entity as being newly added */
	this.addMe       = function(){ this.editStatus = 2; }
	
	/** is this entity newly added? @type boolean */
	this.isNew       = function(){ return this.editStatus==2; }
	/** is this entity newly added? @type boolean */
	this.isDeleted   = function(){ return this.editStatus==1; }


	/** return whether this entity has been changed since last save @type boolean */
	this.inEdit = function(){ return this.editStatus > 0 && this.editStatus!=3; }

	/** Is it legal to edit "this"?
	 * @return errmsg or null
	 * @type String
	 */
	this.canEdit = function(){
		if ( kReadOnly ) return "Not allowed in read-only mode.";
		if ( this.isActive() ) return null;
		return "This is deleted.";
	}

	/** mark this entity as having been edited  */
	this.editMe = function()
	{
		this.preExpand  = this.expanded;
		this.preEdit    = this.clone();//save previous state
		this.editStatus = this.editStatus==2 ? 2 : 4;
		this.expanded   = true;//alway expand on edit
		this.whom       = kUserID;
		this.date       = grvGetTodayAsMMDDYYYY();
	}

	/** restore this EDO to its pre-edited state */
	this.uneditMe = function()
	{
		var propz = this.clazz.Properties;
		for (var i=0; i<propz.length; ++i){
		  var prop = propz[i];
		  this[ prop ] = this.preEdit[ prop ];
		}

		this.date       = this.preEdit.date;
		this.whom       = this.preEdit.whom;
		this.key        = this.preEdit.key;
		this.editStatus = this.preEdit.editStatus;
		this.expanded   = this.preExpand;
		delete this.preEdit;
	}

	/** force this EDO to be collapsed */
	this.collapse = function(){ this.expanded=false; }

	/** force this EDO to be expanded */
	this.expand = function(){ this.expanded=true; }

	/** return whether this EDO is collapsed or open
	 * @return true if open
	 * @type boolean
	 */
	this.isExpanded = function(){
		return this.expanded;
	}
	/** can this EDO toggle the "expanded" flag? @type boolean
	 * @param {boolean} doAllFlag if true, all EDOs are to be set
	 */
	this.canToggleExpand = function( doAllFlag ) {
		return this.isActive(); /* override as needed */
	}
	/** toggle (or set) the "expanded" flag of this EDO.
	 * @param {boolean} optValue if specified, set flag to it, else toggle
	 */
	this.toggleExpand = function( optValue )
	{
		if ( this.canToggleExpand() )
		  this.expanded = (optValue===undefined||optValue==null)
					    ? (!this.expanded)
					    : optValue;
	}

	/** Is this EDO active and editable? (i.e not deleted or in limbo)
	 * @return true if active
	 * @type boolean
	 */
	this.isActive = function(){ return this.editStatus!=1 && this.editStatus!=3; }
	
	/** Is this EDO undead? (i.e in limbo)
	 * @return true if in Limbo
	 * @type boolean
	 */
	this.inLimbo = function(){ return this.editStatus==3; }
	
	/** return whether this EDO can be viewed if desired @type boolean */
//	this.isViewable = function(){ return this.isActive(); }
	
	/** return whether this EDO can be viewed if desired @type boolean */
	this.isViewable = function(){ return this.isActive() || this.inEdit(); }

	/** return the formatted version of our edit state @type String */
	this.state = function()
	{
		switch( this.editStatus )
		{
			case 0: return "Unchanged";
			case 1: return "Deleted";
			case 2: return "Added";
			case 3: return "Limbo";
			case 4: return "Updated";
		}
		return "unknown state:["+this.editStatus+"]";
	}

	/** return our edit state translated to a broadcast message code @type String */
	this.action = function()
	{
		switch( this.editStatus )
		{
			case 1: return "D";
			case 2: return "C";
			case 4: return "U";
		}
		return "";
	}
}

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

Class(EDOListModel,["EDO Class"]).Extends(MVCListModel);
/**
 * @class This class encapsulates a list model for EDOs.
 * @extends MVCListModel
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOListModel()
{
	/** @param {Function} edoClass the class of the EDO
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function( edoClass, optName )
	{
		this.souper( optName );

		// init instance variables
		this.itemType = edoClass;
	}

	/** return the count of viewable EDOs associated with "this" @type int */
	this.getVisibleCount = function()
	{
		var count = 0;
		this.iterate( function(i,edo){ if (edo.isViewable()) ++count; } );
		return count;
	}

	/** return the count of active EDOs associated with "this" @type int */
	this.getActiveCount = function()
	{
		var count = 0;
		this.iterate( function(i,edo){ if (edo.isActive()) ++count; } );
		return count;
	}

	/** return whether any EDOs are changed for this list @type boolean */
	this.isDirty = function(){
		var changed = false;
		this.iterate( function(i,edo){ if (edo.inEdit()) return changed = true; } );
		return changed;
	}

	/** return whether all EDOs are valid for this model @type boolean */
	this.isValid = function(){
		var valid = true;
		this.iterate( function(i,edo){ if (!edo.isValid()){valid = false; return true;} } );
		return valid;
	}

	/** push into given array all AJAX SAVE Post params for this model
	 * @param {Array} posts  string array containing POST params
	 */
	this.pushPosts = function( posts ) {
		this.iterate( function(i,edo){ edo.pushPosts(posts); } );
	}

	/** toggle the "expanded" flag of the EDO.
	 * @param {boolean} newVal value to set all of the EDO flags to
	 */
	this.toggleExpandAll = function( newVal ) {
		this.iterate( function(i,edo){ edo.toggleExpand(newVal); } );
	}

}


Class(EDOHolderModel,["Holder Selection Model","Load XSL DOM","Entity Name"])
.Extends(MVCModel);
/**
 * @class This abstract class encapsulates the functionality of
 * a data model managing a set of editable domain objects (EDOs).
 * <ul>The following methods MUST be overridden
 * <li>{@link #isDirty}</li>
 * <li>{@link #isValid}</li>
 * <li>{@link #_resetEDOs}</li>
 * <li>{@link #keysMatch}</li>
 * <li>{@link #propMatch}</li>
 * <li>{@link #hasEDO}</li>
 * <li>{@link #_newEDO}</li>
 * <li>{@link #getEDO}</li>
 * <li>{@link #createEDO}</li>
 * <li>{@link #appendEDO}</li>
 * <li>{@link #pushPosts}</li>
 * <li>{@link #getUpdatedProperty}</li>
 * <li>{@link #createSelectionItem}</li>
 *</ul><p>
 * Second, this model implements the {@link MVCBoolModel#isTrue}
 * interface where the value reflects whether this model is "dirty".
 * @extends MVCModel
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 * @see EDOIndex
 * @see EDOSelectedModel
 */
function EDOHolderModel()
{
	// init "static" Class methods/elements
	if (!EDOHolderModel.AjaxRequest){//keep this line above jsdoc comments!

		/** Make a REST-style AJAX request to the server.</br>
		 * @param {String}  theType     requested entity name
		 * @param {String}  theKey      unique key of requested entity
		 * @param {Function} replyHandler reply event handler static function
		 * @param {String}  optPostData optional string to pass as POST data
		 * @param {boolean} optWaitFlag optional flag that if true says
		 *                  call synchronously (i.e. wait for reply)
		 */
		EDOHolderModel.AjaxRequest = function( theType, theKey, replyHandler, optPostData, optWaitFlag )
		{
			grvWAIT();
			var url = kXMLPath + theType + theKey + ".xml";
			if (!_kGrvAjaxTestData)
			{
				var requestName = (optPostData?"Update":"GetXML") + theType + "DATA";
				var parms = new Array();
					parms.push( requestName );
					parms.push(    'user=' + grvGetArg("user")    );
					parms.push( 'testing=' + grvGetArg("testing") );
					parms.push( theType.toUpperCase() + '_KEY=' + theKey  );
		// var requestPrefix = "?CONTROLLER=funb.cat.reconcile.controller.";
		// var requestPrefix = "?CONTROLLER=funb.cat.bass.controller.";
		   var requestPrefix = "?action=";
				url = location.pathname + requestPrefix + parms.join("&");
			}

			return new XMLreq( url, replyHandler, optPostData, optWaitFlag );
		}
	}

	/** @param {MVCSelectionModel} holderSelectionModel model of which edoHolderModel is selected
	 *  @param {Object} loadXslDOM XSL DOM that transforms newly loaded XML data into "this"
	 *  @param {String} entityName entity name of this instance
	 */
	this.konstructor = function( holderSelectionModel, loadXslDOM, entityName )
	{
		this.souper( entityName );

		// init instance variables
		this.subscribe( holderSelectionModel );
		this.loadXslDOM = loadXslDOM;
	}

	/** {@link MVCBoolModel} API: return "I am modified" flag @type boolean */
	this.isTrue = function(){ return this.isDirty(); }

	/** return whether any EDOs are changed for this model @type boolean
	 * <p>ABSTRACT: sub classes of EDOHolderModel should override this</p>
	 */
	this.isDirty = function()
	{ grvMustOverride("EDOHolderModel.isDirty"); }

	/** return whether all EDOs are valid for this model @type boolean
	 * <p>ABSTRACT: sub classes of EDOHolderModel should override this</p>
	 */
	this.isValid = function()
	{ grvMustOverride("EDOHolderModel.isValid"); }

	/** clear EDOs
	 * <p>ABSTRACT: sub classes of EDOHolderModel should override this</p>
	 */
	this._resetEDOs = function()
	{ grvMustOverride("EDOHolderModel._resetEDOs"); }

	/** clear EDOs and update timestamp but dont publish */
	this._reset = function(){
		this._resetEDOs();
		this.updateStamp();
	}

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

	/** return whether there is currently an EDO @type boolean
	 * <p>ABSTRACT: sub classes of EDOHolderModel should override this</p>
	 */
	this.hasEDO = function()
	{ grvMustOverride("EDOHolderModel.hasEDO"); }

	/** return a new blank EDO of the type implied by EDOIndex @type EDO
	 * @param {EDOIndex} xi edo index
	 * <p>ABSTRACT: sub classes of EDOHolderModel should override this</p>
	 */
	this._newEDO = function(xi)
	{ grvMustOverride("EDOHolderModel._newEDO"); }

	/** return a new blank EDO of the type implied by EDOIndex @type EDO
	 * @param {EDOIndex} xi edo index
	 */
	this.newEDO = function(xi)
	{
	    var edo = this._newEDO(xi);
	    edo.addMe();
	    return edo;
	}

	/** return reference to the specified editable domain object
	 * @param {EDOIndex} xi edo index
	 * <p>ABSTRACT: sub classes of EDOHolderModel should override this</p>
	 */
	this.getEDO = function(xi)
	{ grvMustOverride("EDOHolderModel.getEDO"); }

	/** return the specified EDO with the intent to change its attributes
	 * @type EDO
	 * @param {EDOIndex} xi edo index
	 */
	this.openEDO = function(xi){
	    this.publish();
	    return this.getEDO(xi);
	}

	/** return whether the specified EDO is collapsed or open
	 * @param {EDOIndex} xi edo index
	 * @return true if open
	 * @type boolean
	 */
	this.isExpandedEDO = function(xi){
		return this.getEDO(xi).isExpanded();
	}
	/** can we toggle the "expanded" flag of the specified EDO? @type boolean
	 * @param {boolean} doAllFlag if true, set all of the EDOs to new value
	 * @param {EDOIndex} xi edo index
	 */
	this.canToggleExpand = function( doAllFlag, xi ) {
		// OVERRIDE TO implement doAllFlag
		return this.getEDO(xi).canToggleExpand();
	}
	/** toggle the "expanded" flag of the specified EDO.
	 * @param {boolean} doAllFlag if true, set all of the EDOs to new value
	 * @param {EDOIndex} xi edo index
	 */
	this.toggleExpand = function( doAllFlag, xi )
	{
		// OVERRIDE TO implement doAllFlag
		this.getEDO(xi).toggleExpand();
		this.updateStamp();
	}

	/** Is it legal to delete selected EDO?
	 * @return errmsg or null
	 * @type String
	 * @param {EDOIndex} xi edo index
	 */
	this.canDeleteEDO = function(xi){
		return this.getEDO(xi).canDelete();
	}
	/** Is it legal to edit selected EDO?
	 * @return errmsg or null
	 * @type String
	 * @param {EDOIndex} xi edo index
	 */
	this.canEditEDO = function(xi){
		return this.getEDO(xi).canEdit();
	}
	/** Is it legal to create a new EDO?
	 * @return errmsg or null
	 * @type String
	 * @param {EDOIndex} xi edo index
	 * @param {boolean} appendFlag if true then append else insert
	 */
	this.canCreateEDO = function(xi,appendFlag){
		var edo = this.getEDO(xi);
		return edo ? edo.canCreate(appendFlag) : null;
	}
	/** If created, can we undo creation? @type boolean
	 * @param {EDOIndex} xi edo index
	 * @param {boolean} appendFlag if true then append else insert
	 */
	this.canUncreateEDO = function(xi,appendFlag){
		var edo = this.getEDO(xi);
		return edo ? edo.canUncreate(appendFlag) : true;
	}

	/** delete the specified EDO
	 * @param {EDOIndex} xi edo index
	 */
	this.deleteEDO = function(xi){
		this.openEDO(xi).deleteMe();
	}

	/** undelete the specified EDO
	 * @param {EDOIndex} xi edo index
	 */
	this.undeleteEDO = function(xi){
		this.openEDO(xi).undeleteMe();
	}

	/** enable editing on the specified EDO
	 * @param {EDOIndex} xi edo index
	 */
	this.editEDO = function(xi){
		this.openEDO(xi).editMe();
	}

	/** rollback edit on the specified EDO
	 * @param {EDOIndex} xi edo index
	 */
	this.uneditEDO = function(xi){
		this.openEDO(xi).uneditMe();
	}

	/** create new EDO which is inserted after selected EDO
	 * @return EDOIndex of new EDO
	 * @type EDOIndex
	 * @param {EDOIndex} xi edo index
	 * <p>ABSTRACT: sub classes of EDOHolderModel should override this</p>
	 */
	this.createEDO = function(xi)
	{ grvMustOverride("EDOHolderModel.createEDO"); }

	/** create new EDO which is appended at end of EDO list
	 * @return EDOIndex of new EDO
	 * @type EDOIndex
	 * @param {EDOIndex} xi edo index
	 * <p>ABSTRACT: sub classes of EDOHolderModel should override this</p>
	 */
	this.appendEDO = function(xi)
	{ grvMustOverride("EDOHolderModel.appendEDO"); }


	/** create a new item for the selection list from current EDO
	 * @type EDOHolderSelectionItem
	 * <p>ABSTRACT: sub classes of EDOHolderModel should override this</p>
	 */
	this.createSelectionItem = function()
	{ grvMustOverride("EDOHolderModel.createSelectionItem"); }

	/** return the updated properties from the current EDO needed for select list update
	 * @return arbitrary value as needed @type Object
	 * <p>ABSTRACT: sub classes of EDOHolderModel should override this</p>
	 */
	this.getUpdatedProperty = function()
	{ grvMustOverride("EDOHolderModel.getUpdatedProperty"); }


	/** handle CRUD event: "just created EDOHolder" */
	this.justCreated = function(){
		this.model.createSelection( this.createSelectionItem() );
	}
	/** handle CRUD event: "just updated EDOHolder" */
	this.justUpdated = function() {
		this.model.updateSelection( this.getUpdatedProperty() );
	}
	/** handle CRUD event: "just deleted EDOHolder" */
	this.justDeleted = function() {
		this.model.deleteSelection( true );
	}
	/** handle CRUD event: "just loaded EDOHolder" */
	this.justRead = function(){ /* do nothing special */ }

	/** return whether current EDO matches selection item "key"
	 * @type boolean
	 * @param {EDOHolderSelectionItem} item selection menu item
	 * <p>ABSTRACT: sub classes of EDOHolderModel should override this</p>
	 */
	this.keysMatch = function(item)
	{ grvMustOverride("EDOHolderModel.keysMatch"); }

	/** return whether current EDO matches selection item "properties"
	 * @type boolean
	 * @param {EDOHolderSelectionItem} item selection menu item
	 * <p>ABSTRACT: sub classes of EDOHolderModel should override this</p>
	 */
	this.propMatch = function(item)
	{ grvMustOverride("EDOHolderModel.propMatch"); }

	/** return the type of CRUD event that caused EDOHolder reload.
	 * @type String
	 * @return type of CRUD event encoded as char 'C' or 'R' or 'U' or 'D'
	 */
	this.getCRUDtype = function()
	{
		var listEmpty   = (this.model.getCount()<1);
		var hasSelection= listEmpty ? false : this.model.hasSelection();
		
		if (this.hasEDO())
		{
			if (listEmpty) return 'C';
			grvASSERT(hasSelection,"menu list exists with no selection");
			var selection = this.model.getSelection();
			var keysMatch = this.keysMatch(selection);
			var propMatch = this.propMatch(selection);
			if (keysMatch) return propMatch ? 'R' : 'U';
			grvASSERT(!propMatch,"EDO was saved with duplicate Properties");
			return 'C';
		}
		else
		{
			if (listEmpty) return 'R';
			grvASSERT(hasSelection,"menu list exists with no selection");
			return 'D';
		}
	}

	/** handle "EDOHolder reloaded" event. */
	this.reloaded = function()
	{
		if (gGrvTraceEvt) grvDebugWindow( this.dump() );//look at the updated data

		// HACK: this normally should have been accomplished via
		// subscriptions, but it would have set up a circular dependency.
		switch( this.getCRUDtype() )
		{
			case 'C': this.justCreated(); break;
			case 'R': this.justRead();    break;
			case 'U': this.justUpdated(); break;
			case 'D': this.justDeleted(); break;
			default: grvError("bad CRUD code");
		}

//		grvUNWAIT();
	}

	/** Make a Load-Data request to the AJAX server.
	 * @param {Object} edoHolderSelected selected item in observed model
	 */
	this.requestLoad = function( edoHolderSelected ){
		//Note: nothing selected when a new EDO is needed
		if (edoHolderSelected)
		  EDOHolderModel.AjaxRequest( this.name, edoHolderSelected.key, onEDOHolderLoadReply );
	}

	/** Make a Save-Data request to the AJAX server.
	 * @param {Object} edoHolderSelected selected item in observed model
	 * @param {String} updates string of EDO update POSTs
	 */
	this.requestSave = function( edoHolderSelected, updates ){
		//Note: nothing selected when saving new EDO
		var theKey = edoHolderSelected ? edoHolderSelected.key : null;
		EDOHolderModel.AjaxRequest( this.name, theKey, onEDOHolderSaveReply, updates );
	}

	/** push into given array all AJAX SAVE Post params for this model
	 * @param {Array} posts  string array containing POST params
	 * <p>ABSTRACT: sub classes of EDOHolderModel should override this</p>
	 */
	this.pushPosts = function( posts )
	{ grvMustOverride("EDOHolderModel.pushPosts"); }

	/** handle update events from the EDOHolder Selection Model */
	this.update = function()
	{
		// clear out old data now so that user doesnt keep seeing
		// old data while new data is being requested and loaded
		this.reset();

		// TODO make gMVCUndoCmds watch gEDOHolderSelected
		gMVCUndoCmds.reset();//must reset here because cmds in queue
						  //can refer to EDOs that dont exist
						  //at this point because of this.reset()

		// request EDOHolder data from server (after small delay)
		this.requestLoad( this.model.getSelection() );
	}

	/** save all of the edits into the database via service layer */
	this.save = function()
	{
		var             postArgs = new Array();
		                postArgs.push( "USER_ID=" + kUserID );
		this.pushPosts( postArgs );

		// immediate invocation of AJAX request with asynch reply handling
		this.requestSave( this.model.getSelection(), postArgs.join('&') );
	}
}


Class(EDOSelectedModel,["EDO Holder model","EDO Index Class"])
.Extends(MVCBoolModel);
/**
 * @class This class encapsulates the data model for the flag
 * indicating whether an editable domain object (EDO) is selected
 * from a set of EDOs in an EDOHolderModel. One and only one
 * EDO can be selected at a time.  This model also keeps track
 * of which EDO is selected.
 * If a new EDOHolder is selected (thus loading new data)
 * "this" gets reset to false (i.e nothing selected).
 * @extends MVCBoolModel
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOSelectedModel()
{
	/** @param {EDOHolderModel} edoHolder model containing EDO set
	 *  @param {Function} edoIndexClass the class/function of the EDOIndex to use
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function( edoHolderModel, edoIndexClass, optName )
	{
		this.souper( optName ? optName : "EDOSelected Flag" );

		// init instance variables
		this.subscribe( edoHolderModel.model );
		this.edoHolder = edoHolderModel;
		this.xiClass   = edoIndexClass;
		this._unselect();
	}

	/** return whether an EDO is selected @type boolean */
	this.hasSelection = function(){ return this.xi.hasSelection(); }

	/** return the selected EDO or null if none selected @type EDO */
	this.getSelected = function(){
		if ( !this.hasSelection() ) return null;
		return this.edoHolder.getEDO( this.xi  );
	}
	/** return the specified EDO (or current selection if not specified) @type EDO
	 * @param {EDOIndex} optXI optional edo index
	 */
	this.getEDO = function(optXI) {
		return optXI ? this.edoHolder.getEDO(optXI) : this.getSelected();
	}

	/** return the selected EDOIndex @type EDOIndex */
	this.getIndex = function(){ return this.xi; }

	/** force the "something is selected" flag to specified value and publish */
	this.forceFlag = function(f){ this.setFlag(f); }

	/** change to "nothing selected" */
	this._unselect = function(){ this._select( new this.xiClass() ); }

	/** {@link #_unselect} and publish */
	this.unselect = function(){ this._unselect(); this.publish(); }

	/** handle update events from the EDOHolder Selection Model */
	this.update = function(){ /*if (this.model.hasChanged)*/ this.unselect(); }

	/** select the specified EDO
	 * @param {EDOIndex} edoIndex edo index
	 */
	this._select = function( edoIndex ) {
		this.xi = edoIndex;
		this._setValue( this.hasSelection() );
	}

	/** select (and publish) the specified EDO
	 * @param {EDOIndex} xi edo index
	 */
	this.select = function(xi){ this._select(xi); this.publish(); }

	/** return whether specified EDO is selected @type boolean
	 * @param {EDOIndex} edoIndex edo index
	 */
	this.isSelected = function( edoIndex ) {
		return this.xi.equals(edoIndex);
	}

	/** Make the specified EDO selected, unless it is
	 * currently selected then merely deselect it.
	 * @param {EDOIndex} xi edo index
	 */
	this.toggleSelect = function(xi)
	{
		if ( this.isSelected(xi) )
			 this.unselect();
		else this.select(xi);
	}

	/** Is it legal to delete selected EDO? @return errmsg or null @type String */
	this.canDeleteSelected = function() {
		return this.edoHolder.canDeleteEDO( this.xi );
	}
	/** Is it legal to edit selected EDO? @return errmsg or null @type String */
	this.canEditSelected = function() {
		return this.canEdit( this.xi );
	}
	/** Is it legal to edit specified EDO? @return errmsg or null @type String
	 * @param {EDOIndex} xi edo index
	 */
	this.canEdit = function(xi) {
		return this.edoHolder.canEditEDO( xi );
	}

	/** Is it legal to create selected EDO? @return errmsg or null @type String
	 *  @param {boolean} appendFlag iff true this is an append else insert
	 *  @param {EDOIndex} optIndex optional index to use
	 */
	this.canCreateSelected = function(appendFlag,optIndex) {
		return this.edoHolder.canCreateEDO( optIndex?optIndex:this.xi, appendFlag );
	}
	/** If created, can we undo creation? @type boolean
	 *  @param {boolean} appendFlag iff true this is an append else insert
	 *  @param {EDOIndex} optIndex optional index to use
	 */
	this.canUncreateSelected = function(appendFlag,optIndex) {
		return this.edoHolder.canUncreateEDO( optIndex?optIndex:this.xi, appendFlag );
	}

	/** delete selected editable domain object */
	this.deleteSelected = function() {
		this.edoHolder.deleteEDO( this.xi );
		this.unselect();
	}

	/** undelete specified editable domain object
	 * @param {EDOIndex} xi edo index
	 */
	this.undelete = function(xi) {
		 this.edoHolder.undeleteEDO(xi);
	}

	/** edit selected editable domain object */
	this.editSelected = function() {
		this.edoHolder.editEDO( this.xi );
	}

	/** return whether specified editable is in edit mode @type boolean
	 * @param {EDOIndex} optXI optional edo index
	 */
	this.inEdit = function(optXI) {
		var selected = this.getEDO(optXI);
		return selected==null ? false : selected.inEdit();
	}

	/** unedit selected editable */
	this.uneditSelected = function() {
		this.edoHolder.uneditEDO( this.xi );
	}

	/** insert new EDO after selected EDO and select it */
	this.newAfterSelected = function() {
		this.select( this.edoHolder.createEDO( this.xi ) );
	}

	/** insert new EDO at end of EDO list and select it
	 * @param {EDOIndex} xi edo index
	 */
	this.appendSelected = function( xi ) {
		this.select( this.edoHolder.appendEDO( xi ) );
	}
}


Class(EDOHolderSelectionItem);
/**
 * @class This class defines the interface expected for items appearing in
 * the list model watched by {@link EDOHolderSelectionModel}.
 * This class implements the {@link MVCMenuItem} interface.
 * @extends GrvObject
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOHolderSelectionItem()
{
	/** quietly update the specified attributes
	 * @param {Object} args arbitrary list of args as needed
	 * <p>ABSTRACT: sub classes of EDOHolderSelectionItem should override this</p>
	 */
	this.updateProperties = function( args )
	{ grvMustOverride("EDOHolderSelectionItem.updateProperties"); }

	/** return "this" formatted for menu item @type String
	 * <p>ABSTRACT: sub classes of EDOHolderSelectionItem should override this</p>
	 */
	this.getDescription = function()
	{ grvMustOverride("MenuItem.getDescription"); }
}


Class(EDOHolderSelectionModel,["EDOHolderSelectionItem List Model"])
.Extends(MVCSelectionModel);
/**
 * @class This class encapsulates the data model for
 * "which EDOHolder is currently selected" from list of {@link EDOHolderSelectionItem}.
 * @extends MVCSelectionModel
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOHolderSelectionModel()
{
	/** @param {MVCListModel} listModel list of main menu items
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function( listModel, optName ){
		 this.souper( listModel, optName );
	}

	/** Select the "initially selected" EDOHolder.
	 *  This is intended to be overridden.
	 * @param {boolean} selectFirst if selection critera has no match then
	 * if selectFirst==true then select the first in list
	 *                      else leave selection alone 
	 */
 	this.selectInitial = function( selectFirst )
 	{
		if (this.model && this.model.getCount()>0)
		{
			if ( selectFirst ) this.select( 0 );
		}
		else this.selectNothing();	//NO DATA IN MENU LIST!!
	}

	/** quietly update the specified attributes of the selected item
	 * @param {Object} args arbitrary list of args as needed
	 */
	this.updateSelection = function( args )
	{
		// we jump thru hoops here to get observers/watchers to notice
		// that the listModel item has changed but not think that
		// "which item is selected" has changed (which would trigger
		// reloading already-loaded EDOHolder data).
		this.getSelection().updateProperties( args );
		this.updateStamp();
//		this.publish(true);
	}

	/** quietly delete the selected item from the parent list
	 * @param {boolean} selectFirst if true select first item in list
	 */
	this.deleteSelection = function( selectFirst ) {
		this.model._del( this.getIndex() );
		if (selectFirst) this.select(0,true);
	}

	/** quietly append the given item into the parent list and select it 
	 * @param {EDOHolderSelectionItem} item list item to append/select
	 */
	this.createSelection = function( item ) {
		// we jump thru hoops here to get observers/watchers to notice
		// that the listModel has changed but not think that
		// "which item is selected" has changed (which would trigger
		// reloading already-loaded EDOHolder data).
		this._select( this.model._push( item ) );
		this.updateStamp();
	}
}


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

Class(EDOListView,["EDO ListModel","EDO View Class"])
.Extends(MVCListView);
/**
 * @class This class produces a view of a List of EDOs.
 * @extends MVCListView
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOListView()
{
	/** @param {EDOListModel} edoListModel EDO list we are to observe
	 *  @param {Function} edoViewClass View Class for individual EDOs
	 */
	this.konstructor = function( edoListModel, edoViewClass  )
	{
		this.souper();

		// init instance variables
		this.itemView  = edoViewClass;
		this.watchModel( edoListModel );
	}

	this.currentContext = function() {
		return new MVCContext( this.model ? this.model.getActiveCount() : 0 );
	}

	/** return the EDOIndex appropriate for the given list index and EDO type. 	 
	 * <p>ABSTRACT: sub classes of EDOListView should override this.</p>
	 * @param {int} index index into ListModel
	 * @type EDOIndex
	 */
	this.itemEDOIndex = function( index ) {
		return new EDOIndex(index);
	}

	this.itemHTMLstr = function( index, item, itemID )
	{
		var HTML = new Array();
		var xi   = this.itemEDOIndex( index );
		HTML.push( this.embedHTML( itemID, new this.itemView(item,xi) ) );
		return HTML.join('');
	}

	this.headHTMLstr = function()
	{   if (this.model && this.model.getVisibleCount()>0) return "";
		return EDOView.ShellHTML( this.itemEDOIndex(0), this.model.itemType );
	}
}


Class(EDOView,["EDO object","EDO index object","inner view"])
.Extends(MVCView);
/**
 * @class This abstract class produces a view of an {@link EDO}.
 * Subclasses should override {@link #buildFields}.
 * @extends MVCView
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOView()
{
	// init "static" Class methods/elements
	if (!EDOView.ShellHTML) {//keep this line above jsdoc comments!

		EDOView.kImgExpanded	= 'expanded.gif';
		EDOView.kImgCollapsed	= 'collapsed.gif';

		/** Static method to generate the shell HTML for given future EDO.
		 * @param {EDOIndex} xi index of EDO
		 * @param {Function} itemClazz "class" of EDO
		 */
		EDOView.ShellHTML = function( xi, EDOclazz )
		{
			if (kReadOnly) return "";
			var title = "Create New " + EDOView.Title( EDOclazz );
			var event = "onEDOAddClick("+ xi.asEventArgs() +")";
			var HTML  = new Array();
			HTML.push( '<tr><td><fieldset class="edoFieldset">' );
			HTML.push( '<span onclick="'+event+'">' );
			HTML.push( '<legend class="edoFieldsetLegend">'+title+'</legend></span>' );
			HTML.push( '&nbsp;' );
			HTML.push( "</fieldset></td></tr>" );
			return HTML.join('');
		}

		EDOView.Title = function( EDOclazz ){
			return EDOclazz.Title ? EDOclazz.Title : EDOclazz.UpdateID;
		}
	}

	/** @param {EDO} edo EDO we are to observe
	 *  @param {EDOIndex} edoIndex composite EDO index
	 */
	this.konstructor = function( edo, edoIndex, innerView )
	{
		this.souper();

		// init instance variables
		this.xi = edoIndex;
		this.watchModel( edo );
		this.inner = innerView;
	}

	/** return the view ID of our clickable panel @type String */
	this.clickID = function(){ return this.getWidgetID() + ".clik"; }
	/** return the view ID of our set of attribute fields @type String */
	this.innerID = function(){ return this.getWidgetID() + ".attr"; }

	/** set the "can edit" status of the specified attribute
	 * @param {boolean} enableEdit if true enable editor else viewer
	 * @param {String} attrID object element name specifying desired attribute
	 */
	this.updateEditMode = function( enableEdit, attrID )
	{
		var dual = this[ attrID ];//originally set in embedAttr()
		if (dual)
		dual.setEditMode( this.model.getEditRule(attrID), enableEdit );
	}

	/**
	 * Select the value of the given attribute into the given select model.
	 * Since global shared menu data models are just temp utility models,
	 * shared by all menu instances, we must manually set each to the
	 * current value of the REAL data model (iff this is the ONE
	 * AND ONLY currently-being-edited EDO). In other words, this
	 * trick of sharing a single data model is possible because we
	 * only allow one menu of this type to be editable at a time.
	 * @param {MVCSelectionModel} selectModel the shared menu model
	 * @param {String} attrID object element name specifying desired attribute
	 */
	this.cascadedMenuSelect = function(selectModel,attrID)
	{
		this[attrID].editor.watchModel( selectModel );
		selectModel._select( this.model[ attrID ], true );

//		selectModel._select( this.model[ attrID ] );
//		selectModel._setValue( this.model[ attrID ] );
//		this[attrID].setModels( this.model[ attrID ] );
	}

	/**
	 * Select the value of the given attribute into the given select model.
	 * Since global shared menu data models are just temp utility models,
	 * shared by all menu instances, we must manually set each to the
	 * current value of the REAL data model (iff this is the ONE
	 * AND ONLY currently-being-edited EDO). In other words, this
	 * trick of sharing a single data model is possible because we
	 * only allow one menu of this type to be editable at a time. 
	 * @param {MVCSelectionModel} selectModel the shared menu model
	 * @param {String} attrID object element name specifying desired attribute
	 */
	this.sharedMenuSelect = function(selectModel,attrID) {
		selectModel._select( this.model[ attrID ], true );
	}

	this.preSelect = function(edo){ return; /*override as needed*/ }

	this.paintHTML = function()
	{
		var widget        = this.getWidget(); if (!widget) {grvError("no["+this.getWidgetID()+"]");return;}
		var edo           = this.model;
		var isSelected    = gEDOSelected.isSelected( this.xi );
		var isDirty       = edo && edo.inEdit();
		var isBeingEdited = isSelected && isDirty;
		var isVisible     = edo && edo.isViewable();
		this.setVisible( isVisible );

		if ( isVisible )
		{
			widget.title = edo.toolTip( this.xi );
			grvSelectElem( widget, isSelected, kReadOnly ? kROUnselectedColor : kUnSelectedColor );

			if (edo.expanded)
			{
				this.setSubViewsVisible(true);
				if (isBeingEdited) this.preSelect();
			}
			else
			{
				this.setSubViewsVisible(false);
				grvSetVisibility( this.innerID(), false );
			}

			//set the edit rule for each attribute
			var propz = edo.clazz.Properties;
			for (var i=0; i<propz.length; ++i)
			  this.updateEditMode( isBeingEdited, propz[i] );

			grvEditElem( grvGetHook(this.clickID()), isSelected, isDirty, !edo.isValid(), kColorDaffodil );
		}
	}

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

	this.fieldWrapper = function( dual,f,newrow,optSpan ){
		var colSpan = optSpan ? (' colspan="'+optSpan+'"') : '';
		var s = '<td'+colSpan+' class="edoInnerTD">'
			  +'<label class="edoFieldlabel">'+f+'</label><br/>'+dual+'&nbsp;</td>';
		return s + (newrow?'</tr><tr class="edoInnerTR">':'');
	}

	this.buildNulField = function( newrow, optSpan ){
		var colSpan = optSpan ? (' colspan="'+optSpan+'"') : '';
		var s = '<td'+colSpan+' class="edoBlankInnerTD"><br/>&nbsp;</td>';
		return s + (newrow?'</tr><tr class="edoBlankInnerTR">':'');
	}

	this.buildTxtField = function( f,rows,cols,max,newrow,optSpan ){
		var dual = mvcEmbedTextDualEditor( this, f, rows, cols, max, 'edoFieldStrVal' );
		return this.fieldWrapper(dual,f,newrow,optSpan);
	}

	this.buildYN_Field = function( f,newrow,optSpan ){
		var dual = mvcEmbedBoolDualEditor( this, f, 5, 'edoFieldStrVal' );
		return this.fieldWrapper(dual,f,newrow,optSpan);
	}

	this.buildBoxField = function( f,newrow,optSpan ){
		var dual = mvcEmbedChkBoxDualEditor( this, f, 5, 'edoFieldStrVal' );
		return this.fieldWrapper(dual,f,newrow,optSpan);
	}

	this.buildStrField = function( f,min,max,newrow,optSpan ){
		var dual = mvcEmbedStringDualEditor( this, f, min, max, 'edoFieldStrVal' );
		return this.fieldWrapper(dual,f,newrow,optSpan);
	}

	this.buildPopField = function( f,newrow,listModel,optSpan,optEvtHandlerName,optInitDesc ){
		var dual = mvcEmbedDualMenu( this, f, listModel, 'edoFieldPopVal',optEvtHandlerName,optInitDesc );
		return this.fieldWrapper(dual,f,newrow,optSpan);
	}

	this.buildUprField = function( f,min,max,newrow,optSpan ){
		var dual = mvcEmbedStringDualEditor( this, f, min, max, 'edoFieldStrVal', grvUpperKeyFilter );
		return this.fieldWrapper(dual,f,newrow,optSpan);
	}

	this.buildDayField = function( f,newrow,optSpan ){
		var dual = mvcEmbedDateDualEditor( this, f, 'edoFieldStrVal' );
		return this.fieldWrapper(dual,f,newrow,optSpan);
	}

	this.buildAmtField = function( f,newrow,optSpan ){
		var dual = mvcEmbedDollarDualEditor( this, f, 'edoFieldAmtVal',grvFormatDollar );
		return this.fieldWrapper(dual,f,newrow,optSpan);
	}

	this.buildPctField = function( f,newrow,optSpan ){
		var dual = mvcEmbedDollarDualEditor( this, f, 'edoFieldPctVal',grvFormatPercent );
		return this.fieldWrapper(dual,f,newrow,optSpan);
	}

	/** push into given array the HTML needed for all EDO fields
	 * <p>ABSTRACT: sub classes of EDOView should override this</p>
	 */
	this.buildFields  = function( HTML )
	{ grvMustOverride("EDOView.buildFields"); }


	this.buildToggle = function()
	{
		var img = this.model.isExpanded()
				? "<img align='middle' src='"+kMVCImgPath+EDOView.kImgExpanded+"'>"
				: "<img align='middle' src='"+kMVCImgPath+EDOView.kImgCollapsed+"'>";

		return '<span title="Click to Toggle; Ctrl-Click to toggle all." onclick="'
		+this.buildToggleEvent()+'">'+img+'&nbsp;</span>';
	}

	this.buildTitle = function(){
		var edo   = this.model;
		var title = EDOView.Title( edo.clazz );

		if (!edo.isExpanded()){
			var suffix = edo.toString();
			if ( grvIsEmpty(suffix) ) suffix  = edo.toolTip( this.xi );
			else if (edo.inEdit()) suffix += (" (Status:"+edo.state()+")");
			title += (": "+suffix);
		}

		return '<fieldset class="edoFieldset" id="'+this.getWidgetID()+'">'
			  +'<legend class="edoFieldsetLegend" id="'+this.clickID()+'">'
			  + this.buildToggle()
			  +'<span onclick="'+this.buildClickEvent()+'">'+title+'</span></legend>';
	}

	this.buildClickEvent = function(){
		return "onEDOClick("+ this.xi.asEventArgs() +")";
	}
	this.buildToggleEvent = function(){
		return "onEDOToggle("+ this.xi.asEventArgs() +")";
	}

	this.buildHTMLstr = function()
	{
		var vwID = this.getWidgetID();
		var HTML  = new Array();
		HTML.push( '<tr><td>' );
		HTML.push( this.buildTitle() );
		HTML.push( '<table id="' + this.innerID()+ '" cellspacing=5 class="edoInnerTABLE"><tr class="edoInnerTR">' );
  		if (this.model.isExpanded()) this.buildFields( HTML );
		HTML.push( "</tr></table></fieldset></td></tr>" );
		return HTML.join('');
	}
}


Class(EDOHolderView,["EDOHolderModel"])
.Extends(MVCView);
/**
 * @class This class produces the view of an EDOHolderModel.
 * @extends MVCView
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOHolderView()
{
	/** @param {EDOHolderModel} edoHolderModel EDOHolderModel we are to observe */
	this.konstructor = function( edoHolderModel )
	{
		this.souper("Main Data Panel View");

		// init instance variables
		this.watchModel( edoHolderModel );
	}

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

	/** push into given array the HTML needed for all EDOs in holder
	 * <p>ABSTRACT: sub classes of EDOHolderView should override this</p>
	 */
	this.buildFields  = function( HTML )
	{ grvMustOverride("EDOHolderView.buildFields"); }

	this.buildHTMLstr = function()
	{
		var vwID = this.getWidgetID();
		var HTML = new Array(30);
		var clas = "edoDatapanel" + (kReadOnly ? "RO" : "");
		HTML.push( '<table class="'+clas+'" cellpadding="5" cellspacing="0" width="100%">' );
  		this.buildFields( HTML );
		HTML.push( '</table>' );
		return HTML.join('');
	}
}

Class(EDOCountersView,["EDOHolder Selection Model"])
.Extends(MVCView);
/**
 * @class This class produces a view of the current selection
 * and total size of a selection model (ie "I of N").
 * @extends MVCView
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOCountersView()
{
	/** @param {EDOHolderSelectionModel} sModel selection model */
	this.konstructor = function( sModel )
	{
		this.souper();

		// init instance variables
		this.watchModel( sModel );
	}

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

	this.buildHTMLstr = function(){
		var sModel = this.model;
		var x = sModel.hasSelection() ? (sModel.getIndex()+1) : 0;
		return sModel.name+x+"&nbsp;of&nbsp;"+sModel.getCount();
	}
}


Class(EDOControlPanelView,["EDOHolder data model"])
.Extends(MVCView);
/**
 * @class This class produces the view of the top control panel.
 * @extends MVCView
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOControlPanelView()
{

	// init "static" Class methods/elements
	if (!EDOControlPanelView.kImgBtnSave){//keep this line above jsdoc comments!

		EDOControlPanelView.kImgBtnSave		= 'save.gif';
		EDOControlPanelView.kImgBtnCancel	= 'cancel.gif';
		EDOControlPanelView.kImgBtnDelete	= 'delete.gif';
		EDOControlPanelView.kImgBtnEdit		= 'edit.gif';
		EDOControlPanelView.kImgBtnExit		= 'exit.gif';
		EDOControlPanelView.kImgBtnInsert	= 'insert.gif';
		EDOControlPanelView.kImgBtnUndo		= 'undo.gif';
		EDOControlPanelView.kImgBtnRedo		= 'redo.gif';
	}

	/** @param {EDOHolderModel} edoHolderModel EDOHolder we are to observe
	 *  @param {Function} optCounterViewClass optional "class" of CountersView to use
	 *  @param {MVCPopupMenuController} optMenuCtrlr optional menu override
	 */
	this.konstructor = function( edoHolderModel, optCounterViewClass, optMenuCtrlr )
	{
		this.souper("Main Control Panel View");

		// init instance variables
		this.watchModel( edoHolderModel );
		this.cntrPanelClass = optCounterViewClass;
		this.optMenuOveride = optMenuCtrlr;
	}

	this.buildHTMLstr = function()
	{
		var vwID = this.getWidgetID();
		var HTML = new Array(30);

		HTML.push(
//		'<table bordercolordark="#eeeeee" bordercolorlight="#000000" bgcolor="#cccccc" valign="MIDDLE" align="CENTER" cellpadding="0" cellspacing="0" border="3" width="100%" class="edoControlPanel"><tr>'
		'<table bordercolordark="#eeeeee" bordercolorlight="#000000" cellpadding="0" cellspacing="0" border="3" class="edoControlPanel"><tr>'
//		'<table border="3" cellpadding="0" cellspacing="0" class="edoControlPanel"><tr>'
		 );

		//SAVE BTNS PANEL
		HTML.push( '<td align="center" nowrap="nowrap">' );

//		HTML.push( this.embedHTML( vwID+".saveBtns",
//						new EDOSaveBtnsView( this.model ) ) );
			HTML.push( mvcSPACER() );
			HTML.push( this.embedHTML( vwID+".save",
//				new MVCImgButtonController( EDOControlPanelView.kImgBtnSave,
				new MVCFormButtonController( "Save",
					"Save changes", "onEDOHolderSaveBtnPressed",
					this.model, "Save" ) ) );
			HTML.push( mvcSPACER() );
			HTML.push( this.embedHTML( vwID+".cancel",
//				new MVCImgButtonController( EDOControlPanelView.kImgBtnCancel,
				new MVCFormButtonController( "Cancel",
					"Cancel changes", "onEDOHolderCancelBtnPressed",
					this.model, "Cancel" ) ) );
			HTML.push( mvcSPACER() );
			HTML.push( this.embedHTML( vwID+".exit",
//				new MVCImgButtonController( EDOControlPanelView.kImgBtnExit,
				new MVCFormButtonController( "Exit",
					"Exit "+kAppName, "onEDOHolderExitBtnPressed",
					null, "Exit" ) ) );
			HTML.push( mvcSPACER() );

		HTML.push( '</td>' );

		//UNDO BTNS PANEL
		HTML.push( '<td align="center" nowrap="nowrap">' );
//		HTML.push( this.embedHTML( vwID+".undoBtns",
//						new EDOUndoBtnsView() ) );
			HTML.push( mvcSPACER() );
			HTML.push( this.embedHTML( vwID+".undo",
//				new MVCCmdImgButtonController( EDOControlPanelView.kImgBtnUndo, true ) ) );
				new MVCCmdButtonController( true ) ) );
			HTML.push( mvcSPACER() );
			HTML.push( this.embedHTML( vwID+".redo",
//				new MVCCmdImgButtonController( EDOControlPanelView.kImgBtnRedo, false ) ) );
				new MVCCmdButtonController( false ) ) );
			HTML.push( mvcSPACER() );

		HTML.push( '</td>' );

		//EDIT BTNS PANEL
		HTML.push( '<td align="center" nowrap="nowrap">' );
//		HTML.push( this.embedHTML( vwID+".editBtns",
//						new EDOEditBtnsView( this.model ) ) );
			HTML.push( mvcSPACER() );
			HTML.push( this.embedHTML( vwID+".delete",
//				new EDOImgButtonController( EDOControlPanelView.kImgBtnDelete,
				new EDOButtonController( "Delete",
					"Delete Selected Item", "onEDODeleteBtnPressed",
					gEDOSelected, kReadOnly, "Delete" ) ) );
			HTML.push( mvcSPACER() );
			HTML.push( this.embedHTML( vwID+".edit",
//				new EDOImgButtonController( EDOControlPanelView.kImgBtnEdit,
				new EDOButtonController( "Edit",
					"Edit Selected Item", "onEDOEditBtnPressed",
					gEDOSelected, kReadOnly, "Edit", true ) ) );
			HTML.push( mvcSPACER() );
			HTML.push( this.embedHTML( vwID+".insert",
//				new EDOImgButtonController( EDOControlPanelView.kImgBtnInsert,
				new EDOButtonController( "Insert",
					"Insert Item", "onEDOInsertBtnPressed",
					gEDOSelected, kReadOnly, "Insert" ) ) );
			HTML.push( mvcSPACER() );

		HTML.push( '</td>' );

		//TOP MENU PANEL
		HTML.push( '<td width="0px"></td>' );
		HTML.push( '<td align="center" nowrap="nowrap">' );
		HTML.push( this.embedHTML( vwID+".topMenu",
			( this.optMenuOveride ? this.optMenuOveride :
			 new EDOHolderSelectMenuController("top menu") )
		) );
		HTML.push( '</td>' );
		HTML.push( '<td width="0px"></td>' );

		//COUNTERS PANEL
		//sneaky...add a debug controller wrapper
		HTML.push( '<td align="center">' );
		HTML.push( '<span onclick="if (event.ctrlKey) grvBreakpoint()">' );
		HTML.push( mvcSPACER() );
		HTML.push(
		 this.embedHTML( vwID+".counter",
		 	( this.cntrPanelClass ? this.cntrPanelClass :
			new EDOCountersView( this.model.model ) )
		) );
		HTML.push( mvcSPACER() );
		HTML.push( '</span></td>' );

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


Class(EDOAppPanelView,["EDOHolderModel","EDOHolderView Class"])
.Extends(MVCView);
/**
 * @class This class produces the view of the entire application page.
 * @extends MVCView
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOAppPanelView()
{
	/** @param {EDOHolderModel} edoHolderModel EDOHolder we are to observe
	 *  @param {Function} edoHolderViewClass "class" of EDOHolderView to use
	 *  @param {Function} optCounterViewClass optional "class" of EDOCountersView to use
	 *  @param {MVCPopupMenuController} optMenuCtrlr optional menu override
	 */
	this.konstructor = function( edoHolderModel, edoHolderViewClass, optCounterViewClass, optMenuCtrlr )
	{
		this.souper("EDOAppPageView");

		// init instance variables
		this.watchModel( edoHolderModel );
		this.cntrPanelClass = optCounterViewClass;
		this.optMenuOveride = optMenuCtrlr;
		this.dataPanelClass = edoHolderViewClass;
	}

	this.buildHTMLstr = function()
	{
		var vwID = this.getWidgetID();
		var HTML = new Array(100);

//		HTML.push( '<body bgcolor="red" onLoad="onEDOHolderPageLoad()" onbeforeUnload="onEDOHolderPageBeforeUnLoad()" onKeyPress="onMVCGlobalKeyPress()">' );
		HTML.push( '<form method="POST" onsubmit="return false" action="foo">' );//WHY IS THIS NEEDED?

		HTML.push( this.embedHTML( vwID+".ctrl",
			new EDOControlPanelView( this.model, this.cntrPanelClass, this.optMenuOveride ) ) );

/* UNCOMMENT THIS TO SEE HOW UI COMPARES TO STD WINDOW SIZES */
//		HTML.push( mvcREDSPACER600() + "<br/>" + mvcREDSPACER768() );

		HTML.push( '<div style="width:100%; ">' );
		HTML.push( this.embedHTML( vwID+".err",
			new MVCScalarView( gEDOServerError, grvFormatErrorStr, "Server Error View" ) ) );
		HTML.push( '</div>' );

		<!-- DATA PANEL -->
		HTML.push( '<div style="width:100%; "'
		  + 'style="overflow-x:auto; overflow-y:auto; width:100%; height:expression(document.body.clientHeight-80);">' );
		HTML.push( this.embedHTML( vwID+".data",
			new this.dataPanelClass( this.model ) ) );
		HTML.push( '</div>' );
		HTML.push( '</form>' );
//		HTML.push( '</body>' );
		return HTML.join('');
	}
}

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

Class(EDOButtonController,["button image filename", "descriptive text",
						 "event handler","enable model","readonly flag"])
.Extends(MVCFormButtonController);
/**
 * @class This class is a button controller for EDO "edit" buttons
 * which shouldn't be enabled while app is in "read only" mode.
 * @extends MVCFormButtonController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOButtonController()
{
	/**
	 * @param {String} label label of the button
	 * @param {EDOSelectedModel} enableModel "is enabled" data model
	 * @param {String} evtHandlerName name of event handler function
	 * @param {String} desc tooltip description for this button
	 * @param {boolean} roFlag read-only mode flag
	 * @param {String} optName optional name of this instance
	 * @param {boolean} optIsEdit editor flag says dont enable if already editing
	 */
	this.konstructor = function ( label, desc, evtHandler, enableModel, roFlag, optName, optIsEdit ) {
		this.souper( label, desc, evtHandler, enableModel, optName );
		this.isEdit = optIsEdit;
		this.readOnly = roFlag;
	}

	/** Return whether this button should be enabled.
	 * If in readonly mode then never enable.
	 * If an entity is selected then enable
	 * UNLESS this is the edit button and we are already editing.
	 * @type boolean
	 */
	this.isEnabled = function()
	{
		var enabled = this.readOnly ? false : this.model.isTrue(); //anything selected?
		if (enabled && this.isEdit && this.model.inEdit()) enabled = false;
		return enabled;
	}
}


Class(EDOImgButtonController,["button image filename", "descriptive text",
						 "event handler","enable model","readonly flag"])
.Extends(MVCImgButtonController);
/**
 * @class This class is an MVCImgButtonController for EDO "edit" buttons
 * which shouldnt be enabled while app is in "read only" mode.
 * @extends MVCImgButtonController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOImgButtonController()
{
	/**
	 * @param {EDOSelectedModel} enableModel "is enabled" data model
	 * @param {String} evtHandlerName name of event handler function
	 * @param {String} imgFN filename of the (enabled) button image
	 * @param {String} desc tooltip description for this button
	 * @param {boolean} roFlag read-only mode flag
	 * @param {String} optName optional name of this instance
	 * @param {boolean} optIsEdit editor flag says dont enable if already editing
	 */
	this.konstructor = function ( imgFN, desc, evtHandler, enableModel, roFlag, optName, optIsEdit ) {
		this.souper( imgFN, desc, evtHandler, enableModel, optName );
		this.isEdit = optIsEdit;
		this.readOnly = roFlag;
	}

	/** Return whether this button should be enabled.
	 * If in readonly mode then never enable.
	 * If an entity is selected then enable
	 * UNLESS this is the edit button and we are already editing.
	 * @type boolean
	 */
	this.isEnabled = function()
	{
		var enabled = this.readOnly ? false : this.model.isTrue(); //anything selected?
		if (enabled && this.isEdit && this.model.inEdit()) enabled = false;
//grvTraceMVC("Btn["+this.altText+"].enabled="+enabled);
		return enabled;
	}
}


Class(EDOHolderSelectMenuController)
.Extends(MVCPopupMenuController);
/**
 * @class This class manages a popup menu controller for
 * the Selection List that chooses the current EDOHolder.
 * @extends MVCPopupMenuController
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOHolderSelectMenuController()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName ) {
		 this.souper( gEDOHolderSelected, optName );
	}

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

	/** return event handler invocation string specific to this controller @type String */
	this.getEvtHandlerStr = function(){ return "onEDOHolderSelect(this.value)"; }
}

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

Class(EDOSuperMenuCmd,["view ID"]).Extends(MVCScalarEditCmd);
/**
 * @class This abstract class is a base for menu commands that affect other EDO attributes.
 * <ul>The following methods MUST be overridden
 * <li>{@link #updateEDO}</li>
 *</ul><p>
 * @extends MVCScalarEditCmd
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 1.0
 */
function EDOSuperMenuCmd()
{
	/** @param {String} viewID viewID of menu generating this event */
	this.konstructor = function( viewID ) {
		this.souper( viewID );
		this.edo    = this.dual.model.base;
		this.oldEDO = this.edo.clone();
	}

	/** copy values that can change when menu selection is changed
	 * @param {EDO} exemplar where to copy attributes from
	 * @param {boolean} isRedo redo if true else undo
	 */
	this.updateEDO = function( exemplar, isRedo )
	{ grvMustOverride("EDOSuperMenuCmd.updateEDO"); }

	/** initialize EDO attributes if needed. */
	this.prefillEDO = function(){}

	this.updateOtherController = function( attrID, value ) {
		this.dual.parentView[attrID].setModels( value );//HACK!!
	}

	this.doit = function(){
		this.updateController( this.newValue );
		this.prefillEDO();
	}
	this.undo = function(){
		this.newEDO = this.edo.clone();
		this.updateEDO( this.oldEDO, false );
		this.updateController( this.oldValue );
	}
	this.redo = function(){
		this.updateEDO( this.newEDO, true );
		this.updateController( this.newValue );
	}
}


Class(EDOHolderCloseWindowIfSavedCmd).Extends(MVCCommand);
/**
 * @class This class implements the "close this window
 * if all EDOs in the global EDOHolder are saved" command.
 * NOTE: It isnt undoable (because we may close this window!).
 * @extends MVCCommand
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOHolderCloseWindowIfSavedCmd()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName ) {
		this.souper( optName, (gEDOHolder.isDirty()
				   ? "You must Save or Cancel data changes before leaving."
				   : null ) );
		this.canUndo = false;
	}

	this.doit = function() {
		if (history.length) history.go(-1); else window.close();
	}
}


Class(EDOHolderCancelAllChangesCmd).Extends(MVCCommand);
/**
 * @class This class implements the "revert" command.
 * NOTE: It isnt undoable (because we lose all edits!).
 * @extends MVCCommand
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOHolderCancelAllChangesCmd()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName ) {
		this.souper( optName );
		this.canUndo = false;
		if (!confirm("Are you sure you want to cancel all changes since last Save?"))
		  this.state = -1;
	}
							//causes reload of original data
	this.doit = function(){	gEDOHolderSelected.reselect(); }
}


Class(EDOHolderSaveAllChangesCmd).Extends(MVCCommand);
/**
 * @class This class implements the "save all changes
 * made to EDOs in the global EDOHolder" command.
 * NOTE: It isnt undoable (because we lose all edits!).
 * @extends MVCCommand
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOHolderSaveAllChangesCmd()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName ) {
		this.souper( optName, (gEDOHolder.isValid() ? null
			: "Can't save while any data is invalid." ) );
		this.canUndo = false;
	}

	this.doit = function() {
		gEDOSelected.unselect();
		gEDOHolder.save();//which implies a reload of data
	}
}


Class(EDOHolderSelectCmd,["selected holder Index"]).Extends(MVCCommand);
/**
 * @class This class implements the "select new EDO Holder" command.
 * NOTE: It isnt undoable (because we lose all edits!).
 * @extends MVCCommand
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOHolderSelectCmd()
{
	/** @param {Object} selectedHolderIndex index of EDOHolder
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function( selectedHolderIndex, optName )
	{
		this.souper( optName, (gEDOHolder.isTrue()
				   ? "You must Save or Cancel data changes first."
				   : null )  );
		this.theIndex = selectedHolderIndex;
		this.canUndo  = false;
	}

	this.doit = function(){ gEDOHolderSelected.select( this.theIndex ); }
}


Class(EDOHolderSelectInitialCmd).Extends(MVCCommand);
/**
 * @class This class implements the "select initially specified EDOHolder" command.
 * NOTE: It isnt undoable (because we dont need it to be).
 * @extends MVCCommand
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOHolderSelectInitialCmd()
{
	/** @param {String} optName optional name of this instance */
	this.konstructor = function( optName ){
		this.souper( optName );
		this.canUndo = false;
	}

	this.doit = function(){ gEDOHolderSelected.selectInitial(true); }
}


Class(EDOHolderLoadReplyCmd,["xml request object"]).Extends(MVCAJAXReplyCmd);
/**
 * @class This Command processes the reply of the "load EDOHolder data" request.
 * NOTE: It isnt undoable (because we lose all edits!).
 * @extends MVCAJAXReplyCmd
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOHolderLoadReplyCmd()
{
	/** @param {XMLreq} xmlReq the XML request being replied to
	 *  @param {String} optName optional name of this instance
	 */
	this.konstructor = function( xmlReq, optName ) {
		this.souper( xmlReq, gEDOHolder.loadXslDOM, optName );
	}
}

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

Class(EDOCmd).Extends(MVCCommand);
/**
 * @class This is the abstract base class for EDO commands.
 * @extends MVCCommand
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOCmd()
{
	/** @param {String} cmdDesc general description of this command
	 *  @param {String} optErrMsg if not null then cancel this cmd
	 */
	this.konstructor = function( cmdDesc, optErrMsg ){
		this.souper( cmdDesc, optErrMsg );
		if (optErrMsg) return;

		//save current EDO selection
		this.cmdX = this.oldX = gEDOSelected.getIndex();
	}

	/** return details portion of command description @type String */
	this.details = function(){ return this.cmdX.asString(); }
}


Class(EDOToggleSelectCmd,["edo index"]).Extends(EDOCmd);
/**
 * @class This class implements the toggle select EDO cmd.
 * @extends EDOCmd
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOToggleSelectCmd()
{
	/** @param {boolean} autoEdit if true then automatically put EDO in edit mode
	 *  @param {EDOIndex} xi index of EDO to toggle selection
	 */
	this.konstructor = function( autoEdit, xi )
	{
		this.souper( "toggle selection"+(autoEdit?" and edit":"") );
		this.cmdX   = xi;
		this.doEdit = autoEdit ? this.canEdit() : false;
	}

	/** @return true iff we can put selection into edit mode @type boolean */
	this.canEdit = function()
	{
		//if we are about to "deselect" EDO then we cant edit
		if ( gEDOSelected.isSelected(this.cmdX) ) return false;

		//if we are not allowed to edit EDO then we cant edit
		var err = gEDOSelected.canEdit(this.cmdX);
		if (err) { alert(err); return false; }

		//if EDO is already in edit mode then we cant (re)edit
		if (gEDOSelected.inEdit(this.cmdX)) return false;

		return true;
	}

	this.doit = function() {
		gEDOSelected.toggleSelect( this.cmdX );
		if (this.doEdit) gEDOSelected.  editSelected();
	}
	this.undo = function() {
		if (this.doEdit) gEDOSelected.uneditSelected();
		gEDOSelected.toggleSelect( this.oldX );
	}
}

Class(EDOToggleExpandCmd,["toggle all flag","edo index"]).Extends(EDOCmd);
/**
 * @class This class implements the toggle EDO display cmd.
 * @extends EDOCmd
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOToggleExpandCmd()
{
	/** @param {boolean} toggleAll if true then toggle all EDOs in EDOHolder
	 *  @param {EDOIndex} xi index of EDO to toggle collapse
	 */
	this.konstructor = function( toggleAll, xi )
	{
	  this.souper("toggle display");
	  if ( gEDOHolder.canToggleExpand( toggleAll, xi ) )
	  {
		   this.all  = toggleAll;
		   this.cmdX = xi;
	  }
	  else this.state = -1;
	}

	this.doit    = function(){ gEDOHolder.toggleExpand( this.all, this.cmdX ); }
	this.undo    = function(){ this.doit(); }
	this.details = function(){ return this.all ? "ALL" : this.cmdX.asString(); }
}


Class(EDOEditCmd).Extends(EDOCmd);
/**
 * @class This class implements the edit EDO cmd.
 * @extends EDOCmd
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOEditCmd()
{
	this.konstructor = function(){
		this.souper("edit selection", gEDOSelected.canEditSelected());
	}
	this.doit = function(){ gEDOSelected.  editSelected(); }
	this.undo = function(){ gEDOSelected.uneditSelected(); }
}


Class(EDODeleteCmd).Extends(EDOCmd);
/**
 * @class This class implements the delete EDO cmd.
 * @extends EDOCmd
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDODeleteCmd()
{
	this.konstructor = function(){
		this.souper("delete selection", gEDOSelected.canDeleteSelected());
	}
	this.doit = function(){
		gEDOSelected.deleteSelected();//leaves nothing selected
	}
	this.undo = function(){
		gEDOSelected.undelete( this.oldX );
		gEDOSelected.select  ( this.oldX );
	}
}


Class(EDOCreateCmd,["append flag"]).Extends(EDOCmd);
/**
 * @class This class implements the append and insert EDO cmds.
 * NOTE: If this is creating a new EDOholder, this command won't
 * be undoable (because we havent made EDO/EDOholder the same
 * via the Composite design pattern!).
 * @extends EDOCmd
 * @see #konstructor
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function EDOCreateCmd()
{
	//Static "Class" constants
	EDOCreateCmd.kAppend = true;
	EDOCreateCmd.kInsert = false;

	/** @param {boolean} appendFlag if true then append else insert EDO
	 *  @param {EDOIndex} optIndex optional index of EDO to insert after
	 */
	this.konstructor = function( appendFlag, optIndex )
	{
		var edoSelected = gEDOSelected;//for future un-globaling
		this.souper((appendFlag?"append":"insert")+" new ",
		  edoSelected.canCreateSelected(appendFlag,optIndex));

		if (kReadOnly) grvError("CreateCmd in read-only mode");
		this.append  = appendFlag;
		this.cmdX    = optIndex ? optIndex : edoSelected.getIndex();
		this.canUndo = edoSelected.canUncreateSelected(appendFlag,this.cmdX);
	}

	this.doit = function()
	{
		//create new EDO
		if (this.append)
			 gEDOSelected.appendSelected( this.cmdX );
		else gEDOSelected.newAfterSelected();
		//save new state
		this.newX = gEDOSelected.getIndex();
	}

	this.undo = function(){
		gEDOSelected.deleteSelected();
		gEDOSelected.select( this.oldX );
	}

	this.redo = function(){
		gEDOSelected.undelete( this.newX );
		gEDOSelected.select  ( this.newX );
	}
}

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


/** handle event for 'click on an EDO Add Button'
 * @param {EDOIndex} xi index of EDO to append after
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onEDOAddClick( xi ){
	mvcDoCmd( new EDOCreateCmd( EDOCreateCmd.kAppend, xi ) );
}

/** handle event for 'click on an EDO'
 * @param {EDOIndex} xi index of EDO clicked
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onEDOClick( xi ){
	mvcDoCmd( new EDOToggleSelectCmd( event.ctrlKey, xi ) );
}

/** handle event for 'toggle an EDO'
 * @param {EDOIndex} xi index of EDO to collapse/expand
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onEDOToggle( xi ){
	mvcDoCmd( new EDOToggleExpandCmd( event.ctrlKey, xi ) );
}

/** handle event for 'delete EDO button pressed'
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onEDODeleteBtnPressed(){
	mvcDoCmd( new EDODeleteCmd() );
}

/** handle event for 'edit EDO button pressed'
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onEDOEditBtnPressed(){
	mvcDoCmd( new EDOEditCmd() );
}

/** handle event for 'insert EDO button pressed'
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onEDOInsertBtnPressed()
{
// TODO dont insert until fix is found for insert goofing up the
// EDO indexes lodged in all those UNDO/REDO command objects!!!
//	mvcDoCmd( new EDOCreateCmd( EDOCreateCmd.kInsert ) );
	mvcDoCmd( new EDOCreateCmd( EDOCreateCmd.kAppend ) );
}


/** handle event for 'save button pressed'
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onEDOHolderSaveBtnPressed(){
	mvcDoCmd( new EDOHolderSaveAllChangesCmd() );
}

/** handle event for 'cancel button pressed'
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onEDOHolderCancelBtnPressed(){
	mvcDoCmd( new EDOHolderCancelAllChangesCmd() );
}

/** handle event for 'exit button pressed'
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onEDOHolderExitBtnPressed(){
	mvcDoCmd( new EDOHolderCloseWindowIfSavedCmd() );
}

/** handle event for 'user selects from the select menu
 * which loads a new EDOHolder'
 * @param {int} selectedHolderIndex the new menu-selection
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onEDOHolderSelect( selectedHolderIndex ){
	mvcDoCmd( new EDOHolderSelectCmd( selectedHolderIndex ) );
}

/** handle event for 'reply received from "load EDOHolder data" server-request'
 * @param {XMLreq} xmlReq the XML request being replied to
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onEDOHolderLoadReply( xmlReq ){
	mvcDoCmd( new EDOHolderLoadReplyCmd( xmlReq ) );
}

/** handle event for 'reply received from "save EDOHolder data" server-request'
 * @param {XMLreq} xmlReq the XML request being replied to
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onEDOHolderSaveReply( xmlReq )
{	//since we get back official new version of EDOHolder Data, just
	//do the same thing as asking for data load in the first place!
	mvcDoCmd( new EDOHolderLoadReplyCmd( xmlReq ) );
}

/** handle event for 'entering EDOHolder web page'
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
function onEDOHolderPageLoad()
{
	gMVCRootView.enable();

// HACK! windows get goofed up if we dont suppress
// the "please wait" window on launch...
_gGrvHackStartupInhibitWait = true;
	mvcDoCmd( new EDOHolderSelectInitialCmd() );
_gGrvHackStartupInhibitWait = false;
}

/** handle event for 'about to leave EDOHolder web page';
 * warn if attempting to leave with unsaved changes in the EDOHolder
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.0
 */
var _gEDOClosingWindow = true;
function onEDOHolderPageBeforeUnLoad()
{	//This cant be done in a Command since this logic must be completed
	//by the time that we return. [A Command may be queued and finished
	//later, and that is out of the callers control.]
	if (_gEDOClosingWindow) //e.g. calendar window launch
	{
	 if ( gEDOHolder.isTrue() )  // i.e. unsaved data
	  event.returnValue = "Edits are NOT saved. Are you sure you want to abandon your changes?";
	}
	else _gEDOClosingWindow = true;
}


The Gravey 2.0 Framework

Documentation generated by JSDoc on Sat Dec 8 21:51:44 2007