// 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.0
 */

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

/** global 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.0
 */
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(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.0
 */
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;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.0
 */
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 event = 'onTransactionClick("'+ si+'","'+ ri +'")';
			return "<td onclick='"+event+"' 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.0
 */
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 event = 'onTransactionClick("'+ si+'","'+ ri +'")';
			return "<td class='py-state' onclick='"+event+"' 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.0
 */
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("'+ 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.0
 */
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 ? "hand" : "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("'+ 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 bordercolordark="white" bordercolorlight="black" cellspacing="0" cellpadding="0" border="1">' );
		HTML.push( '<thead><tr>' );
		HTML.push( '<th colspan="2" class="table-head" valign="middle" id="headTitle">Transaction Type</th>' );
		HTML.push( '<th class="table-head" valign="middle" id="headA">Balance A</th>' );
		HTML.push( '<th class="table-head" valign="middle" id="headB">Balance B</th>' );
		HTML.push( '<th class="table-head" valign="middle" id="headC">Balance C</th>' );
		HTML.push( '<th class="table-head" valign="middle" id="headD">Balance D</th>' );
if (kEnableTotalsColumn)
		HTML.push( '<th class="table-head" valign="middle" 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
 * @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 XMLreq( 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.0
 */
function LoadAccountDataCmd()
{
	/** @param {XMLreq} 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 = grvGetXslDOM( 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 {int} balanceIndex index into balance list (aka account data)
 * @param {int} transIndex index into transaction list (of balance)
 */
function onTransactionClick( balanceIndex, transIndex ){
	mvcDoCmd( new ToggleTranCmd( balanceIndex, transIndex ) );
}

/** handle event for 'click on a balance record'
 * @param {int} balanceIndex index into balance list (aka account data)
 */
function onBalanceClick( balanceIndex ){
	mvcDoCmd( new ToggleBalCmd( event.ctrlKey, balanceIndex ) );
}

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

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

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

/** handle event for 'entering web page' */
function 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;
}

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

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

/** handle event for 'reply received from "save account data" server-request'
 * @param {XMLreq} 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 ) );
}