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

Pavel Krivanek pavel.krivanek at gmail.com
Wed Oct 18 07:15:17 EDT 2017


this is a simple use-case for the chagnes proposed in the pull request 374


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

    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.

    form := self instantiate: StandaloneFormPresenter on: self

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.

    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:

    nameTextInput := self newTextInput autoAccept: true.
    surnameTextInput := self newTextInput autoAccept: true.

    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

    workingModel := self model copy.
    self 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

    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.

    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

    self 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.

    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.

    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

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.

    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".

-- Pavel
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.pharo.org/pipermail/pharo-dev_lists.pharo.org/attachments/20171018/8e8bf883/attachment-0002.html>

More information about the Pharo-dev mailing list