Saturday, 31 October 2015

Domain model, Presenter, View

Domain model classes should be subclasses of Model and not of Object.  This means they inherit the event methods.

Then we need a presenter equivalent to the app-level part of the domain model - i.e. that contains the presenter for the first Class the app domain model has.
In my app, a presenter that displays a DraftPool 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.

DraftPool subclass: #Model
instanceVariableNames: 'name poolId initialInd cumInd bouts'


Shell subclass: #DraftPoolShell
       'namePresenter poolIdPresenter initialIndPresenter boutsPresenter cumIndPresenter'

For every instance variable in the model, the shell includes an instance variable for the matching presenter.  These instance variables with the suffix 'Presenter's will handle the editing of each of the instance variables in the model.  They are set to be a specific subPresenter for the type of data in the model's instance varable in the Shells's createComponents method.

So the DraftFencing's app has top level object is the DraftPool, with these instance variables:
strings: name poolId 
numbers: initialInd cumInd
lists of pointers to lower level objects: bouts

Moving to presenters,

The Shell is is a DraftPoolShell, and it has instance variables:
textPresenter: namePresenter poolIdPresenter
numberPresenter: initialIndPresenter cumIndPresenter
listPresenter: boutsPresenter

( n.b. NumberPresenter are for integers or reals )

       "Private - Create the presenters contained by the receiver"

       super createComponents.
       namePresenter := self add: TextPresenter new name: 'name'.
       poolIdPresenter := self add: TextPresenter new name: 'poolId'.
       initialIndPresenter := self add: NumberPresenter new name: 'initialInd'.
       boutsPresenter := self add: ListPresenter new name: 'bouts'.
       cumIndPresenter := self add: NumberPresenter new name: 'cumInd'.

i.e. for each aspect of the domain model (the instance variables of the domain model) we create a suitable presenter, and add it to the shell composite. 

The choice of presenter to use for each aspect depends on the effective type of the aspect's value

The sub-presenters are given names to identify them when the view is connected and each sub-view will be given an identical name so that presenter-view pairs can be matched and connected together. 

Then we specify how to connect the model to the presenter, as a method of the Shell of the top-level Domain model object.

model: aDraftPool
"Set the model associated with the receiver."
super model: aDraftPool.
"Presenters for aspects which automatically trigger on changes" 
namePresenter model: (aDraftPool aspectValue: #name).
poolIdPresenter model: (aDraftPool aspectValue: #poolId).
initialIndPresenter model: (aDraftPool aspectValue: #initialInd).
boutsPresenter model: (aDraftPool bouts).
cumIndPresenter model: (aDraftPool aspectValue: #cumInd).

"Presenters for parts of a model which need to trigger their own events on changes"
cumIndPresenter model aspectTriggers: #cumIndChanged.

If a model triggers its own events when some aspects of it are changed,  we must explicitly state this, 
so when such an aspect is changed, i.e. other than by going through the adaptor itself,
the adaptor can to update its observers.

In DraftPool, the only aspect that triggers a change this way is #cumInd. 
We inform our newly created aspect adaptor that its model
triggers #cumIndChanged whenever the cumInd is updated. See

1st: we assign the DraftPool as the actual model of our composite using the super message send. 

2nd: Then we set the model of each sub-presenter to be ValueAspectAdaptors on the appropriate aspects of the Pool. 

Tip: a shortcut to creating a ValueAspectAdaptor is to send #aspectValue: to the account object specifying the name of the aspect you want.

3rd: set the special cases: The one special case here; creating the ValueAspectAdapator for #cumInd. Once created the adaptor is sent #aspectTriggers:. 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 ( remember the #cumInd method for DraftPool? )
cumInd: anInteger
     cumInd := aInteger.
     self trigger: #cumIndChanged

i.e. it triggers a #cumIndChanged event when the cumulative indicators is assigned to using #cumInd: directly. If we didn't send #aspectTriggers: to the ValueAspectAdaptor we create for this aspect (see above)  then it wouldn't be able to inform its observers when the #cumInd: method is called by some other object. This would result in some notifications being missed and the displays of cumulative indicator  not being updated correctly.

4th:  wire together the sub-presenters, using the standard Smalltalk event notification mechanism. This wiring is implemented by overriding the createSchematicWiring method. You only need 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 DraftPoolShell when a transaction in the transactions list is double-clicked.

       "(A Class method) Private - Create the trigger wiring for the receiver"

       super createSchematicWiring.
       boutsPresenter when: #actionRequested send: #editTransaction to: self.

6th, when creating any presenter,: define class methods, defaultModel and defaultView

defaultModel should answer an object which the presenter will use as its model in cases where this is not specified explicitly (which is the usual situation). In this case we answer a default instance of DraftPool. 

defaultView must answer a resource name to use to load a default view. The defaultView method inherited from Presenter class specifies a view name, not surprisingly, of 'Default view'. This is suitable for most purposes. We just have to make sure that this matches the name under which the actual view is saved by the View Composer. 

       "(A class method) Answer a default model to be assigned to the receiver when it is initialized."

       ^DraftPool new 

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.

No comments:

Post a Comment