Pattern: Composite MVP Component


Context

When creating a Model-View-Presenter triad, you will have decided whether the component can be built from a collaboration of other components or whether it is elemental in nature. This pattern discusses how to create Composite component once the underlying domain model class has been designed once you have already created a model class. presenter to handle the model.

Diagrammatic representation of a composite component triad

Solution

Create the presenter class

You will use the New Class pattern to create a new presenter class. First of all, you must decide where this is to be subclassed from. All composite presenters inherit from CompositePresenter in the class hierarchy. Follow these rules to decide on a suitable superclass.

  1. The first decision is whether you want the component to be embeddable in other components or whether it is only likely to be seen in a shell window. If the former is true you will normally descend your presenter directly from CompositePresenter. This is the most flexible situation since you will then be able to reuse your component in other composites that you design in future. The disadvantage is that you will not be able to directly use the component in a top level shell window.
  2. If your component is to appear in a shell window then the presenter must be subclassed beneath the Shell class. It can then connect to an instance of ShellView.
  3. If it is to handle a modal dialog style of interface then it must be subclassed beneath Dialog. This can then connect to an instance of DialogView.
  4. You have a further choice when creating a dialog. If the model of the component is to be immutable and you do not want to allow its internal state to be changed using accessor methods then you should subclass beneath ValueDialog. This gives the added flexibility that comes from using the ValueModel protocol, in particular, it allows you to "clip" your dialog presenter onto any aspect of a domain object using a ValueAspectAdaptor.

Required presenter methods

#defaultModel -- Once you have created a presenter class you should add a #defaultModel class method to answer an object to use as the default model when an instance of this presenter component is created. This is normally a suitably initialized instance of your model class. If you have created a ValueDialog subclass you should wrap this in a ValueHolder by sending #asValue to the model object.

#defaultView -- A presenter must also support a #defaultView class method to answer the name of a resource that will yield a default view to be used when a instance of the presenter is created. You may choose to delay writing this method until after you have created a suitable view class and registered a named instance with the Resource Manager.

#createComponents -- a composite presenter should override the #createComponents method to create the sub-presenter's from which it is composed. New presenters are added to the composite using #add:name:. Each sub-presenter is given a textual name which will lay to be used to match against a same named view when the composite is opened into a window.

Example: MethodBrowser>>createComponents
	"Private - Create the presenters contained by the receiver"
	super createComponents. 
	methodsPresenter := self add: MethodListPresenter new name: 'methods'.
	sourcePresenter := self add: SmalltalkWorkspace new name: 'source'.

#createSchematicWiring -- this method should be overridden to register the composite presenter with any event notifications that are generated by its sub-presenters or their views. This method is called once the composite has been fully created and its view opened and attached.

Example: MethodBrowser>>createSchematicWiring
	"Private - Create the trigger wiring for the receiver"
	super createSchematicWiring.
	methodsPresenter 
		when: #selectionChanged send: #onMethodSelected to: self;
		when: #selectionChanging: send: #onSelectionChanging: to: self;
		when: #actionPerformed send: #browseClasses to: self.
	self model
		when: #methodAdded: send: #onMethodAdded: to: self;
		when: #methodRemoved: send: #onMethodRemoved: to: self.

#model: -- when a model is assigned to a composite presenter it is usually necessary to connect its sub-presenters up to the same model. Very often the sub-presenters will be value presenters and will be hooked up to aspects of the composite model using ValueAspectAdaptors.

Example: ResourceIdentifierDialog>>model: aResourceIdentifier
	"Set the model associated with the receiver."
	super model: aResourceIdentifier.
	classTree model: (self model aspectValue: #owningClass).
	resourceName model: (self model aspectValue: #name).

Creating the view

Obviously, you must add additional methods to the presenter class to perform manipulations of the model as required by the user. It's probably make sense to add these methods later once you have had a first pass at designing a view class and testing the entire MVP triad together.

Designing the view class

Although you can create a view suitable for connecting to a composite presenter by creating a new View subclass, such views are almost invariably built using the View Composer. The View Composer works by allow you to build up an instance of a composite view by constructing it using view resources is held by the Resource Manager.

As you create a composite view to match a composite presenter you should be aware of the following:

Once you have created a suitable view in the View Composer, it must be saved down as a resource and associated with the presenter class with which it is intended to be used. Don't forget to complete a #defaultView class method for the presenter to identify one such view to be used by default.

Known Uses

The Dolphin development tools provide many examples of composite components. ClassBrowserShell is a composite descended from SmalltalkSystemShell. As such, it can only be used in a shell window and cannot be embedded within other composites.

MethodBrowser however, is a fine example of an embeddable composite component. We decided that we would need to embed a MethodBrowser within a ClassBrowserShell and therefore it had to be descended directly from CompositePresenter. The upshot of this was that an additional class, MethodBrowserShell, is needed to implement the version of a method browser that appears in a top-level window.

Related Patterns