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

## rats.js

#### Summary

Collection of application level classes. This collection implements a rich-internet-application, RATS (Reconcile Account Transaction System), which is a demonstration of the Gravey framework.
Version: 2.5
Requires:

- grvMVC.js
- grvAJAX.js

Author: Bruce Wallace (PolyGlotInc.com)

Class Summary
Account | DOMAIN-OBJECT: This class encapsulates the identifying attributes of a Account.
AccountSelectController | This class manages a popup menu controller specifically for the Account Selection List.
AccountSelectionModel | This class encapsulates the data model for "which Account is currently selected".
Balance | DOMAIN-OBJECT: This class encapsulates an Account balance.
BalanceListModel | DOMAIN-OBJECT: This class encapsulates the data model for the Balance list of a particular Account.
BalanceListView | This class produces a view of a BALANCE List.
BalanceView | This class produces a view of a Balance.
CancelAllAccountChangesCmd | This class implements the "revert this account" command.
CloseWindowIfDataSavedCmd | This class implements the "close this window if all data is saved" command.
ComplexView | This class produces a view of the complex type of a Transaction.
CountersView | This class produces a view of the current selection and total size of a selection model (ie "I of N").
CreateTranCmd | This class implements the append and insert transaction cmds.
DataPanelView | This class produces the view of the entire data panel.
DebugView | This class acts as quick and dirty debug view that pops up a window showing the current state of the arbitrary data model being watched.
DeleteTranCmd | This class implements the delete transaction cmd.
EditButtonController | This class is an MVCImgButtonController for "edit" buttons which shouldnt be enabled while app is in "read only" mode.
EditTranCmd | This class implements the edit transaction cmd.
LoadAccountDataCmd | This class implements the "load account data" command.
RatsCmd | This is the abstract base class for RATS commands.
SaveAllAccountChangesCmd | This class implements the "save changes" command.
SelectAccountCmd | This class implements the "select account" command.
SelectFirstUnreconciledCmd | This class implements the "select first unreconciled account" command.
ToDoView | This class produces a view of how many unreconciled accounts are left.
ToggleBalCmd | This class implements the toggle balance display cmd.
ToggleTranCmd | This class implements the toggle transaction selection cmd.
TotalsView | This class produces a view of the unreconciled totals for an entire Account.
Transaction | DOMAIN-OBJECT: This class encapsulates a Account "transaction".
TransactionListView | This class produces a view of a Transaction List.
TransactionSelectedModel | This class encapsulates the data model for the flag indicating whether a Transaction is selected.
TransactionView | This class produces a view of a Transaction.
TrTypeMenuCmd | This class implements the trans type menu edit command.
UnreconciledView | This class produces a view of the unreconciled totals of a Balance.

Method Summary
`static Object` | `_RATSRequest( <String> aRequestName, <Function> aReplyEventHandler, <String> aBANK, <String> anACCT, <String> optPostData, <boolean> optWaitFlag )` Make a REST-style AJAX request to the RATS server. NOTE: this is a Command helper function. NEVER call this from outside the context of a Command object!
`static void` | `_requestAccountLoad( <String> aBANK, <String> anACCT )` Make a Load-Account-Data request to the server. NOTE: this is a Command helper function. NEVER call this from outside the context of a Command object!
`static void` | `_requestAccountSave( <String> aBANK, <String> anACCT, <String> updates )` Make a Save-Account-Data request to the server. NOTE: this is a Command helper function. NEVER call this from outside the context of a Command object!
`static boolean` | `_selectFirstUnreconciled( <boolean> selectFirst )` Select the first unreconciled Account NOTE: this is a Command helper function. NEVER call this from outside the context of a Command object!
`static String` | `findComplexCode( <MVCCollection> transtypes )` static routine to search for the special "complex" code
`static void` | `onAccountLoadReply( <grvXMLreq> xmlReq )` handle AJAX event for 'reply received from "load account data" server-request'
`static void` | `onAccountSaveReply( <grvXMLreq> xmlReq )` handle AJAX event for 'reply received from "save account data" server-request'
`static void` | `onAccountSelect( <event> e, <int> selectedAccountKey )` handle event for 'user selects from the account menu'
`static void` | `onBalanceClick( <event> e, <int> balanceIndex )` handle event for 'click on a balance record'
`static Object` | `onBeforeUnLoad()` handle event for 'about to leave web page'; warn if attempting to leave with unsaved changes
`static void` | `onCancelBtnPressed()` handle event for 'cancel button pressed'
`static void` | `onDeleteBtnPressed()` handle event for 'delete button pressed'
`static void` | `onEditBtnPressed()` handle event for 'edit button pressed'
`static void` | `onExitBtnPressed()` handle event for 'exit button pressed'
`static void` | `onInsertBtnPressed()` handle event for 'insert button pressed'
`static void` | `onLoad()` handle event for 'entering web page'
`static void` | `onSaveBtnPressed()` handle event for 'save button pressed'
`static void` | `onTransactionClick( <event> e, <int> balanceIndex, <int> transIndex )` handle event for 'click on a transaction record'
`static Object` | `onTrTypeSelect( <event> e, <String> viewID )` handle event for 'user selects from the transaction type menu'
`static void` | `onUnreconciledClick( <event> e, <int> balanceIndex )` handle event for 'click on an unreconciled panel'

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

    /**
     * @file         rats.js
     * @fileoverview Collection of application level classes.
     * This collection implements a rich-internet-application,
     * RATS (Reconcile Account Transaction System), which is
     * a demonstration of the Gravey framework.
     * @author       Bruce Wallace  (PolyGlotInc.com)
     * @requires     grvMVC.js
     * @requires     grvAJAX.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("rats.js: Begin");

    /** global flag to enable/disable the totals column of the UI */
    var kEnableTotalsColumn = true;

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

    /** constant defining "broadcast" message separator character */
    var kMsgSep = "~";

    Class(Account,["Bank ID","Account #","Reconciled Flag"]);
    /**
     * @class DOMAIN-OBJECT: This class encapsulates the identifying
     * attributes of a Account.
     * This class implements the {@link MVCMenuItem} interface.
     * @extends GrvObject
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function Account()
    {
    	/** @param {String} bankID account's bank ID
    	 *  @param {String} acct account number
    	 *  @param {boolean} reconciled true if account is already reconciled
    	 */
    	this.konstructor = function( bankID, acct, reconciled )
    	{
    		// define instance variables
    		this.bank = bankID;
    		this.acct = acct;
    		this.todo = !reconciled; //todo===not-reconciled
    	}

    	/** set reconciled status to given value and "broadcast" */
    	this.updateStatus = function( reconciled ){
    		this.todo = ! reconciled;
    		this.broadcastReconciledStatus();
    	}

    	/** return "this" formatted for account menu item @type String */
    	this.getDescription = function() {
    		return grvDiamond(!this.todo) + this.toString();
    	}

    	/** Send Account Reconciled Status Message */
    	this.broadcastReconciledStatus = function() {
    		grvSendStatusMessage( this.toBroadcastString() );
    	}

    	/** return Account Reconciled Status Message.
    	 * msg format: "UP~acct~bank~reconciledFlag"
    	 * where reconciledFlag is encoded as 'Y'==true, 'N'==false...
    	 * @return message
    	 * @type String
    	 */
    	this.toBroadcastString = function()
    	{
    	 	var		s = new Array();
    				s.push( "UP" );
    				s.push( this.acct );
    				s.push( this.bank );
    				s.push( this.todo ?'N':'Y' );
    		return	s.join( kMsgSep );
    	}

    	/** return "this" as human-readable string @type String */
    	this.toString = function()
    	{
    	 	var		s = new Array();
    	 			s.push( "Acct" );
    				s.push( this.acct );
    				s.push( this.bank );
    		return	s.join( "-" );
    	}
    }

    /** static routine to search for the special "complex" code
     * @param {MVCCollection} transtypes Decode collection of transaction-types
     * @return the "code" of the special "complex" transaction type
     * @type String
     */
    function findComplexCode( transtypes )
    {
    	var code = 2; //heuristic ;-)
    	transtypes.iterate
    	(
    	  function( k, decode )
    	  {
    	  	if (decode.aux && decode.aux.length>0
    	  	 && decode.aux[ decode.aux.length-1 ] == 'C')
    	  	{
    		  	code = decode.code;
    		  	return true;
    	  	}
    	  }
    	);
    	return code;
    }

    // Constants defining IDs for the 4 balance properties
    var kBalanceIdA = 'BalanceA';
    var kBalanceIdB = 'BalanceB';
    var kBalanceIdC = 'BalanceC';
    var kBalanceIdD = 'BalanceD';

    // Constants defining IDs for the 2 code properties
    var kCodeIdTrType  = 'TransactionType';
    var kCodeIdComplex = 'ComplexType';

    Class(Balance,["Date","Balance A","Balance B","Balance C",
    "Balance D","Is Reconciled?","System","Transactions List Model"]);
    /**
     * @class DOMAIN-OBJECT: This class encapsulates an Account balance.
     * @extends GrvObject
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function Balance()
    {
    	/** @param {String} sdate balance date
    	 *  @param {Float} balA balance A
    	 *  @param {Float} balB balance B
    	 *  @param {Float} balC balance C
    	 *  @param {Float} balD balance D
    	 *  @param {boolean} reconciled true if balance is already reconciled
    	 *  @param {String} system where did balance come from
    	 *  @param {MVCListModel} rList {@link Transaction} list
    	 */
    	this.konstructor = function( sdate, balA, balB, balC, balD, reconciled, system, rList )
    	{
    		// define instance variables
    		this.sdate = sdate;
    		this.todo  = !reconciled;
    		this.whom  = system;
    		this.list  = rList;
    		this.open  = false;

    		this[kBalanceIdA] = balA;
    		this[kBalanceIdB] = balB;
    		this[kBalanceIdC] = balC;
    		this[kBalanceIdD] = balD;
    	}

    	/** return the debug details of "this" @type String */
    	this.dump = function()
    	{
    		return "S...["
    		      +"\n    date="+this.sdate
    		      +"\n    todo="+this.todo
    		      +"\n    whom="+this.whom
    		      +"\n    balA="+this[kBalanceIdA]
    		      +"\n    balB="+this[kBalanceIdB]
    		      +"\n    balC="+this[kBalanceIdC]
    		      +"\n    balD="+this[kBalanceIdD]
    		      +"\n    list="+this.list.dump()
    		      +"\n]...S\n";
    	}

    	/** return whether any of our transactions are in Edit mode @type boolean */
    	this.anyTransactionsInEdit = function()
    	{
    		var inEdit = false;
    		this.list.iterate( function(i,r){ if (r.inEdit()) return inEdit = true; } );
    		return inEdit;
    	}

    	/** return whether all of our transactions are valid @type boolean */
    	this.isValid = function()
    	{
    		var valid = true;
    		this.list.iterate( function(i,r){ if (!r.isValid()){valid = false; return true;} } );
    		return valid;
    	}

    	/** return the specified transaction @type Transaction
    	 * @param {int} rIndex index into transaction list for this balance
    	 */
    	this.getTransaction = function( rIndex )
    	{
    	    return this.list.getItem( rIndex );
    	}

    	/** return sum of balances for this balance @type Float */
    	this.getTotal = function()
    	{
    		var a = parseFloat( this[kBalanceIdA] );
    		var b = parseFloat( this[kBalanceIdB] );
    		var c = parseFloat( this[kBalanceIdC] );
    		var d = parseFloat( this[kBalanceIdD] );
    		return a + b + c + d;
    	}

    	/** return sum of all Net totals for this balance @type Float */
    	this.getNetTotal = function()
    	{
    		var a = parseFloat( this.getNetA() );
    		var b = parseFloat( this.getNetB() );
    		var c = parseFloat( this.getNetC() );
    		var d = parseFloat( this.getNetD() );
    		return a + b + c + d;
    	}

    	/** return sum of specified balance for this balance and
    	 * all its transactions.
    	 * @param {String} b attribute name of desired balance
    	 * @type Float
    	 */
    	this.getNet = function(b)
    	{
    		var sum = parseFloat( this[b] );//force the data type to number
    		this.list.iterate( function(i,r){ if (!r.inactive()) sum -= r[b]; } );
    		return sum;
    	}

    	/** convenience call to {@link #getNet} for balance D @type Float */
    	this.getNetD = function(){ return this.getNet( kBalanceIdD ); }

    	/** convenience call to {@link #getNet} for balance A @type Float */
    	this.getNetA = function(){ return this.getNet( kBalanceIdA ); }

    	/** convenience call to {@link #getNet} for balance B @type Float */
    	this.getNetB = function(){ return this.getNet( kBalanceIdB ); }

    	/** convenience call to {@link #getNet} for balance C @type Float */
    	this.getNetC = function(){ return this.getNet( kBalanceIdC ); }

    	/** return the formatted title for this balance @type String */
    	this.getTitle = function(){ return "Balances - "+this.sdate; }
    }

    Class(Transaction, [
    	"complex Code",
    	"transaction Type",
    	"amount A",
    	"amount B",
    	"amount C",
    	"amount D",
    	"edit Date",
    	"edit User",
    	"unique Key"
    ]);
    /**
     * @class DOMAIN-OBJECT: This class encapsulates a Account "transaction".
     * @extends GrvObject
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function Transaction()
    {
    	/** @param {String} complexCode complex code
    	 *  @param {String} transType "transaction type" code
    	 *  @param {Float} amountA balance A
    	 *  @param {Float} amountB balance B
    	 *  @param {Float} amountC balance C
    	 *  @param {Float} amountD balance D
    	 *  @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
    	 */
    	this.konstructor = function
    	(
    		complexCode,
    		transType,
    		amountA,
    		amountB,
    		amountC,
    		amountD,
    		editDate,
    		editUser,
    		theKey,
    		optEditStatus
    	){
    		// define instance variables
    		this.date  = editDate;
    		this.whom  = editUser;
    		this.key   = theKey;

    		// each of these have a "validity" attribute associated with it
    		this[ kCodeIdComplex ] = complexCode;
    		this[ kCodeIdTrType  ] = transType;

    		this[ kBalanceIdA ] = amountA;
    		this[ kBalanceIdB ] = amountB;
    		this[ kBalanceIdC ] = amountC;
    		this[ kBalanceIdD ] = amountD;

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

    	/** return the debug details of "this" @type String */
    	this.dump = function()
    	{
    		return "T...["
    		      +"\n    date="+this.date
    		      +"\n    whom="+this.whom
    		      +"\n     key="+this.key
    		      +"\n    edit="+this.editStatus
    		      +"\n     src="+this[kCodeIdTrType]
    		      +"\n     col="+this[kCodeIdComplex]
    		      +"\n    balA="+this[kBalanceIdA]
    		      +"\n    balB="+this[kBalanceIdB]
    		      +"\n    balC="+this[kBalanceIdC]
    		      +"\n    balD="+this[kBalanceIdD]
    		      +"\n]...R\n";
    	}

    	/** return a clone of this object @type Transaction */
    	this.clone = function()
    	{
    		return new Transaction(
    						this[ kCodeIdComplex  ],
    						this[ kCodeIdTrType   ],
    						this[ kBalanceIdA ],
    						this[ kBalanceIdB  ],
    						this[ kBalanceIdC    ],
    						this[ kBalanceIdD ],
    						this.date,
    						this.whom,
    						this.key,
    						this.editStatus
    					);
    	}

    	/** return a string of updates for this transaction formatted as<pre>
    	 * "{key}~{action}~{sdate}~{ttype}~{ccode}~{balA}~{balB}~{balC}~{balD}"
    	 * where the fields are defined as follows...
    	 *     {key}    = the transaction's unique key number
    	 *     {action} = the transaction's action code
    	 * (encode as strings 'C','D', and 'U' for values create, delete, and update.)
    	 *     {sdate}  = the transaction's parent balance date
    	 *     {ttype}  = the transaction's new transaction Type
    	 *     {ccode}  = the transaction's new complex code
    	 *     {balA}   = the transaction's new balance A
    	 *     {balB}   = the transaction's new balance B
    	 *     {balC}   = the transaction's new balance C
    	 *     {balD}   = the transaction's new balance D
    	 *</pre>
    	 * @type String
    	 * @param {Balance} parentBalance balance "this" is attached to
    	 */
    	this.asUpdateString = function( parentBalance )
    	{
    	 	var		uStr = new Array();
    				uStr.push( this.key ? this.key : "0" );
    				uStr.push( this.action()             );
    				uStr.push( parentBalance.sdate       );
    				uStr.push( this[ kCodeIdTrType   ]   );
    				uStr.push( this[ kCodeIdComplex  ]   );
    				uStr.push( this[ kBalanceIdA     ]   );
    				uStr.push( this[ kBalanceIdB     ]   );
    				uStr.push( this[ kBalanceIdC     ]   );
    				uStr.push( this[ kBalanceIdD     ]   );
    		return	uStr.join( kMsgSep );
    	}

    	/** set the validity code of the specified attribute
    	 * @param {String} ID transaction 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 transaction attribute name
    	 * @type String
    	 */
    	this.getValidity = function(ID){ return this[ID+MVCScalarModel.kValiditySuffix]; }

    	/** mark this transaction as deleted */
    	this.deleteMe = function(){
    		this.preDeleteStatus = this.editStatus;
    		this.editStatus      = this.editStatus==2 ? 3 : 1;
    	}

    	/** restore this transaction to its pre-deleted state */
    	this.undeleteMe = function(){
    		this.editStatus = this.preDeleteStatus;
    		delete this.preDeleteStatus;
    	}

    	/** return whether this transaction needs to update server
    	 * @return true if NO UPDATE is needed
    	 * @type boolean
    	 */
    	this.inactive = function(){ return this.editStatus==1 || this.editStatus==3; }

    	/** mark this transaction as being newly added */
    	this.addMe    = function(){ this.editStatus = 2; }

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

    	/** mark this record as having been edited */
    	this.editMe = function()
    	{
    		this.preEdit    = this.clone();//save previous state
    		this.editStatus = this.editStatus==2 ? 2 : 4;
    		this.whom       = kUserID;
    		this.date       = grvGetTodayAsMMDDYYYY();
    	}

    	/** restore this record to its pre-edited state */
    	this.uneditMe = function()
    	{
    		this[ kCodeIdComplex ] = this.preEdit[ kCodeIdComplex ];
    		this[ kCodeIdTrType  ] = this.preEdit[ kCodeIdTrType  ];
    		this[ kBalanceIdA    ] = this.preEdit[ kBalanceIdA    ];
    		this[ kBalanceIdB    ] = this.preEdit[ kBalanceIdB    ];
    		this[ kBalanceIdC    ] = this.preEdit[ kBalanceIdC    ];
    		this[ kBalanceIdD    ] = this.preEdit[ kBalanceIdD    ];
    		this.date              = this.preEdit.date;
    		this.whom              = this.preEdit.whom;
    		this.key               = this.preEdit.key;
    		this.editStatus        = this.preEdit.editStatus;
    		delete this.preEdit;
    	}

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

    	/** return whether this transaction can be viewed if desired @type String */
    	this.isViewable = function()
    	{
    		switch( this.editStatus )
    		{
    			case 0:
    			case 2:
    			case 4: return true;
    		}
    		return false;
    	}

    	/** return whether this transaction is a complex type @type boolean */
    	this.isComplex = function(){ return this[kCodeIdTrType]==kComplexKey; }

    	/** return the edit/validation rule for the specified balance.
    	 * If no balance is specified then return entire set of rules
    	 * for the transaction type that "this" is currently set to.
    	 * @type String
    	 * @param {String} optBalanceID optional object element name specifying desired balance
    	 */
    	this.getEditRule = function( optBalanceID )
    	{
    		var decode = kTransTypeMap.getItem( this[kCodeIdTrType] );
    		if (decode==null) return null;
    		var rules = decode.aux;
    		if (grvIsEmpty(rules) || rules.length<4) return null;

    		/*********************************************************
    		 *** NOTE: At this point, translation of the codes would
    		 *** need to take place if the codes used in the MVC lib
    		 *** were different than the codes used by the Reconcile
    		 *** configuration database but they're the same for now
    		 *********************************************************/

    		//if optional balance ID not specified then return all rules
    		if (grvIsEmpty(optBalanceID)) return rules;

    		//optional balance ID was specified so return specific rule
    		if (optBalanceID==kBalanceIdA) return rules.charAt(0);
    		if (optBalanceID==kBalanceIdB) return rules.charAt(1);
    		if (optBalanceID==kBalanceIdC) return rules.charAt(2);
    		if (optBalanceID==kBalanceIdD) return rules.charAt(3);
    		throw "bad balance ID in getEditRule()";
    	}

    	/** return sum of balances for this transaction @type Float */
    	this.getTotal = function()
    	{
    		var a = parseFloat( this[kBalanceIdA] );
    		var b = parseFloat( this[kBalanceIdB] );
    		var c = parseFloat( this[kBalanceIdC] );
    		var d = parseFloat( this[kBalanceIdD] );
    		return a + b + c + d;
    	}

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

    		return this.getValidity( kBalanceIdA    )==null
    			&& this.getValidity( kBalanceIdB    )==null
    			&& this.getValidity( kBalanceIdC    )==null
    			&& this.getValidity( kBalanceIdD    )==null
    			&& this.getValidity( kCodeIdTrType  )==null
    			&& this.getValidity( kCodeIdComplex )==null;
    	}
    }

    				////////////////////////////////////////////
    				////////////// Data Models /////////////////
    				////////////////////////////////////////////

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

    	/** set isReconciled flag of selected Account to given value */
    	this.updateSelected = function( isReconciled )
    	{
    		// we jump thru hoops here to get observers/watchers to notice
    		// that the account data has changed but not think that
    		// "which account is selected" has changed (which would trigger
    		// reloading already-loaded Account data).
    		this.getSelection().updateStatus( isReconciled );
    		this.updateStamp();
    //		this.publish(true);
    	}

    	/** return how many accounts are unreconciled @type int */
    	this.getToDoCount = function()
    	{
    		var sum = 0;
    		this.model.iterate( function(i,f){ if (f.todo) ++sum; } );
    		return sum;
    	}
    }

    Class(TransactionSelectedModel,["Account Selection Model","balance list model"])
    .Extends(MVCBoolModel);
    /**
     * @class This class encapsulates the data model for the flag
     * indicating whether a Transaction is selected.
     * If a new account is selected (thus loading new data)
     * this flag gets reset to false.
     * This model also keeps track of which Transaction is selected.
     * @extends MVCBoolModel
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function TransactionSelectedModel()
    {
    	/** @param {AccountSelectionModel} accountSelectionModel selection model
    	 *  @param {BalanceListModel} sModel balance list model
    	 *  @param {String} optName optional name of this instance
    	 */
    	this.konstructor = function( accountSelectionModel, sModel, optName )
    	{
    		this.souper( optName );

    		// init instance variables
    		this.subscribe( accountSelectionModel );
    		this.transholder = sModel;
    		this._unselect();
    	}

    	/** return whether a transaction is selected @type boolean */
    	this.hasSelection = function(){ return this.bal_Index!=null
    	                                    && this.tranIndex!=null; }

    	/** return the specified transaction @type Transaction
    	 * @param {int} si index into balance list for current Account
    	 * @param {int} ri index into transaction list for specified balance
    	 */
    	this.getSelected = function(si,ri){
            if ( !this.hasSelection() ) return null;
    		return this.transholder.getTransaction(si,ri);
    	}

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

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

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

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

    	/** select the specified transaction
    	 * @param {int} si index into balance list for current Account
    	 * @param {int} ri index into transaction list for specified balance
    	 */
    	this._select    = function(si,ri){ this.bal_Index = si;
    									   this.tranIndex = ri;
    									   this.setFlag( si!=null && ri!=null );
    								     }

    	/** select (and publish) the specified transaction
    	 * @param {int} si index into balance list for current Account
    	 * @param {int} ri index into transaction list for specified balance
    	 */
    	this.select     = function(si,ri){ this._select(si,ri); this.publish(); }

    	/** return whether specified transaction is selected @type boolean
    	 * @param {int} si index into balance list for current Account
    	 * @param {int} ri index into transaction list for specified balance
    	 */
    	this.isSelected = function(si,ri){ return this.bal_Index==si && this.tranIndex==ri; }

    	/** Make the specified transaction selected, unless it is
    	 * currently selected then merely deselect it.
    	 * @param {int} si index into balance list for current Account
    	 * @param {int} ri index into transaction list for specified balance
    	 */
    	this.toggle     = function(si,ri){
    		if ( this.isSelected(si,ri) )
    			 this.unselect();
    	    else this.select(si,ri);
    	}

    	/** delete selected transaction */
    	this.deleteSelected = function() {
    		this.transholder.deleteTransaction( this.bal_Index, this.tranIndex );
    		this.unselect();
    	}

    	/** undelete specified transaction
    	 * @param {int} si index into balance list for current Account
    	 * @param {int} ri index into transaction list for specified balance
    	 */
    	this.undelete = function( si, ri ) {
    		 this.transholder.undeleteTransaction( si, ri );
    	}

    	/** edit selected transaction */
    	this.editSelected = function() {
    		this.transholder.editTransaction( this.bal_Index, this.tranIndex );
    	}

    	/** return whether selected transaction is in edit mode @type boolean
    	 */
    	this.inEdit = function()
    	{
    		var selected = this.getSelected( this.bal_Index, this.tranIndex );
    		return selected==null ? false : selected.inEdit();
    	}

    	/** unedit selected transaction */
    	this.uneditSelected = function() {
    		this.transholder.uneditTransaction( this.bal_Index, this.tranIndex );
    	}

    	/** insert new transaction after selected transaction and select it */
    	this.newAfterSelected = function() {
    		this.select( this.bal_Index,
    		 this.transholder.createTransaction( this.bal_Index, this.tranIndex )
    		);
    	}

    	/** insert new transaction at end of specified balance list and select it
    	 * @param {int} balanceIndex index into balance list for current Account
    	 */
    	this.appendTransaction = function( balanceIndex )
    	{
    		this.select( balanceIndex,
    		 this.transholder.appendTransaction( balanceIndex )
    		);
    	}
    }

    Class(BalanceListModel,["Account Selection Model"]).Extends(MVCListModel);
    /**
     * @class DOMAIN-OBJECT: This class encapsulates the data model
     * for the {@link Balance} list of a particular Account.
     * This model subscribes to a {@link AccountSelectionModel}
     * so that a new balance list can be downloaded whenever
     * a new account is selected.
     * Secondarily, this model implements the {@link MVCBoolModel#isTrue}
     * interface where the value reflects whether this model is "dirty".
     * @extends MVCListModel
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function BalanceListModel()
    {
    	/** @param {AccountSelectionModel} accountSelectionModel model of which account is selected */
    	this.konstructor = function( accountSelectionModel )
    	{
    		this.souper();

    		// init instance variables
    		this.subscribe( accountSelectionModel );
    	}

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

    	/** return whether any transactions are changed for the whole Account @type boolean */
    	this.isDirty = function(){
    		var changed = false;
    		var self    = this;
    		this.iterate( function(i,s){ if (self.isBalanceDirty(i)) return changed = true; } );
    		return changed;
    	}

    	/** handle update events from the Account 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 AccountSelectionModel
    		gMVCUndoCmds.reset();//must reset here because cmds in queue
    						  //can refer to balances that dont exist
    						  //at this point because of this.reset()

    		// request balance/transaction data from server (after small delay)
    		var acct = this.model.getSelection();
    		_requestAccountLoad( acct.bank, acct.acct );
    	}

    	/** save all of the edits into the database via service layer */
    	this.save = function()
    	{
    		var acct   = this.model.getSelection(); //current Account
    	 	var posts = new Array();
    			posts.push( "USER_ID=" + kUserID );

    							//for every edited transaction in every balance,
    							// generate update request
    							this.iterate( function(i,s){
    								s.list.iterate( function(j,r){
    									if (r.inEdit())
    			posts.push( "UPDATES=" + r.asUpdateString(s) );
    								} );
    							} );

    		// immediate invocation of AJAX request with asynchronous reply handling
    		_requestAccountSave( acct.bank, acct.acct, posts.join('&') );
    	}

    	/** return opaque key of our Account */
    	this.getAccountKey = function(){ return this.model.getValue(); }

    	///// methods to manipulate transaction lists embedded within this model /////

    	/** return a new blank transaction @type Transaction */
    	this.newTransaction = function()
    	{
    	    var r = new Transaction( "0", "0", 0,0,0,0, grvGetTodayAsMMDDYYYY(), kUserID, null );
    	    r.addMe();
    	    return r;
    	}

    	/** return the specified transaction
    	 * @type Transaction
    	 * @param {int} sIndex index into "this" balance list
    	 * @param {int} rIndex index into transaction list for specified balance
    	 */
    	this.getTransaction = function( sIndex, rIndex )
    	{
    	    return this.getItem(sIndex).getTransaction(rIndex);
    	}

    	/** return the specified transaction with the intent to change its attributes
    	 * @type Transaction
    	 * @param {int} sIndex index into "this" balance list
    	 * @param {int} rIndex index into transaction list for specified balance
    	 */
    	this.openTransaction = function( sIndex, rIndex )
    	{
    	    this.publish();
    	    return this.getTransaction( sIndex, rIndex );
    	}

    	/** delete the specified transaction
    	 * @param {int} sIndex index into "this" balance list
    	 * @param {int} rIndex index into transaction list for specified balance
    	 */
    	this.deleteTransaction = function( sIndex, rIndex ){
    		this.openTransaction(sIndex,rIndex).deleteMe();
    	}

    	/** undelete the specified transaction
    	 * @param {int} sIndex index into "this" balance list
    	 * @param {int} rIndex index into transaction list for specified balance
    	 */
    	this.undeleteTransaction = function( sIndex, rIndex ){
    		this.openTransaction(sIndex,rIndex).undeleteMe();
    	}

    	/** insert a new blank transaction associated with the specified balance
    	 * @return index of new transaction
    	 * @type int
    	 * @param {int} sIndex index into "this" balance list
    	 * @param {int} rIndex index into transaction list for specified balance
    	 */
    	this.createTransaction = function( sIndex, rIndex )
    	{
    	    this.getItem(sIndex).list.addBefore( rIndex, this.newTransaction() );
    	    return rIndex;
    	}

    	/** append a new blank transaction associated with the specified balance
    	 * @return index of new transaction
    	 * @type int
    	 * @param {int} sIndex index into "this" balance list
    	 */
    	this.appendTransaction = function( sIndex )
    	{
    	    var rList = this.getItem(sIndex).list;
    	    rList.addItem( this.newTransaction() );
    	    return rList.getCount()-1;
    	}

    	/** enable editing on the specified transaction
    	 * @param {int} sIndex index into "this" balance list
    	 * @param {int} rIndex index into transaction list for specified balance
    	 */
    	this.editTransaction = function( sIndex, rIndex ){
    		this.openTransaction(sIndex,rIndex).editMe();
    	}

    	/** rollback edit on the specified transaction
    	 * @param {int} sIndex index into "this" balance list
    	 * @param {int} rIndex index into transaction list for specified balance
    	 */
    	this.uneditTransaction = function( sIndex, rIndex ){
    		this.openTransaction(sIndex,rIndex).uneditMe();
    	}

    	/** return whether the specified balance is being reconciled @type boolean
    	 * @param {int} i index into "this" balance list
    	 */
    	this.isBalanceDirty = function(i){
    		if (i==0) return false; //by definition, first balance is clean
    		return this.getItem(i).anyTransactionsInEdit();
    	}

    	/** return whether the specified balance is expanded @type boolean
    	 * @param {int} i index into "this" balance list
    	 */
    	this.isBalanceExpanded = function(i){
    		return this.isBalanceOpen (i)
    			|| this.isBalanceDirty(i);
    	}

    	/** return whether the specified balance can expand/collapse @type boolean
    	 * @param {int} i index into "this" balance list
    	 */
    	this.canBalanceToggle = function(i){
    		return i /*first balance cant be toggled*/
    			&&  this.isBalanceReconciled(i)
    			&& !this.isBalanceDirty     (i);
    	}

    	/** return whether balance(s) can expand/collapse @type boolean
    	 * @param {boolean} doAllFlag if true, check all of the balances
    	 * @param {int} si index into "this" balance list
    	 */
    	this.canToggle = function( doAllFlag, si )
    	{
    		si -= 0; //force to int
    		var start = doAllFlag ? 0               : si;
    		var stop  = doAllFlag ? this.getCount() : si+1;
    		for (var i=start; i<stop; ++i)
    			if (this.canBalanceToggle(i)) return true;
    		return false;
    	}

    	/** return whether the specified balance is collapsed or open
    	 * @return true if open
    	 * @type boolean
    	 * @param {int} i index into "this" balance list
    	 */
    	this.isBalanceOpen = function(i){
    		if (i==0) return false; //by definition, first balance cant expand
    		return this.getItem(i).open;
    	}

    	/** toggle the "open" flag of the specified balance.
    	 * @param {boolean} doAllFlag if true, set all of the balances to new value
    	 * @param {int} si index into "this" balance list
    	 */
    	this.toggleOpen = function( doAllFlag, si )
    	{
    		si -= 0; //convert to int
    		var start = doAllFlag ? 0               : si;
    		var stop  = doAllFlag ? this.getCount() : si+1;
    		var setTo = !this.getItem(si).open;
    		for (var i=start; i<stop; ++i){
    			var balance = this.getItem(i);
    			balance.open = setTo;
    		}
    	}

    	/** return how many transactions are associated with the specified balance
    	 * @type int
    	 * @param {int} i index into "this" balance list
    	 */
    	this.getTransactionCount = function(i)
    	{
    		if (i==0) return false; //by definition, first balance has none
    		return this.getItem(i).list.getCount();
    	}

    	///// methods to get differences between the net of this      /////
    	///// balance and the previous i.e. the unreconciled amounts /////

    	/** return whether the specified balance is reconciled
    	 * and update the balance todo flag as a side effect.
    	 * @type boolean
    	 * @param {int} i index into "this" balance list
    	 */
    	this.isBalanceReconciled = function(i)
    	{
    		var s = this.getItem(i);
    		var isReconciled = true;
    		//by definition, first balance is always reconciled
    		if (i) isReconciled = s.isValid() && this.hasAllZeroDiff(i);
    		s.todo = !isReconciled;
    		return isReconciled;
    	}

    	/** return true iff all unreconciled amounts for specified balance are zero
    	 * @type boolean
    	 * @param {int} i index into "this" balance list
    	 */
    	this.hasAllZeroDiff = function(i)
    	{
    	    if (i<1) throw "illegal index in getDiffTotal() call";
    	    return grvIsZeroDollars( this.getDiffA(i) )
    			&& grvIsZeroDollars( this.getDiffB (i) )
    			&& grvIsZeroDollars( this.getDiffC (i) )
    			&& grvIsZeroDollars( this.getDiffD(i) );
    	}

    	/** return total of unreconciled amount for specified balance
    	 * @type Float
    	 * @param {int} i index into "this" balance list
    	 */
    	this.getDiffTotal = function(i)
    	{
    	    if (i<1) throw "illegal index in getDiffTotal() call";
    	    var prev = this.getItem(i-1).getTotal();
    	    var curr = this.getItem(i  ).getNetTotal();
    	    return curr - prev;
    	}

    	/** return unreconciled balance A for specified balance
    	 * @type Float
    	 * @param {int} i index into "this" balance list
    	 */
    	this.getDiffA = function(i)
    	{
    	    if (i<1) throw "illegal index in getDiffA() call";
    	    var prev = this.getItem(i-1)[kBalanceIdA];
    	    var curr = this.getItem(i  ).getNetA();
    	    return curr - prev;
    	}

    	/** return unreconciled balance B for specified balance
    	 * @type Float
    	 * @param {int} i index into "this" balance list
    	 */
    	this.getDiffB = function(i)
    	{
    	    if (i<1) throw "illegal index in getDiffB() call";
    	    var prev = this.getItem(i-1)[kBalanceIdB];
    	    var curr = this.getItem(i  ).getNetB();
    	    return curr - prev;
    	}

    	/** return unreconciled balance C for specified balance
    	 * @type Float
    	 * @param {int} i index into "this" balance list
    	 */
    	this.getDiffC = function(i)
    	{
    	    if (i<1) throw "illegal index in getDiffC() call";
    	    var prev = this.getItem(i-1)[kBalanceIdC];
    	    var curr = this.getItem(i  ).getNetC();
    	    return curr - prev;
    	}

    	/** return unreconciled balance D for specified balance
    	 * @type Float
    	 * @param {int} i index into "this" balance list
    	 */
    	this.getDiffD = function(i)
    	{
    	    if (i<1) throw "illegal index in getDiffD() call";
    	    var prev = this.getItem(i-1)[kBalanceIdD];
    	    var curr = this.getItem(i  ).getNetD();
    	    return curr - prev;
    	}

    	///// methods to get aggregate unreconciled amounts of whole list /////

    	/** is the whole Account (i.e. this whole balance list) reconciled? @type boolean */
    	this.isAccountReconciled = function()
    	{
    		//the grand total can be zero but we are still unreconciled, its
    		//just that the unreconciled balance amts cancel each other out!
    		//return grvIsZeroDollars( this.getNetTotal() );
    		var N = this.getCount();
    		for (var i=0; i<N; ++i)
    		  if (!this.isBalanceReconciled(i)) return false;
    		return true;
    	}

    	/** return unreconciled balance A for the whole Account @type Float */
    	this.getNetA = function(){
    		var subtotal = 0, N = this.getCount();
    		for (var i=1; i<N; ++i) subtotal += this.getDiffA(i);
    		return subtotal;
    	}

    	/** return unreconciled balance B for the whole Account @type Float */
    	this.getNetB = function(){
    		var subtotal = 0, N = this.getCount();
    		for (var i=1; i<N; ++i) subtotal += this.getDiffB(i);
    		return subtotal;
    	}

    	/** return unreconciled balance C for the whole Account @type Float */
    	this.getNetC = function(){
    		var subtotal = 0, N = this.getCount();
    		for (var i=1; i<N; ++i) subtotal += this.getDiffC(i);
    		return subtotal;
    	}

    	/** return unreconciled balance D for the whole Account @type Float */
    	this.getNetD = function(){
    		var subtotal = 0, N = this.getCount();
    		for (var i=1; i<N; ++i) subtotal += this.getDiffD(i);
    		return subtotal;
    	}

    	/** return total unreconciled balances for the whole Account @type Float */
    	this.getNetTotal = function(){
    		var a = this.getNetA();
    		var b = this.getNetB();
    		var c = this.getNetC();
    		var d = this.getNetD();
    		return a + b + c + d;
    	}

    	/** return whether all transactions are valid for the whole Account @type Float */
    	this.isValid = function(){
    		var valid = true;
    		this.iterate( function(i,s){ if (!s.isValid()){valid = false; return true;} } );
    		return valid;
    	}
    }

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

    Class(EditButtonController,["button image filename", "descriptive text", "event handler","enable model"])
    .Extends(MVCImgButtonController);
    /**
     * @class This class is an MVCImgButtonController for "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 EditButtonController()
    {
    	/**
    	 * @param {MVCBoolModel} optEnableModel optional "is enabled" data model
    	 * @param {String} evtHandlerName name of event handler function
    	 * @param {String} optName optional name of this instance
    	 * @param {String} imgFN filename of the (enabled) button image
    	 * @param {String} desc tooltip description for this button
    	 * @param {boolean} optIsEdit editor flag says dont enable if already editing
    	 */
    	this.konstructor = function ( imgFN, desc, evtHandler, enableModel, optName, optIsEdit ){
    		this.souper( imgFN, desc, evtHandler, enableModel, optName );
    		this.isEdit = optIsEdit;
    	}

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

    Class(AccountSelectController,["selection model"]).Extends(MVCPopupMenuController);
    /**
     * @class This class manages a popup menu controller specifically for
     * the Account Selection List.
     * @extends MVCPopupMenuController
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.5
     */
    function AccountSelectController()
    {
    	/** @param {AccountSelectionModel} selectionModel selection model
    	 *  @param {String} optName optional name of this instance
    	 */
    	this.konstructor  =  function( selectionModel, optName ){
    	  this.souper( selectionModel, 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 "onAccountSelect(event,this.value)"; }
    }

    				////////////////////////////////////////////
    				////////////// Data Views  /////////////////
    				////////////////////////////////////////////

    Class(DebugView,["A Data Model"]).Extends(MVCView);
    /**
     * @class This class acts as quick and dirty debug view that
     * pops up a window showing the current state of the
     * arbitrary data model being watched.
     * @extends MVCView
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function DebugView()
    {
    	/** @param {Model} aModel a data model to watch */
    	this.konstructor = function( aModel )
    	{
    		this.souper();

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

    	this.mustRebuild = function(){ return this.model.hasChanged; }

    	/** implement this view as an alert box */
    	this.buildHTML   = function(){
    		grvBreak( "=== "+this.name+" ==="
    		  +"\nUpdate Event from Model["+this.model.name+"]...\n"
    	      + this.model.hasChanged +this.model );
    	}
    }

    Class(CountersView,["Account 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 CountersView()
    {
    	/** @param {SelectionModel} sModel account selection model */
    	this.konstructor = function( sModel )
    	{
    		this.souper();

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

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

    	this.buildHTMLstr = function(){
    		var x = this.model.getIndex()+1;
    		return x+"&nbsp;of&nbsp;"+this.model.getCount();
    	}
    }

    Class(ToDoView,["Account Selection Model"]).Extends(MVCView);
    /**
     * @class This class produces a view of how many unreconciled
     * accounts are left.
     * @extends MVCView
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.5
     */
    function ToDoView()
    {
    	/** @param {AccountSelectionModel} sModel account selection model */
    	this.konstructor = function( sModel )
    	{
    		this.souper();

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

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

    	this.buildHTMLstr = function(){
    	    var x = this.model.getToDoCount();
    		return "<span class='"+(x>0?"td-more":"td-none")
    		+"' title='Number of Unreconciled Accounts'>"
    		+x+"&nbsp;Accts Unreconciled</span>";
    	}
    }

    Class(ComplexView,["Transaction object","index of parent balance","index of transaction"])
    .Extends(MVCView);
    /**
     * @class This class produces a view of the complex type of a {@link Transaction}.
     * @extends MVCView
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.5
     */
    function ComplexView()
    {
    	/** @param {Transaction} transaction transaction we are to observe
    	 *  @param {int} balanceIndex index into balance list for current Account
    	 *  @param {int} transIndex index into transaction list for specified balance
    	 */
    	this.konstructor = function( transaction, balanceIndex, transIndex )
    	{
    		this.souper();

    		// init instance variables
    		this.si = balanceIndex;
    		this.ri = transIndex;
    		this.watchModel( transaction );
    	}

    	/** return the view ID of our status panel @type String */
    	this.statID = function(){ return this.getWidgetID() + ".stat"; }

    	this.paintHTML = function()
    	{
    		var row           = this.getWidget(); if (!row) return;
    		var r             = this.model;
    		var isSelected    = gTransactionSelected.isSelected( this.si, this.ri );
    		var isDirty       = r.inEdit();
    		var isBeingEdited = isSelected && isDirty;
    		var isVisible     = !r.inactive()
    						 && r.isComplex()
    		                 && (gAccountData.isBalanceExpanded  ( this.si )
    		                 || !gAccountData.isBalanceReconciled( this.si ));

    		this.setVisible( isVisible );

    		var menu = this[ kCodeIdComplex ];//originally set in mvcEmbedDualMenu
    		if ( isVisible )
    		{
    			grvSelectElem( row, isSelected );

    			//TODO refactor state display into its own view
    			grvSelectElemID( this.statID(), false );

    			//Since this global data model is just a temp utility model,
    			//shared by all collateral menus, we must manually set it
    			//to the current value of the REAL data model (iff this is
    			//the ONE AND ONLY currently-being-edited transaction). In
    			//other words, this trick of sharing a single gComplexSelected
    			//data model is because we only allow one collateral menu
    			//to be editable at a time.
    			if (isBeingEdited) gComplexSelected._select( r[kCodeIdComplex] );

    			//TODO refactor these views to have a visibility model (like ButtonController)
    			menu.setVisible( true );
    			menu.setEditMode( MVCEditRule.kNonZ, isBeingEdited );
    		}
    		else menu.setEditMode( MVCEditRule.kEdit, false );
    	}

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

    	/** return HTML version of a complex type panel. @type String */
    	this.buildHTMLstr = function()
    	{
    		function buildStat( hookID, si, ri ){
    		 	var evtHandlerStr = 'onTransactionClick(event,"'+ si+'","'+ ri +'")';
    			return "<td onclick='"+evtHandlerStr+"' id='"+hookID+"'>&nbsp;</td>";
    		}
    		function buildHead( editorHTML ){
    			var cols = kEnableTotalsColumn ? 6 : 5;
    			return "<td colspan='"+cols+"'>"+editorHTML+"</td>";
    		}
    		function buildRow( hookID ){
    			return "<tr class='py-row' id='"+hookID+"'>";
    		}

    	 	var HTML  = new Array();
    		HTML.push( buildRow ( this.getWidgetID() ) );
    		HTML.push( buildStat( this.statID(), this.si, this.ri ) );
    		HTML.push( buildHead( mvcEmbedDualMenu( this, kCodeIdComplex, gComplexSelected, "py-coll" ) ) );
    		HTML.push( "</tr>" );
    		return HTML.join('');
    	}
    }

    Class(TransactionView,["Transaction object","index of parent balance","index of transaction"])
    .Extends(MVCView);
    /**
     * @class This class produces a view of a {@link Transaction}.
     * @extends MVCView
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.5
     */
    function TransactionView()
    {
    	/** @param {Transaction} transaction transaction we are to observe
    	 *  @param {int} balanceIndex index into balance list for current Account
    	 *  @param {int} transIndex index into transaction list for specified balance
    	 */
    	this.konstructor = function( transaction, balanceIndex, transIndex )
    	{
    		this.souper();

    		// init instance variables
    		this.si = balanceIndex;
    		this.ri = transIndex;
    		this.watchModel( transaction );
    	}

    	/** return the view ID of our status panel @type String */
    	this.statID = function(){ return this.getWidgetID() + ".stat"; }

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

    	this.paintHTML = function()
    	{
    		function buildTip( r ){
    			return "Last Edit by "+r.whom+" on "+r.date+" State:"+r.state()+" Key="+r.key;
    		}

    		var row           = this.getWidget(); if (!row) return;
    		var r             = this.model;
    		var isSelected    = gTransactionSelected.isSelected( this.si, this.ri );
    		var isDirty       = r.inEdit();
    		var isBeingEdited = isSelected && isDirty;
    		var isVisible     = !r.inactive()
    		                 && (gAccountData.isBalanceExpanded  ( this.si )
    		                 || !gAccountData.isBalanceReconciled( this.si ));
    		this.setVisible( isVisible );

    		if ( isVisible )
    		{
    			this.setSubViewsVisible(true);
    			row.title = buildTip( r );
    			grvSelectElem( row, isSelected );

    			//Since this global data model is just a temp utility model,
    			//shared by all transaction-type menus, we must manually set
    			//it to the current value of the REAL data model (iff this
    			//is the ONE AND ONLY currently-being-edited transaction). In
    			//other words, this trick of sharing a single gTrTypeSelected
    			//data model is because we only allow one transaction-type
    			//menu to be editable at a time.
    			if (isBeingEdited) gTrTypeSelected._select( r[kCodeIdTrType] );

                //update editability of trans type menu
                var menu = this[ kCodeIdTrType ];//originally set in mvcEmbedDualMenu
                menu.enableEdit( isBeingEdited );

    			//set the edit rule for each balance
    			this.updateEditMode( isBeingEdited, kBalanceIdA );
    			this.updateEditMode( isBeingEdited, kBalanceIdB );
    			this.updateEditMode( isBeingEdited, kBalanceIdC );
    			this.updateEditMode( isBeingEdited, kBalanceIdD );

    			//TODO refactor state display into its own subview
    			var isInValid = ! r.isValid();
    			var state     = grvGetHook( this.statID() );
    			grvEditElem( state, isSelected, isDirty, isInValid );
    			grvSetElemText( state, grvSubitem( isInValid ) );
    			//once a span title is set it cant be deleted and will
    			//always obscure the row title that it happily displayed
    			//before that span title was set...F. U. I. E.
    			//state.title = isInValid ? "This transaction has errors." : row.title;
    		}
    	}

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

    	this.buildHTMLstr = function()
    	{
    		function buildStat( hookID, si, ri ){
    		 	var evtHandlerStr = 'onTransactionClick(event,"'+ si+'","'+ ri +'")';
    			return "<td class='py-state' onclick='"+evtHandlerStr+"' id='"+hookID+"'></td>";
    		}
            function buildHead( editorHTML ){
                    return "<td>"+editorHTML+"</td>";
            }
    		function buildData( editorHTML ){
    			return "<td>"+editorHTML+"</td>";
    		}
    		function buildTotl( viewerHTML ){
    			return "<td class='py-total'>"+viewerHTML+"</td>";
    		}
    		function buildRow( hookID ){
    			return "<tr class='py-row' id='"+hookID+"'>";
    		}

    	 	var HTML = new Array();
    	 	HTML.push( buildRow ( this.getWidgetID()              ) );
    		HTML.push( buildStat( this.statID(), this.si, this.ri ) );
    		HTML.push( buildHead( mvcEmbedDualMenu( this, kCodeIdTrType, gTrTypeSelected, "py-src", "onTrTypeSelect" ) ) );
    		HTML.push( buildData( mvcEmbedDollarDualEditor( this, kBalanceIdA,'py-data' ) ) );
    		HTML.push( buildData( mvcEmbedDollarDualEditor( this, kBalanceIdB,'py-data' ) ) );
    		HTML.push( buildData( mvcEmbedDollarDualEditor( this, kBalanceIdC,'py-data' ) ) );
    		HTML.push( buildData( mvcEmbedDollarDualEditor( this, kBalanceIdD,'py-data' ) ) );
    if (kEnableTotalsColumn)
    		HTML.push( buildTotl( mvcEmbedAttributeViewer( this, "getTotal", grvFormatDollar ) ) );
    		HTML.push( "</tr>" );
    		return HTML.join('');
    	}
    }

    Class(UnreconciledView,["Balance List model","index of parent balance"])
    .Extends(MVCView);
    /**
     * @class This class produces a view of the unreconciled totals
     * of a {@link Balance}.
     * @extends MVCView
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.5
     */
    function UnreconciledView()
    {
    	/** @param {Balance} sModel balance we are to observe
    	 *  @param {int} balanceIndex index into balance list for current Account
    	 */
    	this.konstructor = function( sModel, balanceIndex )
    	{
    		this.souper();

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

    	/** return the view ID of our header panel @type String */
    	this.headID    = function(){ return this.getWidgetID() + ".head"; }

    	this.paintHTML = function()
    	{
    		var row          = this.getWidget(); if (!row) return;
    		var isReconciled = this.model.isBalanceReconciled( this.si );
    		var isExpanded   = this.model.isBalanceExpanded  ( this.si );
    		var tranCount    = this.model.getTransactionCount( this.si );
    		var isDirty      = this.model.isBalanceDirty     ( this.si );
    		var isVisible    = !isReconciled || isDirty || (isExpanded && tranCount==0);
    		this.setVisible( isVisible );
    		if (isVisible)
    		{
    			var head = grvGetHook( this.headID() );
    			 row.className = isReconciled ? "tr-row"  : "df-row";
    			head.className = isReconciled ? "ur-head" : "df-head";
    			this.setSubViewsVisible( true );
    		}
    	}

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

    	this.buildHTMLstr = function()
    	{
    		function buildHead( hookID, sIndex ){
    		 	var event = 'onUnreconciledClick(event,"'+ sIndex +'")';
    			return "<td>&nbsp;</td><td onclick='"+event+"' id='"+hookID+"'>Amount Unreconciled</td>";
    		}
    		function buildData( viewer ){
    			var cls = "df-data";
    			return "<td class='"+cls+"'>"+viewer+"</td>"
    		}
    		function buildTotl( viewer ){
    			return "<td class='df-total'>"+viewer+"</td>";
    		}
    		function buildRow( hookID, balanceAsString ){
    		 	var tip = "Unreconciled Amounts for " + balanceAsString;
    			return "<tr title='"+tip+"' id='"+hookID+"'>";
    		}

    		var i = this.si;
    	 	var HTML = new Array();
    		HTML.push( buildRow ( this.getWidgetID(), this.model.getItem(i).getTitle() ) );
    		HTML.push( buildHead( this.headID(), i ) );
    		HTML.push( buildData( mvcEmbedAttributeViewer( this, "getDiffA"    , grvFormatDollarNot, i ) ) );
    		HTML.push( buildData( mvcEmbedAttributeViewer( this, "getDiffB"    , grvFormatDollarNot, i ) ) );
    		HTML.push( buildData( mvcEmbedAttributeViewer( this, "getDiffC"    , grvFormatDollarNot, i ) ) );
    		HTML.push( buildData( mvcEmbedAttributeViewer( this, "getDiffD"    , grvFormatDollarNot, i ) ) );
    if (kEnableTotalsColumn)
    		HTML.push( buildTotl( mvcEmbedAttributeViewer( this, "getDiffTotal", grvFormatDollar   , i ) ) );
    		HTML.push( "</tr>" );
    		return HTML.join('');
    	}
    }

    Class(TotalsView,["Balance List model"]).Extends(MVCView);
    /**
     * @class This class produces a view of the unreconciled totals
     * for an entire {@link Account}.
     * @extends MVCView
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function TotalsView()
    {
    	/** @param {BalanceListModel} sModel Account balances we are to observe */
    	this.konstructor = function( sModel )
    	{
    		this.souper();

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

    	/** return the view ID of our header panel @type String */
    	this.headID = function(){ return this.getWidgetID() + ".head"; }

    	this.paintHTML = function()
    	{
    		var row  = this.getWidget(); if (!row) return;
    		var head = grvGetHook( this.headID() );
    		var isReconciled = this.model.isAccountReconciled();

    		 row.className = isReconciled ? "tr-row"  : "df-row";
    		head.className = isReconciled ? "tr-head" : "tu-head";
    	}

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

    	this.buildHTMLstr = function()
    	{
    		function buildHead( hookID ){
    			return "<td colspan='2' id='"+hookID+"'>Total Unreconciled</td>";
    		}
    		function buildData( viewer ){
    			var cls = "df-data";
    			return "<td class='"+cls+"'>"+viewer+"</td>"
    		}
    		function buildTotl( viewer ){
    			return "<td class='df-total'>"+viewer+"</td>";
    		}
    		function buildRow( hookID, accountAsString ){
    		 	var tip  = "Total Unreconciled Amounts for " + accountAsString;
    			return "<tr title='"+tip+"' id='"+hookID+"'>";
    		}

    	 	var HTML = new Array();
    		HTML.push( buildRow ( this.getWidgetID(), this.model.model.getSelectionStr() ) );
    		HTML.push( buildHead( this.headID() ) );
    		HTML.push( buildData( mvcEmbedAttributeViewer( this, "getNetA"    , grvFormatDollar ) ) );
    		HTML.push( buildData( mvcEmbedAttributeViewer( this, "getNetB"    , grvFormatDollar ) ) );
    		HTML.push( buildData( mvcEmbedAttributeViewer( this, "getNetC"    , grvFormatDollar ) ) );
    		HTML.push( buildData( mvcEmbedAttributeViewer( this, "getNetD"    , grvFormatDollar ) ) );
    if (kEnableTotalsColumn)
    		HTML.push( buildTotl( mvcEmbedAttributeViewer( this, "getNetTotal", grvFormatDollar ) ) );
    		HTML.push( "</tr>" );
    		return HTML.join('');
    	}
    }

    Class(BalanceView,["Balance object","index of balance"]).Extends(MVCView);
    /**
     * @class This class produces a view of a {@link Balance}.
     * @extends MVCView
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.5
     */
    function BalanceView()
    {
    	/** @param {Balance} balance balance we are to observe
    	 *  @param {int} balanceIndex index into balance list for current Account
    	 */
    	this.konstructor = function( balance, balanceIndex )
    	{
    		this.souper();

    		// init instance variables
    		this.si = balanceIndex;
    		this.watchModel( balance );
    	}

    	this.paintHTML = function()
    	{
    	  var active = !kReadOnly && this.si && gAccountData.isBalanceReconciled(this.si);
    	  var row = this.getWidget();
    	  row.style.cursor = active ? "pointer" : "default";
    	}

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

    	this.buildHTMLstr = function()
    	{
    		function buildHead(title){
    			return "<td class='sn-head' colspan='2'>"+title+"</td>";
    		}
    		function buildData(x){
    			return "<td class='sn-data'>"+grvFormatDollar(x)+"</td>";
    		}
    		function buildTotl(x){
    			return "<td class='sn-total'>"+grvFormatDollar(x)+"</td>";
    		}
    		function buildRow(i,s,hookID){
    		 	var cls  = "";
    		 	var event= 'onBalanceClick(event,"'+ i +'")';
    		 	var tip  = "From:" + kSystemsMap.getItem( s.whom ).desc
    		 	         + "...Click to Toggle; Ctrl-Click to toggle all";
    			return "<tr class='sn-row' onclick='"+event+"' title='"+tip+"' id='"+hookID+"'>";
    		}

    		var s = this.model;
    		var i = this.si;
    		var h = this.getWidgetID();

    	 	var HTML = new Array();
    		HTML.push( buildRow ( i, s, h        ) );
    		HTML.push( buildHead( s.getTitle()   ) );
    		HTML.push( buildData( s[kBalanceIdA] ) );
    		HTML.push( buildData( s[kBalanceIdB] ) );
    		HTML.push( buildData( s[kBalanceIdC] ) );
    		HTML.push( buildData( s[kBalanceIdD] ) );
    if (kEnableTotalsColumn)
    		HTML.push( buildTotl( s.getTotal()   ) );
    		HTML.push( "</tr>" );
    		return HTML.join('');
    	}
    }

    Class(TransactionListView,["Transaction List model","index of parent balance"])
    .Extends(MVCListView);
    /**
     * @class This class produces a view of a Transaction List.
     * @extends MVCListView
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function TransactionListView()
    {
    	/** @param {MVCListModel} transListModel transaction list we are to observe
    	 *  @param {int} balanceIndex index into balance list for current Account
    	 */
    	this.konstructor = function( transListModel, balanceIndex  )
    	{
    		this.souper();

    		// init instance variables
    		this.si = balanceIndex;
    		this.watchModel( transListModel );
    	}

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

    	this.itemHTMLstr = function( index, item, itemID )
    	{
    		var HTML = new Array();

    	 	HTML.push( this.embedHTML( itemID,
    	 			new TransactionView(item,this.si,index) ) );

    		HTML.push( this.embedHTML( itemID+".coll",
    	 			new     ComplexView(item,this.si,index) ) );

    		return HTML.join('');
    	}
    }

    Class(BalanceListView,["Balance List model"]).Extends(MVCListView);
    /**
     * @class This class produces a view of a BALANCE List.
     * @extends MVCListView
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function BalanceListView()
    {
    	/** @param {BalanceListModel} sModel balance list we are to observe */
    	this.konstructor = function( sModel )
    	{
    		this.souper();

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

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

    	this.itemHTMLstr = function( index, item, itemID )
    	{
    		var HTML = new Array();

    	  if (index!=0) //there is nothing to reconcile for 1st balance!
    	  {
    	 	HTML.push( this.embedHTML( itemID+".py",
    	 			new TransactionListView(item.list,index) ) );

    		HTML.push( this.embedHTML( itemID+".unrec",
    	 			new UnreconciledView(this.model,index) ) );
    	  }
    		HTML.push( this.embedHTML( itemID,
    	 			new BalanceView(item,index) ) );

    		return HTML.join('');
    	}
    }

    Class(DataPanelView,["Balance List model"]).Extends(MVCView);
    /**
     * @class This class produces the view of the entire data panel.
     * @extends MVCView
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function DataPanelView()
    {
    	/** @param {BalanceListModel} ssModel Account balances we are to observe */
    	this.konstructor = function( ssModel )
    	{
    		this.souper();

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

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

    	this.buildHTMLstr = function()
        {
        	if (this.model.getCount()<1) return "";

    		var vwID = this.getWidgetID();
    		var HTML = new Array(30);
    		HTML.push( '<table class="data-panel">' );
    		HTML.push( '<thead><tr>' );
    		HTML.push( '<th colspan="2" class="table-head" id="headTitle">Transaction Type</th>' );
    		HTML.push( '<th class="table-head" id="headA">Balance A</th>' );
    		HTML.push( '<th class="table-head" id="headB">Balance B</th>' );
    		HTML.push( '<th class="table-head" id="headC">Balance C</th>' );
    		HTML.push( '<th class="table-head" id="headD">Balance D</th>' );
    if (kEnableTotalsColumn)
    		HTML.push( '<th class="table-head" id="headTotal"><i>Row Total</i></th>' );
    		HTML.push( '</tr></thead>' );
    		HTML.push( '<tbody>'
    				+ this.embedHTML( vwID+".sn", new BalanceListView( this.model ) )
    				+ '</tbody>' );
    		HTML.push( '<tr><td colspan="7">'+mvcSECTIONBREAK()+'</td></tr>' );
    		HTML.push( '<tfoot>'
    				+ this.embedHTML( vwID+".totalunrec", new TotalsView( this.model ) )
    				+ '</tfoot>' );
    		HTML.push( '</table>' );
    		return HTML.join('');
    	}
    }

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

    // ----------------------------------------------------------------------------
    // Utility routines for making AJAX requests
    // ----------------------------------------------------------------------------
    /** Make a REST-style AJAX request to the RATS server.</br>
     *  NOTE: this is a Command helper function.</br>
     *  NEVER call this from outside the context of a Command object!
     * @param {String} aRequestName "controller" name
     * @param {Function} aReplyEventHandler reply event handler static function
     *                   (called as a method of grvXMLrec and passed grvXMLrec as param)
     * @param {String} aBANK   Account bank number
     * @param {String} anACCT  Account number
     * @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)
     */
    function _RATSRequest( aRequestName, aReplyEventHandler, aBANK,
    						anACCT, optPostData, optWaitFlag )
    {
    	grvWAIT();
    	var url;
    	var parms = new Array();
    	if (_kGrvAjaxTestData)
    	{
    		parms.push( aBANK   );
    		parms.push( anACCT  );
    		url = kXMLPath + "RATS" + parms.join("_") + ".xml";
    	}
    	else
    	{
    		parms.push(              aRequestName         );
    		parms.push(    'user=' + grvGetArg("user")    );
    		parms.push( 'testing=' + grvGetArg("testing") );
    		parms.push(    'BANK=' + aBANK                );
    		parms.push(    'ACCT=' + anACCT               );
    		url = location.pathname + "?action=" + parms.join("&");
    	}

    	return new grvXMLreq( url, aReplyEventHandler, optPostData, optWaitFlag );
    }

    /** Make a Load-Account-Data request to the server.<br>
     *  NOTE: this is a Command helper function.<br>
     *  NEVER call this from outside the context of a Command object!
     * @param {String} aBANK   Account bank number
     * @param {String} anACCT  Account number
     */
    function _requestAccountLoad( aBANK, anACCT ){
    	_RATSRequest( "GetXMLAccountData", onAccountLoadReply, aBANK, anACCT );
    }

    /** Make a Save-Account-Data request to the server.<br>
     *  NOTE: this is a Command helper function.<br>
     *  NEVER call this from outside the context of a Command object!
     * @param {String} aBANK   Account bank number
     * @param {String} anACCT  Account number
     * @param {String} updates list of transaction update commands
     */
    function _requestAccountSave( aBANK, anACCT, updates ){
    	_RATSRequest( "UpdateAccountData", onAccountSaveReply, aBANK, anACCT, updates );
    }

    /** Select the first unreconciled Account<br>
     *  NOTE: this is a Command helper function.<br>
     *  NEVER call this from outside the context of a Command object!
     * @param {boolean} selectFirst if FALSE then leave selection alone if none unreconciled
     * otherwise selected the first Account if none unreconciled.
     * @return true iff new selection was made
     * @type boolean
     */
    function _selectFirstUnreconciled( selectFirst )
    {
    	// Select 1st unreconciled account but
    	// if all are reconciled then select the 1st account if requested
    	// otherwise leave the selection where it is.
    	var N = gAccountList.getCount();
    	for (var i=0; i<N; ++i)
    	  if (gAccountList.getItem(i).todo) break;

    	if ( i>=N && selectFirst ) i = 0;
    	if ( i>=N ) return false;
    	return gAccountSelected.setValue( i );
    }

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

    Class(LoadAccountDataCmd,["xml request object","auto-select-unreconciled flag"])
    .Extends(MVCCommand);
    /**
     * @class This class implements the "load account data" command.
     * NOTE: It isnt undoable (because we lose all edits!).
     * @extends MVCCommand
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.5
     */
    function LoadAccountDataCmd()
    {
    	/** @param {grvXMLreq} xmlReq the XML request being replied to
    	 *  @param {boolean} autoFlag if true then auto-advance to next
    	 *                   unreconciled Account if current data is reconciled
    	 *  @param {String} optName optional name of this instance
    	 */
    	this.konstructor = function( xmlReq, autoFlag, optName )
    	{
    		// init static member variables
    		if (!LoadAccountDataCmd.XSL)
    		     LoadAccountDataCmd.XSL = grvLoadXslDOM( kXSLPath+"RATSxmlToJS.xsl" );

    		this.souper( optName );
    		this.canUndo = false;
    		this.auto    = autoFlag;

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

    	this.doit = function()
    	{
    		grvTraceEvt( this.script );
    		       eval( this.script );

    		// updated reconcile status notification
    		//grvDebugWindow( gAccountData.dump() );//look at the updated data

    		// HACK: this normally should have been accomplished via
    		// subscriptions, but it would have set up a circular dependency.
    		gAccountSelected.updateSelected( gAccountData.isAccountReconciled() );

    		// automatically select next unreconciled (if requested)
    		// (which would mean we must wait on data from server)
    		var  stillWaiting = true;
    		if (this.auto)
    		     stillWaiting = _selectFirstUnreconciled( false );
    		else stillWaiting = false;
    		if (!stillWaiting) grvUNWAIT();
    	}
    }

    Class(SelectFirstUnreconciledCmd).Extends(MVCCommand);
    /**
     * @class This class implements the "select first unreconciled account" 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 SelectFirstUnreconciledCmd()
    {
    	/** @param {String} optName optional name of this instance */
    	this.konstructor = function( optName ){
    		this.souper( optName );
    		this.canUndo = false;
    	}

    	this.doit = function(){ _selectFirstUnreconciled(true); }
    }

    Class(SelectAccountCmd,["selected Account Key"]).Extends(MVCCommand);
    /**
     * @class This class implements the "select account" command.
     * NOTE: It isnt undoable (because we lose all edits!).
     * @extends MVCCommand
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function SelectAccountCmd()
    {
    	/** @param {Object} selectedAccountKey opaque key of Account
    	 *  @param {String} optName optional name of this instance
    	 */
    	this.konstructor = function( selectedAccountKey, optName )
    	{
    		this.souper( optName, (gAccountData.isTrue()
    				   ? "You must Save or Cancel data changes first."
    				   : null ) );
    		this.acctKey  = selectedAccountKey;
    		this.canUndo = false;
    	}

    	this.doit = function(){ gAccountSelected.select( this.acctKey ); }
    }

    Class(CloseWindowIfDataSavedCmd).Extends(MVCCommand);
    /**
     * @class This class implements the "close this window if all
     * data is 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 CloseWindowIfDataSavedCmd()
    {
    	/** @param {String} optName optional name of this instance */
    	this.konstructor = function( optName )
    	{
    		this.souper( optName, (gAccountData.isDirty() /*.isTrue()*/
    				   ? "You must Save or Cancel data changes before leaving."
    				   : null )  );
    		this.canUndo = false;
    	}

    	this.doit = function(){ window.close(); }
    }

    Class(CancelAllAccountChangesCmd).Extends(MVCCommand);
    /**
     * @class This class implements the "revert this account" command.
     * NOTE: It isnt undoable (because we lose all edits!).
     * @extends MVCCommand
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function CancelAllAccountChangesCmd()
    {
    	/** @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 to this Account?"))
    		  this.state = -1;
    	}
    							//causes reload of original data
    	this.doit = function(){	gAccountSelected.reselect(); }
    }

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

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

    Class(TrTypeMenuCmd,["view ID"]).Extends(MVCScalarEditCmd);
    /**
     * @class This class implements the trans type menu edit command.
     * @extends MVCScalarEditCmd
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function TrTypeMenuCmd()
    {
    	/** @param {String} viewID viewID of menu generating this event */
    	this.konstructor = function( viewID ){
    		this.souper( viewID );
    		this.si             = this.dual.parentView.si;
    		this.transaction    = this.dual.model.base;
    		this.oldTransaction = this.transaction.clone();
    	}

    	/** copy values that can change when transaction type is changed
    	 * @param {Transaction} exemplar where to copy attributes from
    	 */
    	this.updateTransaction = function( exemplar ){
    		this.transaction[ kCodeIdComplex ] = exemplar[ kCodeIdComplex ];
    		this.transaction[ kBalanceIdA    ] = exemplar[ kBalanceIdA    ];
    		this.transaction[ kBalanceIdB    ] = exemplar[ kBalanceIdB    ];
    		this.transaction[ kBalanceIdC    ] = exemplar[ kBalanceIdC    ];
    		this.transaction[ kBalanceIdD    ] = exemplar[ kBalanceIdD    ];
    	}

    	/** prefill balance values when transaction type is changed
    	 * taking into account that the existing values in this transaction
    	 * have to be applied back to the unreconciled balances before
    	 * prefilling again.
    	 */
    	this.prefillTransaction = function(){
    		var i = this.si;
    		this.transaction[ kBalanceIdA ] = 0;
    		this.transaction[ kBalanceIdB ] = 0;
    		this.transaction[ kBalanceIdC ] = 0;
    		this.transaction[ kBalanceIdD ] = 0;
    		this.transaction[ kBalanceIdA ] = gAccountData.getDiffA(i);
    		this.transaction[ kBalanceIdB ] = gAccountData.getDiffB (i);
    		this.transaction[ kBalanceIdC ] = gAccountData.getDiffC (i);
    		this.transaction[ kBalanceIdD ] = gAccountData.getDiffD(i);
    	}

    	this.doit = function(){
    		this.updateController( this.newValue );
    		this.prefillTransaction();
    	}
    	this.undo = function(){
    		this.newTransaction = this.transaction.clone();
    		this.updateTransaction( this.oldTransaction );
    		this.updateController( this.oldValue );
    	}
    	this.redo = function(){
    		this.updateTransaction( this.newTransaction );
    		this.updateController( this.newValue );
    	}
    }

    Class(RatsCmd).Extends(MVCCommand);
    /**
     * @class This is the abstract base class for RATS commands.
     * @extends MVCCommand
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function RatsCmd()
    {
    	/** @param {String} cmdDesc general description of this command */
    	this.konstructor = function( cmdDesc ){
    		this.souper( cmdDesc );
    		//save current transaction selection
    		this.cmdSI = this.oldSI = gTransactionSelected.bal_Index;
    		this.cmdRI = this.oldRI = gTransactionSelected.tranIndex;
    	}

    	/** return balance description @type String */
    	this.balString = function(){
    		return gAccountData.getItem( this.cmdSI ).sdate;
    	}

    	/** return transaction description @type String */
    	this.tranString = function(){
    		this.cmdRI -= 0; //type cast to int
    		return "["+(this.cmdRI+1)+"] of " + this.balString();
    	}

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

    Class(ToggleBalCmd,["toggle all flag","balance index"]).Extends(RatsCmd);
    /**
     * @class This class implements the toggle balance display cmd.
     * @extends RatsCmd
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function ToggleBalCmd()
    {
    	/** @param {boolean} toggleAll if true then toggle all balance views
    	 *  @param {int} bal_Index index into current account's balance list
    	 */
    	this.konstructor = function( toggleAll, bal_Index )
    	{
    	  this.souper("toggle balance display");
    	  if ( gAccountData.canToggle( toggleAll, bal_Index ) )
    	  {
    		   this.all   = toggleAll;
    		   this.cmdSI = bal_Index;
    	  }
    	  else this.state = -1;
    	}

    	this.doit    = function(){ gAccountData.toggleOpen( this.all, this.cmdSI ); }
    	this.undo    = function(){ this.doit(); }
    	this.details = function(){ return this.all ? "ALL" : this.balString(); }
    }

    Class(ToggleTranCmd,["balance index","trans index"]).Extends(RatsCmd);
    /**
     * @class This class implements the toggle transaction selection cmd.
     * @extends RatsCmd
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function ToggleTranCmd()
    {
    	/** @param {int} tranIndex index into specified balance's transaction list
    	 *  @param {int} bal_Index index into current account's balance list
    	 */
    	this.konstructor = function( bal_Index, tranIndex ){
    		this.souper("toggle transaction selection");
    		this.cmdRI = tranIndex;
    		this.cmdSI = bal_Index;
    	}

    	this.doit = function(){ gTransactionSelected.toggle( this.cmdSI, this.cmdRI ); }
    	this.undo = function(){ gTransactionSelected.toggle( this.oldSI, this.oldRI ); }
    }

    kAppendTransaction = true;
    kInsertTransaction = false;
    Class(CreateTranCmd,["append flag"]).Extends(RatsCmd);
    /**
     * @class This class implements the append and insert transaction cmds.
     * @extends RatsCmd
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function CreateTranCmd()
    {
    	/** @param {boolean} appendFlag if true then append else insert transaction
    	 *  @param {int} optBalIndex optional index of balance to insert after
    	 */
    	this.konstructor = function( appendFlag, optBalIndex )
    	{
    		this.souper((appendFlag?"append":"insert")+" transaction");
    		//TODO refactor balance&unreconciled panels to be enabled/disabled
    		//     Controllers instead of putting "read-only" logic here
    		if ( ! kReadOnly )
    		{
    			 this.append = appendFlag;
    			 this.cmdSI  = optBalIndex ? optBalIndex : gTransactionSelected.bal_Index;
    		}
    		else this.state  = -1;
    	}

    	this.doit = function()
    	{
    		//create new transaction
    		if (this.append)
    			 gTransactionSelected.appendTransaction( this.cmdSI );
    		else gTransactionSelected.newAfterSelected();
    		//save new state
    		this.newSI = gTransactionSelected.bal_Index;
    		this.newRI = gTransactionSelected.tranIndex;
    	}

    	this.undo = function(){
    		gTransactionSelected.deleteSelected();
    		gTransactionSelected.select( this.oldSI, this.oldRI );
    	}

    	this.redo = function(){
    		gTransactionSelected.undelete( this.newSI, this.newRI );
    		gTransactionSelected.select  ( this.newSI, this.newRI );
    	}

    	this.details = function(){ return this.balString(); }
    }

    Class(DeleteTranCmd).Extends(RatsCmd);
    /**
     * @class This class implements the delete transaction cmd.
     * @extends RatsCmd
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function DeleteTranCmd()
    {
    	this.konstructor = function(){
    		this.souper("delete transaction");
    	}
    	this.doit = function(){
    		gTransactionSelected.deleteSelected();//leaves nothing selected
    	}
    	this.undo = function(){
    		gTransactionSelected.undelete( this.oldSI, this.oldRI );
    		gTransactionSelected.select  ( this.oldSI, this.oldRI );
    	}
    }

    Class(EditTranCmd).Extends(RatsCmd);
    /**
     * @class This class implements the edit transaction cmd.
     * @extends RatsCmd
     * @see #konstructor
     * @author Bruce Wallace  (PolyGlotInc.com)
     * @version 2.0
     */
    function EditTranCmd()
    {
    	this.konstructor = function(){
    		this.souper("edit transaction");
    	}
    	this.doit = function(){ gTransactionSelected.  editSelected(); }
    	this.undo = function(){ gTransactionSelected.uneditSelected(); }
    }

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

    /** handle event for 'delete button pressed' */
    function onDeleteBtnPressed(){
    	mvcDoCmd( new DeleteTranCmd() );
    }

    /** handle event for 'insert button pressed' */
    function onInsertBtnPressed()
    {
    // dont insert until fix is found for insert goofing up the transaction
    // indexes lodged in all those command objects!!!
    //	mvcDoCmd( new CreateTranCmd( kInsertTransaction ) );
    	mvcDoCmd( new CreateTranCmd( kAppendTransaction ) );
    }

    /** handle event for 'edit button pressed' */
    function onEditBtnPressed(){
    	mvcDoCmd( new EditTranCmd() );
    }

    /** handle event for 'click on a transaction record'
     * @param {event} e browser event
     * @param {int} balanceIndex index into balance list (aka account data)
     * @param {int} transIndex index into transaction list (of balance)
     */
    function onTransactionClick( e, balanceIndex, transIndex ){
    	mvcDoCmd( new ToggleTranCmd( balanceIndex, transIndex ) );
    }

    /** handle event for 'click on a balance record'
     * @param {event} e browser event
     * @param {int} balanceIndex index into balance list (aka account data)
     */
    function onBalanceClick( e, balanceIndex ){
    	var ev = e || window.event;
    	var cmdKey = (typeof ev.metaKey == "undefined") ? false : ev.metaKey;
    	mvcDoCmd( new ToggleBalCmd( ev.ctrlKey || cmdKey, balanceIndex  ) );
    }

    /** handle event for 'click on an unreconciled panel'
     * @param {event} e browser event
     * @param {int} balanceIndex index into balance list (aka account data)
     */
    function onUnreconciledClick( e, balanceIndex ){
    	mvcDoCmd( new CreateTranCmd( kAppendTransaction, balanceIndex ) );
    }

    /** handle event for 'user selects from the transaction type menu'
     * @param {event} e browser event
     * @param {String} viewID ID of the controller generating this event
     */
    function onTrTypeSelect( e, viewID ){
    	mvcDoCmd( new TrTypeMenuCmd( viewID ) );
    	return true;
    }

    /** handle event for 'user selects from the account menu'
     * @param {event} e browser event
     * @param {int} selectedAccountKey the new menu-selection
     */
    function onAccountSelect( e, selectedAccountKey ){
    	mvcDoCmd( new SelectAccountCmd( selectedAccountKey ) );
    }

    /** handle event for 'entering web page' */
    function onLoad()
    {
    	grvTraceEvt("rats.js: in onload");
    	gMVCRootView.enable();

    // HACK! windows get goofed up if we dont suppress
    // the "please wait" window on launch...
    _gGrvHackStartupInhibitWait = true;

    	mvcDoCmd( new SelectFirstUnreconciledCmd() );

    //_gGrvHackStartupInhibitWait = false;//modern browsers are a pain with popups now..retire this technique
    	grvTraceEvt("rats.js: leaving onload");
    }

    /** handle event for 'save button pressed' */
    function onSaveBtnPressed(){
    	mvcDoCmd( new SaveAllAccountChangesCmd() );
    }

    /** handle event for 'cancel button pressed' */
    function onCancelBtnPressed(){
    	mvcDoCmd( new CancelAllAccountChangesCmd() );
    }

    /** handle event for 'exit button pressed' */
    function onExitBtnPressed(){
    	mvcDoCmd( new CloseWindowIfDataSavedCmd() );
    }

    /** handle event for 'about to leave web page';
     * warn if attempting to leave with unsaved changes
     */
    function onBeforeUnLoad()
    {	//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 ( gAccountData.isTrue() ){  // i.e. unsaved data
    		return "Edits are NOT saved. Are you sure you want to abandon your changes?";
    	}
    }

    /** handle AJAX event for 'reply received from "load account data" server-request'
     * @param {grvXMLreq} xmlReq the XML request being replied to
     */
    function onAccountLoadReply( xmlReq ){
    	mvcDoCmd( new LoadAccountDataCmd( xmlReq, false ) );
    }

    /** handle AJAX event for 'reply received from "save account data" server-request'
     * @param {grvXMLreq} xmlReq the XML request being replied to
     */
    function onAccountSaveReply( xmlReq )
    {	//since we get back official new version of Account Data, just
    	//do the same thing as asking for data load in the first place!
    	//...except on save we ask that the next unreconciled be autoselected
    	mvcDoCmd( new LoadAccountDataCmd( xmlReq, true ) );
    }

    grvTraceCmp("rats.js: End");

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

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