Overview | File | Class | Tree | Index | Help
| The Gravey 2.5 Framework and AIM RIA
PREV NEXT | FRAMES NO FRAMES

## 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.5
Requires:

- grvMVC.js

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( <event> e, <EDOIndex> xi )` handle event for 'click on an EDO Add Button'
`static void` | `onEDOClick( <event> e, <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( <grvXMLreq> xmlReq )` handle AJAX event for 'reply received from "load EDOHolder data" server-request'
`static Object` | `onEDOHolderPageBeforeUnLoad()`
`static void` | `onEDOHolderPageLoad()` handle event for 'entering EDOHolder web page'
`static void` | `onEDOHolderSaveBtnPressed()` handle event for 'save button pressed'
`static void` | `onEDOHolderSaveReply( <grvXMLreq> xmlReq )` handle AJAX event for 'reply received from "save EDOHolder data" server-request'
`static void` | `onEDOHolderSelect( <event> e, <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( <event> e, <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.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("grvEDO.js: Begin");

    ////////////////////////////////////////////
    /////////// 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 += MVCEditRule.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 grvXMLreq( 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.5
     */
    function EDOView()
    {
    	// init "static" Class methods/elements
    	if (!EDOView.ShellHTML) {//keep this line above jsdoc comments!

    		EDOView.kImgExpanded	= 'expandedX.gif';
    		EDOView.kImgCollapsed	= 'collapsedX.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 evtHandlerStr = "onEDOAddClick(event,"+ xi.asEventArgs() +")";
    			var HTML          = new Array();
    			HTML.push( '<tr><td><fieldset class="edoFieldset">' );
    			HTML.push( '<legend class="edoFieldsetLegend">' );
    			HTML.push( '<span  class="edoFieldsetLegendTitleSpan" onclick="'+evtHandlerStr+'">'+title+'</span></legend>' );
    			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 = grvIs_IE_Pre7()
    				? this.model.isExpanded() ? "<font face='Wingdings'>&#218;</font>" : "<font face='Wingdings'>&#216;</font>"
    				: this.model.isExpanded() ? "&#x25BC;" : "&#x25BA;";

    		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 class="edoFieldsetLegendTitleSpan" onclick="'+this.buildClickEvent()+'">'+title+'</span></legend>';
    	}

    	this.buildClickEvent = function(){
    		return "onEDOClick(event, "+ this.xi.asEventArgs() +")";
    	}
    	this.buildToggleEvent = function(){
    		return "onEDOToggle(event, "+ 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 class="edoControlPanel"><tr>');

    		//SAVE BTNS PANEL
    		HTML.push( '<td>' );
    		HTML.push( this.embedHTML( vwID+".save",
    				new MVCFormButtonController( "Save",
    					"Save changes", "onEDOHolderSaveBtnPressed",
    					this.model, "Save" ) ) );
    		HTML.push( this.embedHTML( vwID+".cancel",
    				new MVCFormButtonController( "Cancel",
    					"Cancel changes", "onEDOHolderCancelBtnPressed",
    					this.model, "Cancel" ) ) );
    		HTML.push( this.embedHTML( vwID+".exit",
    				new MVCFormButtonController( "Exit",
    					"Exit "+kAppName, "onEDOHolderExitBtnPressed",
    					null, "Exit" ) ) );
    		HTML.push( '</td>' );

    		//UNDO BTNS PANEL
    		HTML.push( '<td>' );
    		HTML.push( this.embedHTML( vwID+".undo",
    				new MVCCmdButtonController( true ) ) );
    		HTML.push( this.embedHTML( vwID+".redo",
    				new MVCCmdButtonController( false ) ) );
    		HTML.push( '</td>' );

    		//EDIT BTNS PANEL
    		HTML.push( '<td>' );
    		HTML.push( this.embedHTML( vwID+".delete",
    				new EDOButtonController( "Delete",
    					"Delete Selected Item", "onEDODeleteBtnPressed",
    					gEDOSelected, kReadOnly, "Delete" ) ) );
    		HTML.push( this.embedHTML( vwID+".edit",
    				new EDOButtonController( "Edit",
    					"Edit Selected Item", "onEDOEditBtnPressed",
    					gEDOSelected, kReadOnly, "Edit", true ) ) );
    		HTML.push( this.embedHTML( vwID+".insert",
    				new EDOButtonController( "Insert",
    					"Insert Item", "onEDOInsertBtnPressed",
    					gEDOSelected, kReadOnly, "Insert" ) ) );
    		HTML.push( '</td>' );

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

    		//COUNTERS PANEL
    		HTML.push( '<td>' );
    		//sneaky...add a debug controller wrapper
    		HTML.push( '<span>' );
    		HTML.push(
    		 this.embedHTML( vwID+".counter",
    		 	( this.cntrPanelClass ? this.cntrPanelClass :
    			new EDOCountersView( this.model.model ) )
    		) );
    		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);

    		document.body.className = kReadOnly ? "body-readonly" : "body-readwrite";

    //		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.5
     */
    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(event,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 {grvXMLreq} 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 {event} e browser event
     * @param {EDOIndex} xi index of EDO to append after
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.5
     */
    function onEDOAddClick( e, xi ){
    	mvcDoCmd( new EDOCreateCmd( EDOCreateCmd.kAppend, xi ) );
    }

    /** handle event for 'click on an EDO'
     * @param {event} e browser event
     * @param {EDOIndex} xi index of EDO clicked
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.5
     */
    function onEDOClick( e, xi ){
    	var ev = e || window.event;
    	var cmdKey = (typeof ev.metaKey == "undefined") ? false : ev.metaKey;
    	mvcDoCmd( new EDOToggleSelectCmd( ev.ctrlKey || cmdKey, xi ) );
    }

    /** handle event for 'toggle an EDO'
     * @param {event} e browser event
     * @param {EDOIndex} xi index of EDO to collapse/expand
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.5
     */
    function onEDOToggle( e, xi ){
    	var ev = e || window.event;
    	var cmdKey = (typeof ev.metaKey == "undefined") ? false : ev.metaKey;
    	mvcDoCmd( new EDOToggleExpandCmd( ev.ctrlKey || cmdKey, 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 {event} e browser event
     * @param {int} selectedHolderIndex the new menu-selection
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.5
     */
    function onEDOHolderSelect( e, selectedHolderIndex ){
    	mvcDoCmd( new EDOHolderSelectCmd( selectedHolderIndex ) );
    }

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

    /** handle AJAX event for 'reply received from "save EDOHolder data" server-request'
     * @param {grvXMLreq} xmlReq the XML request being replied to
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.5
     */
    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;//modern browsers are a pain with popups now..retire this technique
    }

    /** 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.5
     */
    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
    			return "Edits are NOT saved. Are you sure you want to abandon your changes?";
    	}
    	else _gEDOClosingWindow = true;
    }

    grvTraceCmp("grvEDO.js: End");

Overview | File | Class | Tree | Index | Help
| The Gravey 2.5 Framework and AIM RIA
PREV NEXT | FRAMES NO FRAMES

Documentation generated by JSDoc on Thu Jan 6 12:46:39 2011