|
The Gravey Framework and RATS RIA | |||||||
| PREV NEXT | FRAMES NO FRAMES | |||||||
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.0
Requires:
| 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( <XMLreq> xmlReq )
handle event for 'reply received from "load account data" server-request' |
static void
|
onAccountSaveReply( <XMLreq> xmlReq )
handle event for 'reply received from "save account data" server-request' |
static void
|
onAccountSelect( <int> selectedAccountKey )
handle event for 'user selects from the account menu' |
static void
|
onBalanceClick( <int> balanceIndex )
handle event for 'click on a balance record' |
static void
|
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( <int> balanceIndex, <int> transIndex )
handle event for 'click on a transaction record' |
static Object
|
onTrTypeSelect( <String> viewID )
handle event for 'user selects from the transaction type menu' |
static void
|
onUnreconciledClick( <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.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+" of "+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+" 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+"'> </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> </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 ) ); }
|
The Gravey Framework and RATS RIA | |||||||
| PREV NEXT | FRAMES NO FRAMES | |||||||