[Spec] Improved way of linking presenters to domain model objects

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[Spec] Improved way of linking presenters to domain model objects

Pavel Krivanek-3
Hi,

this is a simple use-case for the chagnes proposed in the pull request 374 (20551-Improve-way-how-Spec-presenters-are-linked-with-domain-models). 


I would like to know your comments on it.

Situation: You have a presenter that shows a form with information about a person. This form contains a submit button and restore button. Next to it it shows a table with currently saved results. It is only a demo so the domain object is stored directly in this presenter.

ComposablePresenterWithModel subclass: #FormPresenter
    instanceVariableNames: 'form table'

The instance variable "form" is a presenter of a form subcomponent, the "table" is a fast table.

The ComposablePresenterWithModel stores the model in an instance variable named "specCompatibleModel". This can be a Model or a NewValueHolder (as it is a subclass of Model).

We have our domain model class FormModel which contains information about a person like name, surname etc. It is a subclass of Object.

Object subclass: #FormModel
    instanceVariableNames: 'name surname ...' 

At the beginning we create a new instance of this class as a model for our presenter:

FormPresenter>>initialize
    self model: FormModel new.
    super initialize.

This will create in the instance variable "specCompatibleModel" a value holder that will contain our domain object and subscribes yourself to announcements of this value holder.
We need to have this model ready before we will initialize subpresenters because we will provide this model to our form.

FormPresenter>>initializeWidgets
    form := self instantiate: StandaloneFormPresenter on: self specCompatibleModel
    ...

We use here "self specCompatibleModel" and not "self model" because we want to use our value holder directly. If we would use "self model", the form would create a new value holder and then we would need to synchronize the data between the form and parent presenter manually.

When the model of the presenter will change, we will fill the table.

FormPresenter>>imodelChanged
    table items: { 
        self model name.
        self model surname. }

The presenter for the form is a subclass of ComposablePresenterWithModel too. It contains input boxes for name and surname. Then it includes buttons for submitting and restoring of the form content. It contains an instance variable "workingModel" to store current state of the form. It is different from the model because we want to be able to restore original data.

ComposablePresenterWithModel subclass: #StandaloneFormPresenter
    instanceVariableNames: 'workingModel nameTextInput surnameTextInput submitButton restoreButton'

To create the form is straightforward and there is nothing special on it:

StandaloneFormPresenter>>initializeWidgets
    nameTextInput := self newTextInput autoAccept: true.
    surnameTextInput := self newTextInput autoAccept: true.

StandaloneFormPresenter>>initializePresenter
    self submitButton action: [self submit].
    self restoreButton action: [self restore].

When we obtain a new model, we will create a new working model as copy of it and we will fill the form with its data

StandaloneFormPresenter>>modelChanged
    workingModel := self model copy.
    self fillFormWithWorkingModel.

StandaloneFormPresenter>>fillFormWithWorkingModel
    self nameTextInput text: workingModel name.
    self surnameTextInput text: workingModel surname.

When we restore the form, we will only do the same as in case of model change - create a new working copy and update the form 

StandaloneFormPresenter>>restore
    self modelChanged

When we submit the form we obtain the current data from the inputs, store them in the working model. Then we will replace the model with it and announce change of the model to other components.

submit
    workingModel name: self nameTextInput text.
    workingModel surname: self surnameTextInput text.
    self model: workingModel.
    self specCompatibleModel valueChanged.
This operation will force update of the parent presenter (that will update the table). The form itself will be updated too (and a new working copy will be created).

In this simple case we can make it work without need of a the model working copy. In the simpler approach we wil create parent presenter model as a subclass of Model.

Model subclass: #FormModel
    instanceVariableNames: 'name surname ...' 

The instantiation of StandaloneFormPresenter can be done on the model directly but it is optional because "self model" and "self specCompatibleModel" return the same object here.

self instantiate: StandaloneFormPresenter on: self model

When the model will change, we will simply fill the form

StandaloneFormPresenter>>modelChanged
    self fillForm

StandaloneFormPresenter>>fillForm
    self nameTextInput text: self model name.
    self surnameTextInput text: self model surname.

When the form will be submitted, we fill the model with new data and announce changes.

StandaloneFormPresenter>>submit
    self model name: self nameTextInput text.
    self model surname: self surnameTextInput text.
    self model valueChanged.

So the difference here is in absence of the value holder that will store the domain object. Instead of it we use directly announcer that is provided by the Model class and modify the domain object directly.

The original version with the working copy makes more sense in case when you modify the working copy directly on the fly when the user changes data.

    nameTextInput := self newTextInput
        autoAccept: true;
        whenTextChanged: [ 
            self workingModel name: nameTextInput text ].

Then during submitting you can use the the working copy directly to replace the model.

submit
    self model: workingModel.
    self specCompatibleModel valueChanged.

Sometimes for more complex applications it may be handy to use value holders with subinstances of Model inside, when you need to subscribe some presenters directly to models and in the same time to be able to swap then (and be notified about it). This combination is possible too, just use code linke:

self model: (NewValueHolder value: FormModel new).

...but then you need to take care if you are wokring with the value holder or with the model inside.

submit
    self specCompatibleModel value: self workingModel.
    self specCompatibleModel valueChanged.

So I hope this proposed changes are quite flexible and will make using of Spec much easier.

I'm not sure with the naming of "specCompatibleModel".

Cheers,
-- Pavel