We want to create a presenter that displays a PersonalAccount as its model and allows for its various aspects to be edited. This should be a top level shell window so a suitable superclass for our new class will be Shell.
Shell subclass: #PersonalAccountShell instanceVariableNames: 'namePresenter accountNumberPresenter initialBalancePresenter transactionsPresenter currentBalancePresenter ' classVariableNames: '' poolDictionaries: ''
The definition includes instance variables to hold separate presenters which will handle the editing of each of the relevant aspects of the model. These sub-presenters are initialized in the createComponents method.
createComponents "Private - Create the presenters contained by the receiver" super createComponents. namePresenter := self add: TextPresenter new name: 'name'. accountNumberPresenter := self add: TextPresenter new name: 'accountNumber'. initialBalancePresenter := self add: NumberPresenter new name: 'initialBalance'. transactionsPresenter := self add: ListPresenter new name: 'transactions'. currentBalancePresenter := self add: NumberPresenter new name: 'currentBalance'.
For each model aspect a suitable presenter is created and added to the shell composite. The sub-presenters are given names to identify them when the view is connected; each sub-view will be given an identical name so that presenter-view pairs can be matched and connected together. The choice of presenter to use for each aspect depends on the effective type of the aspect's value. Each of the aspects will be accessed using a ValueAspectAdaptor so that #value and #value: can be used to get and set its value. For this reason, each of the presenters that will be used to edit a model aspect will inherit from the class ValuePresenter. Take a look at the subclasses of ValuePresenter to see what's available. Obviously, if there was nothing yet available that was suitable then you'd have to create a new ValuePresenter subclass to do the job. In this case the existing subclasses will be sufficient.
The next task is to specify how the model is connected to the presenter.
model: aPersonalAccount "Set the model associated with the receiver." super model: aPersonalAccount. namePresenter model: (aPersonalAccount aspectValue: #name). accountNumberPresenter model: (aPersonalAccount aspectValue: #accountNumber). initialBalancePresenter model: (aPersonalAccount aspectValue: #initialBalance). currentBalancePresenter model: (aPersonalAccount aspectValue: #currentBalance) aspectTriggersUpdates. transactionsPresenter model: (aPersonalAccount transactions).
First of all the PersonalAccount is assigned as the actual model of our composite using the super message send. Then the models of all the sub-presenters are set to be ValueAspectAdaptors on the appropriate aspects of the account.
Tip: a shortcut to creating a ValueAspectAdaptor is to send #aspectValue: to the account object specifying the name of the aspect you want.
Take a look at the one special case here; creating the ValueAspectAdapator for #currentBalance. Once created the adaptor is sent #aspectTriggersUpdates. This is important since it informs the adaptor that the aspect it's connected to will trigger its own update notifications whenever it is changed. Do you remember the #currentBalance method we wrote for PersonalAccount?
PersonalAccount>>currentBalance: aNumber "Set the current balance of the receiver to aNumber." currentBalance := aNumber. self trigger: #currentBalanceChanged
See that , it triggers #currentBalanceChanged when the balance is assigned to. If we didn't send #aspectTriggersUpdates to the ValueAspectAdaptor we create for this aspect then it would believe that it was responsible for triggering these notifications itself. This would result in two notifications for each change in the current balance. Hardly the end of the world but irritating nonetheless.
The next step is to wire together the sub-presenters using the standard Smalltalk event notification mechanism. This wiring is implemented by overriding the createSchematicWiring method. It is only necessary to override this method if you wish to respond to events triggered by your sub-presenters. In this case we want to send an #editTransaction message to the PersonalAccountShell when a transaction in the transactions list is double-clicked.
createSchematicWiring "Private - Create the trigger wiring for the receiver" super createSchematicWiring. transactionsPresenter when: #actionRequested send: #editTransaction to: self.
The last essential task that must be considered when creating any presenter is to define class methods, defaultModel and defaultView. The former should answer an object which the presenter will use as its model in cases where this is not specified explicitly (which is the most likely situation). In this case we answer a default instance of PersonalAccount. The defaultView method must answer a resource name to use to load a default view. This must match the name under which the view is saved by the View Composer.
defaultModel "Answer a default model to be assigned to the receiver when it is initialized." ^PersonalAccount new
defaultView "Answer the resource name of the default view for the receiver." ^'DefaultView'
Tip: remember these are class methods.
Now add the remainder of the accessing methods for PersonalAccountShell.
selectedTransactionOrNil "Answer the currently selected transaction or nil if there is none" ^transactionsPresenter selectionOrNil
selectedTransactionOrNil: aPersonalAccountTransactionOrNil "Sets the currently selected transaction to aPersonalAccountTransactionOrNil. If nil if there will be no selection" ^transactionsPresenter selectionOrNil: aPersonalAccountTransactionOrNil
hasSelectedTransaction "Answer true it there is a currently selected transaction in the receiver"
^transactionsPresenter hasSelection
One of the fundamental ways in which a user can interact with a model is by issuing commands from a menu or toolbar. It is the presenter's role to intercept these commands and translate them into modifications to the model. Commands are parameterless messages and we need to provide the appropriate methods for handling them.
editTransaction "Edit the selected transaction" #todo "Write this later".
newTransaction "Prompt for a new transaction and add it to the receiver's model" #todo "Write this later".
removeTransaction "Removes the current transaction from the receiver's model" | transaction | transaction := self selectedTransactionOrNil. transaction notNil ifTrue: [ self model removeTransaction: transaction ].
These methods should go in the commands category. We will implement the #todos after we have created a suitable dialog for editing transactions.
Tip: you can use a #todo symbol to mark methods where something needs to be modified or implemented in future. You can then browse for references to the #todo symbols to find all the outstanding tasks. Remember the period following the comment, otherwise the statement will not be valid Smalltalk syntax.
By default any command that is implemented by a presenter will be enabled when the menu is pulled down. To change this default behavior and enable or disable commands directly, you must implement a queryCommand: method.
queryCommand: aCommandQuery "Enters details about a potential command for the receiver into aCommandQuery" super queryCommand: aCommandQuery. (#(editTransaction removeTransaction) includes: aCommandQuery command) ifTrue: [ aCommandQuery enabled: self hasSelectedTransaction ]
Click here to move on to the next section or here to go back to the previous section.