Cleanest way for this MVP scenario.

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

Cleanest way for this MVP scenario.

gregarican
I am porting a CRM application into Smalltalk and one of the things it
does is pass back an XML-RPC array of customer purchases. Rather than
present this to the enduser in a long series of results I want to
present the enduser a screen with one purchase at a time and give them
a back and forward button to toggle through the purchase history.

Where I am running into trouble is implementing this using the MVP
philosophy/framework. I launch a presenter of one purchase event based
on the model where I provide an aspect that is the first event in that
customer's history. If I want to move forward to the next record would
I have to tear down the entire presenter and create a new one based on
a new reference to the model where the aspect now is the second event?
Or is it preferred to keep the same presenter and refresh it by
invoking its own methods to pass along details of the next event?

The model has an object representing the customer's entire purchase
history. This is an array. Then it also has a several objects that
represent elements of the first event in their purchase history. These
are a handful of strings. There's a method in the model that advances
to the next purchase event and returns access to this group of string
objects.

Sorry if I am not making sense perhaps. I am relatively new to Dolphin
Smalltalk and can't get my head around where to build in the
functionality I am looking for.


Reply | Threaded
Open this post in threaded view
|

Re: Cleanest way for this MVP scenario.

Schwab,Wilhelm K
> I am porting a CRM application into Smalltalk and one of the things it
> does is pass back an XML-RPC array of customer purchases. Rather than
> present this to the enduser in a long series of results I want to
> present the enduser a screen with one purchase at a time and give them
> a back and forward button to toggle through the purchase history.
>
> Where I am running into trouble is implementing this using the MVP
> philosophy/framework. I launch a presenter of one purchase event based
> on the model where I provide an aspect that is the first event in that
> customer's history. If I want to move forward to the next record would
> I have to tear down the entire presenter and create a new one based on
> a new reference to the model where the aspect now is the second event?
> Or is it preferred to keep the same presenter and refresh it by
> invoking its own methods to pass along details of the next event?

AFAIK, the preference is to throw away the existing presenter and
replace it with a new one.  My Pane Holders goodie offers a few
different encapsulations of this, and the archives contain similar
implementations.

Have a good one,

Bill


--
Wilhelm K. Schwab, Ph.D.
[hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Cleanest way for this MVP scenario.

Chris Uppal-3
Bill,

> AFAIK, the preference is to throw away the existing presenter and
> replace it with a new one.

I'm not trying to start a squabble, but I have to say that I disagree with that
entirely.  My other post gives details.

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: Cleanest way for this MVP scenario.

Chris Uppal-3
In reply to this post by gregarican
gregarican wrote:

> Where I am running into trouble is implementing this using the MVP
> philosophy/framework. I launch a presenter of one purchase event based
> on the model where I provide an aspect that is the first event in that
> customer's history. If I want to move forward to the next record would
> I have to tear down the entire presenter and create a new one based on
> a new reference to the model where the aspect now is the second event?
> Or is it preferred to keep the same presenter and refresh it by
> invoking its own methods to pass along details of the next event?

The way I would handle it is to decompose the application into two Presenters
with different Models.   The outer presenter (which might be a top-level
window) would have the customer's entire history as its Model.  That might be a
list of PurchaseRecords, or perhaps the Model would be some  object which
contains the history -- e.g. a Customer.  My other kind of presenter would take
as its Model just one PurchaseRecord.  I would use one of these as a
sub-component of the window.

The sub-component would be a subclass of Presenter, PurchaseRecordPresenter,
with a compound View to show the details of a purchase.  In
PurchaseRecordPresenter>>model: you connect the aspects of the PurchaseRecord
to the text views and whatnot.

The outer window would include whatever GUI you decide on for selecting a
purchase (drop down list, scrolling list, filtered list, back/forward arrows,
etc).  Whenever the user selects a purchase, it sends #model: with the
indicated PurchaseRecord to its nested PurchaseRecordPresenter.  Note that the
outer window knows nothing at all about how PurchaseRecords are displayed,
while a PurchaseRecordPresenter knows nothing at all about how PurchaseRecords
are selected.

It's a good idea to ensure that the PurchaseRecordPresenter will accept nil as
its model.  Alternatively (cleaner but more work) you could have a special
"null" PurchaseRecord which you use as the Model when the user hasn't actually
selected a PurchaseRecord.

(BTW, it's also a good idea to use "reference views" to embed the sub-component
in the outer window.  Hold down the ALT key when you drag the
PurchaseRecordPresenter's view into the outer View in the View composer.
That way you can make changes to one view without having to change the other.)

If you want an example of this in practise, then my "Ghoul" tool uses this
pattern.  The outer tool has a GhoulModel (created by parsing a error file).
That Model contains a list of GhoulStackTraces, and the tool presents a
drop-list of these from which the user can make a selection.  The selected
stack trace is then displayed as the Model of a nersted
GhoulStackTracePresenter.  In fact the pattern is repeated since each
GhoulStackTrace itself contains a list of GhoulStackFrames, and the user can
select which of these will be displayed as the Model of a sub-sub-component, a
GhoulStackFramePresenter.

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: Cleanest way for this MVP scenario.

gregarican
In reply to this post by Chris Uppal-3
Chris Uppal wrote:

> I'm not trying to start a squabble, but I have to say that I disagree with that
> entirely.  My other post gives details.
>
>     -- chris

Thanks for both of your viewpoints on this. Where I am still a little
puzzled is how to call another presenter object out of the existing
one. Here is how I pull the first record (a customer's wish list) from
the model and then invoke the presenter just using the Workspace:

contactWishes := CrmWishModel new.
firstWishItem := contactWishes getWishElt: 1.
displayCrmWishes := CrmWishShell createOn: firstWishItem.
displayCrmWishes model: firstWishItem.
displayCrmWishes show.

If I have a nextRecord control to advance to the next wish list item I
have the following objects defined in the CrmWishShell createComponents
method:

currentPositionPresenter := self add: TextPresenter new name:
'currentPosition'.
totalRecordsPresenter := self add: TextPresenter new name:
'totalRecords'.
nextRecordPresenter := self add: NumberPresenter new name:
'nextRecord'.
contactWishesPresenter := self add: CollectionPresenter new name:
'contactWishes'.

They are defined in the model: method as follows:

currentPositionPresenter model: (aCrmWishSet aspectValue:
#wishListPosition).
totalRecordsPresenter model: (aCrmWishSet aspectValue: #recordCount).
nextRecordPresenter model: (aCrmWishSet aspectValue:
#nextWishListPosition).
contactWishesPresenter model: (aCrmWishSet aspectValue: #contactWishes)

The contactWishes Presenter represents the entire set of customer wish
list items, of which I can present data as to the current record
number, total number of records, and which record number comes next
through using the other objects. Now how I can I best define a
nextRecord object that will be the forward button?


Reply | Threaded
Open this post in threaded view
|

Re: Cleanest way for this MVP scenario.

gregarican
Please disregard, as I stumbled upon what to do. I was trying to
reference the nextRecord object through presenter, aspect, etc. means.
Instead I just defined a variable in the CrmWishShell presenter
(nextRecordNum) that represents the next record number. And I also
defined a variable in the same presenter (contactWishRecord) that
represents the customer wish list array. Then my forward button does
this:

forward
        "Advance the presenter to the next wish record."
        | nextWishItem |
        CrmWishShell allInstances do: [:i | i hide].
        nextWishItem := contactWishRecord getWishElt: nextRecordNum.
        nextRecordNum := nextRecordNum+1.
        newCrmWishShell := CrmWishShell createOn: nextWishItem.
        newCrmWishShell show.
        self exit.

Works for me. This works, and hopefully doesn't break the MVP paradigm
of keeping the different elements of the program separate...


Reply | Threaded
Open this post in threaded view
|

Re: Cleanest way for this MVP scenario.

Schwab,Wilhelm K
gregarican wrote:

> Please disregard, as I stumbled upon what to do. I was trying to
> reference the nextRecord object through presenter, aspect, etc. means.
> Instead I just defined a variable in the CrmWishShell presenter
> (nextRecordNum) that represents the next record number. And I also
> defined a variable in the same presenter (contactWishRecord) that
> represents the customer wish list array. Then my forward button does
> this:
>
> forward
> "Advance the presenter to the next wish record."
> | nextWishItem |
> CrmWishShell allInstances do: [:i | i hide].
> nextWishItem := contactWishRecord getWishElt: nextRecordNum.
> nextRecordNum := nextRecordNum+1.
> newCrmWishShell := CrmWishShell createOn: nextWishItem.
> newCrmWishShell show.
> self exit.
>
> Works for me. This works, and hopefully doesn't break the MVP paradigm
> of keeping the different elements of the program separate...
>

If I am reading this correctly, I don't much care for it.  See either
Ghoul (noting that Chris and I appear to have found something on which
we disagree) [*], or my BugOff goodie for doing a less violent manuever
using Pane Holdres.

Have a good one,

Bill

[*] BTW, I think Ghoul is GREAT; I just would not send an newbie off to
follow its MVP example.


--
Wilhelm K. Schwab, Ph.D.
[hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Cleanest way for this MVP scenario.

Schwab,Wilhelm K
In reply to this post by Chris Uppal-3
Chris,

>>AFAIK, the preference is to throw away the existing presenter and
>>replace it with a new one.
>
>
> I'm not trying to start a squabble, but I have to say that I disagree with that
> entirely.  My other post gives details.

No offense taken.  I think what you have done is cleaner than the usual
yank/replace approach, but the latter is safer, especially for a newbie.
  It is quite easy to get stray connections (either references or by
triggers) that might create a debugging nightmare.

Have a good one,

Bill

--
Wilhelm K. Schwab, Ph.D.
[hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Cleanest way for this MVP scenario.

gregarican
In reply to this post by Chris Uppal-3
Chris Uppal wrote:

> The sub-component would be a subclass of Presenter, PurchaseRecordPresenter,
> with a compound View to show the details of a purchase.  In
> PurchaseRecordPresenter>>model: you connect the aspects of the PurchaseRecord
> to the text views and whatnot.
>
>
> The outer window would include whatever GUI you decide on for selecting a
> purchase (drop down list, scrolling list, filtered list, back/forward arrows,
> etc).  Whenever the user selects a purchase, it sends #model: with the
> indicated PurchaseRecord to its nested PurchaseRecordPresenter.  Note that the
> outer window knows nothing at all about how PurchaseRecords are displayed,
> while a PurchaseRecordPresenter knows nothing at all about how PurchaseRecords
> are selected.

I have split my presenters up so that there is one presenter that
represents the entire array of wish list items and another presenter
that represents a specifc wish list item. Where I am still puzzled
(which I must apologize since I am indeed very new to using Dolphin
Smalltalk) is how I can invoke a specific wish list item using all of
the #model: messages. I checked sample code of the Ghoul package as
well as the Pane Holder package but things still aren't clicking.

Are you (hopefully patient) folks willing to post a small simple
example of how I can have a method in the main presenter launch a
specific record in the subpresenter by that specific record's
recordnumber? Looking in the "Dolphin Smalltalk Companion" I can't find
this more complicated scenario outlined...


Reply | Threaded
Open this post in threaded view
|

Re: Cleanest way for this MVP scenario.

Chris Uppal-3
In reply to this post by Schwab,Wilhelm K
Bill,

> I think what you have done is cleaner than the usual
> yank/replace approach, but the latter is safer, especially for a newbie.
>   It is quite easy to get stray connections (either references or by
> triggers) that might create a debugging nightmare.

I've got to disagree again, I'm afraid.  Since the connection between the M and
the V/P is always established (and subsequently broken) via the single #model:
method, there is no difficulty at all (it requires no conscious effort) to
ensure that the links are broken, since one is always overwriting them with
something else using the /same/ code as before.

Of course, you do have to disconnect yourself from the old model, but that's
trivial:
    oldModel removeEventsTriggeredFor: self.
and not even necessary if the model is immutable -- generates no events.  Or, I
suppose, if nothing else will change the Model after it has been discarded; but
that's pretty dodgy, and I wouldn't rely on it myself.

Adding/removing MVP triads dynamically, on the other hand, is an advanced, and
somewhat tricky, technique which I would advise people to avoid unless they
actually need a dynamically configured or changing UI.  (Especially if Win98 is
included amongst the deployment targets ;-)  Note that instead of /reusing/ the
#model: code to configure the new GUI, you have to /duplicate/ the code.  You
both build the new UI components, and /then/ use essentially the same #model:
code as I would use to connect them to the new Model.

I'm not saying that there isn't the odd bug or gotcha lurking in the MVP
framework when used like this, but then there are problems with any technique
(given this is Windows, after all), and my experience is that swapping the
Model is simple, easy, reliable, and natural.

But I'll leave it to the reader to decide for him or herself ;-)

Cheers!

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: Cleanest way for this MVP scenario.

Schwab,Wilhelm K
Chris,

> I've got to disagree again, I'm afraid.  Since the connection between the M and
> the V/P is always established (and subsequently broken) via the single #model:
> method, there is no difficulty at all (it requires no conscious effort) to
> ensure that the links are broken, since one is always overwriting them with
> something else using the /same/ code as before.
>
> Of course, you do have to disconnect yourself from the old model, but that's
> trivial:
>     oldModel removeEventsTriggeredFor: self.
> and not even necessary if the model is immutable -- generates no events.  Or, I
> suppose, if nothing else will change the Model after it has been discarded; but
> that's pretty dodgy, and I wouldn't rely on it myself.

That's the problem: what if triggers are registered elsewhere, and on
other objects (sub aspects of the model, etc.)?  There is nothing to
prevent it.  Getting rid of an entire triad is the safer approach.
There is nothing to enforce the rules under which your approach is
robust, _especially_ with a newbie behind the wheel.


> Adding/removing MVP triads dynamically, on the other hand, is an advanced, and
> somewhat tricky, technique which I would advise people to avoid unless they
> actually need a dynamically configured or changing UI.

That is why I built a framework to handle it.


 > (Especially if Win98 is
> included amongst the deployment targets ;-)

What is the problem, beyond the usual 16 bit kernal stuff?


 > Note that instead of /reusing/ the
> #model: code to configure the new GUI, you have to /duplicate/ the code.  You
> both build the new UI components, and /then/ use essentially the same #model:
> code as I would use to connect them to the new Model.

That is not about the dynamics of writing one GUI, it's more a question
of going from one problem to the next.  If anything, I would submit that
remove/replace is more likely to work w/o changes.

Have a good one,

Bill


--
Wilhelm K. Schwab, Ph.D.
[hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: Cleanest way for this MVP scenario.

gregarican
In reply to this post by gregarican
gregarican wrote:

> Are you (hopefully patient) folks willing to post a small simple
> example of how I can have a method in the main presenter launch a
> specific record in the subpresenter by that specific record's
> recordnumber? Looking in the "Dolphin Smalltalk Companion" I can't find
> this more complicated scenario outlined...

I have worked this out now. I am keeping the same presenter up during
the record selections, and inspecting the presenter's objects I can see
that the records are paging forward and backward when I click on the
forward and back buttons in the view. Now how do I repaint the view so
that the underlying record the presenter is referencing appears on the
screen?


Reply | Threaded
Open this post in threaded view
|

Re: Cleanest way for this MVP scenario.

gregarican
gregarican wrote:

> I have worked this out now. I am keeping the same presenter up during
> the record selections, and inspecting the presenter's objects I can see
> that the records are paging forward and backward when I click on the
> forward and back buttons in the view. Now how do I repaint the view so
> that the underlying record the presenter is referencing appears on the
> screen?

Finally got this last detail taken care of. First I was passing a 'self
model:' message to recreate the presenter's details then a 'self view
invalidate' message to redraw the view. But given some of the ugliness
and clumsiness of recreating things using the 'self model:' message I
finally found out about the model's trigger: and presenter's
aspectTrigger: mechanisms to automatically update the presenter based
on the model having its state changed.

The Dolphin Smalltalk Companion was invaluable in helping me find out
about this based on the sample code on the CD. I tell you, maybe I am
not the swiftest but it seems as if learning all of this MVP stuff was
quite a task. There didn't seem to be a lot of detailed examples on the
Internet and the fine details of the Companion text weren't provided in
the book. But the CD unlocked the mysteries for me.

Some folks have said that Smalltalk is self-documenting and that just
reviewing source code is enough. For me that didn't seem to be the
case. Each language has its own drawbacks. In Ruby, for example, there
are certain libraries that I tried to use, but the API documentation
was all in Japanese or the library source was just geared for Linux/GCC
compilation. Using Java as another example, getting past the rigid
rules, syntactical hoops, and verbose coding style makes learning new
things difficult. Personally I love Smalltalk and most of its basic
ways click with me. But certain aspects of one specific Smalltalk
implementation are harder to get my head around, especially since it
can be hard to find a lot documentation and examples. In this case it
came down to the inner workings of the Dolphin MVP framework.

I do appreciate Chris's and Bill's replies and their goodies that used
some of these methods. Its just that perhaps I am too much of a newbie
to fully comprehend everything :-)