Creating an Application

PersonalMoneyShell:
a Presenter for PersonalMoney

The final Presenter-View pair that we need to create will be the user interface for an instance of PersonalMoney which, in fact, is the entry point to the entire application.

If you recall, it is the responsibility of a PersonalMoney instance to hold a list of all the accounts for a particular user. Each account, in turn, holds all the transactions on it. Therefore the PersonalMoney object contains a network of all the data related to a user's accounts. It would be convenient if this information could be saved to, and restored from, disk easily. Luckily, the Dolphin MVP framework provides an abstract class, DocumentShell, which implements much of the grunt work necessary for saving a model to disk and allowing it to be reloaded later. So, we'll create our presenter class PersonalMoneyShell as a subclass of DocumentShell.

DocumentShell subclass: #PersonalMoneyShell
instanceVariableNames: 'ownerPresenter accountsPresenter'
classVariableNames: ''
poolDictionaries: ''

The createComponents and model: methods are pretty straightforward, as before:

createComponents
	"Private - Create the presenters contained by the receiver"

	super createComponents.
	ownerPresenter := self add: TextPresenter new name: 'owner'.
	accountsPresenter := self add: ListPresenter new name: 'accounts'.
model: aPersonalMoney
	"Set the model associated with the receiver."

	super model: aPersonalMoney.
	ownerPresenter model: (aPersonalMoney aspectValue: #owner).
	accountsPresenter model: (aPersonalMoney accounts).

Remember to specify the defaultModel and defaultView class methods:

defaultModel
	"Answer a default model to be assigned to the receiver when it
	is initialized."

	^PersonalMoney new
defaultView
	"Answer the resource name of the default view for the receiver."

	^'DefaultView'

Add the accessors:

selectedAccountOrNil
	"Answer the currently selected account or nil if there is none"

	^accountsPresenter selectionOrNil
selectedAccountOrNil: aPersonalAccountOrNil
	"Sets the currently selected account to aPersonalAccountOrNil.
	If nil if there will be no selection"

	^accountsPresenter selectionOrNil: aPersonalAccountOrNil
hasSelectedAccount
	"Answer true if there is a currently selected account in the receiver"

	^accountsPresenter hasSelection

And now the command handlers, once again to be placed in the commands category:

newAccount
	"Prompt for a new account and add it to the receiver's model"

	| newAccount |
	newAccount := self model addAccount: PersonalAccount new.
	self selectedAccountOrNil: newAccount.
	self editAccount
removeAccount
	"Removes the current account from the receiver's model"

	self hasSelectedAccount ifTrue: [
		self model removeAccount: self selectedAccountOrNil ]
editAccount
	"Edit the selected account"

	| account index shell |
	self hasSelectedAccount ifTrue: [	
		account := self selectedAccountOrNil.
		shell := PersonalAccountShell showOn: account.
		shell when: #viewClosed send: #updateAccount: to: self with: account ].

We tack on to the notification of #viewDestroyed here as an indication that the editing of a particular account has been completed. We can use this point to update the representation of the account in our accounts list.

Tip: this is only necessary be because we chose to edit our accounts using a modeless shell window rather than a dialog. In the latter case we would implicitly know when the editing operation was complete (following the #showModal call) and the update could be done at this time. However, using a shell is sometimes more user friendly than using a dialog, the price being some extra coding complexity.

updateAccount: aPersonalAccount
	"Update aPersonalAccount in the accounts list"

	| index |
	index := self model accounts indexOf: aPersonalAccount.
	self model accounts updateAtIndex: index

There are two additional class methods that can be overridden by subclasses of DocumentShell. These indicate what file extensions to use for the disk file representations of the model.

defaultFileExtension
	"Answer a default extension that will be used for files saved from
	the receiver"

	^'pm'
fileTypes
	"Answer an Array of file types that can be associated with this
	class of document."
	^#(('Personal Money files (*.pm)' '*.pm') 
	('All Files (*.*)' '*.*'))

Tip: remember these are class methods.


Click here to move on to the next section or here to go back to the previous section.