Specific MVP troubles (Long)

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

Specific MVP troubles (Long)

Phil Lewis-2
OK. I've been whining about not understanding MVP for a few weeks now, and
last night I thought 'This is daft, you've successfully designed and coded
[x number of complex sytems], and you can't work out a few little classes?'.
So I sat down to suss it out.

The following is a very full description of what I tried to do, and the
compromises I had to make to get it working.

If anyone who knows Dolphin MVP can read it and let me know where I am going
wrong, I would be immeasurably grateful.

Obvious thing: Pick a simple domain, and implement a tiny app to just try
out the MVP stuff.

A library. People can add books, check books out and return them. A book has
a name. I want to know what books are in the library, and which are
currently available (i.e. not on loan).

NOTE: The following code was transcribed by hand. My machine with Dolphin is
my works laptop, and my machine with ng access is my personal box. The two
will not speak to each other yet, so I am writing this with my laptop beside
my desktop, and copying the code by hand. Any syntax errors are a result of
this process. I have the code running, honest. If you want a package, I can
send it.

I always start with domain classes, so I did this:

Object sublass: #Library
    instanceVariableNames: 'books'
    classVariableNames: ''
    poolDictionaries: ''
    classInstanceVariableNames: ''

Library(class)>>#new
    ^super new initialize

Library>>#initialize
    books := ListModel with:
                        (SortedCollection sortBlock: [:x :y | x title <= y
title])

I chose ListModel because I had it in mind that I would be using a
ListPresenter later to view the books. As I understand, the ListModel does
things with events, so we don't have to, and I like the sound of that. I
chose a SortedCollection because its as easy as anything else in Smalltalk.

Then we need to be able to add books to the Library, and check books out.

Library>>#addBook: aBook
    books add: aBook

Library>>#checkOut: aBook to: aPerson
    aBook onLoanTo: aPerson

I figured the easiest way to approach this is to tell a book that it is on
loan. Book will come later (I'm trying to program by intention just a little
bit).
Returning books then also becomes easy:

Library>>#return: aBook
    aBook beAvailable

The only other thing the Library needs to be able to do is tell us about all
its books, and about its currently available books:

Library>>#books
    ^books

Library>>#availableBooks
    ^books select: [:each | each isAvailable]

All we need now is a book that knows how to be on loan, and be availble.

Object sublass: #Book
    instanceVariableNames: 'title borrower'
    classVariableNames: ''
    poolDicationaries: ''
    classInstanceVariableNames: ''

Book needs some basic things:

Book(Class)>>#title: aString
    ^super new title: aString

Book>>#title: aString
    title := aString

Book>>#title
    ^title

Book>>#printOn: aStream (also #displayOn the same)
    aStream nextPutAll: title

Now it needs its specific behaviour. According to the Library, we need it to
be able to be on loan to a person, make itself available, and answer if it
is available:

Book>>#onLoanTo: aPerson
    borrower := aPerson

Book>>#isAvailable
    ^borrower isNil

Book>>#beAvailable
    borrower := nil

This was the simplest way I could imagine to fulfil the requirements.

Using a workspace to create a few books, add them to the library check a
couple out, and view availableBooks gives correct results. There is a
caveat. This depends on a book being returned as the same object that was
checked out. Its not prime time code, for sure. I'm not looking for
robustness, just a simple working model. A book is always checked to 'Object
new' as its person. (I was hoping get as far as adding people, but, well...)

Now we are going to need a Presenter. As I am unsure about MVP, I decided to
play (what I think is) safe, and I dragged my two classes onto Model, so
that they become subclasses of model. Run the test again. Everything cool.

Define a Presenter. I am going to start with one listbox just for the
collection of all books:

Shell subclass: #LibraryShell
    instancevariableNames: 'booksPresenter'
    classVariableNames: ''
    poolDictionaries: ''
    classinstanceVariableNames: ''

I did a defaultModel method, eventhough I won't use it (bad boy).

LibraryShell(Class)>>#defaultModel
    ^Library new

Then the guess work starts.

LibraryShell>>#createComponents
    super createComponents.
    booksPresenter := self add: ListPresenter new name: 'books'

LibraryShell>>#createSchematicWiring
    "What the hell is this???"
    super createSchematicWiring

LibraryShell>>model: aLibrary
    super model: aLibrary
    booksPresenter model: (aLibrary books)

I can figure what createComponents is doing (binding an element from the
view to an instance variable in the presenter?) and model: appears to be
setting the model of the view element to a particular attribute from the
model. But createSchematicWiring? From the docs suggested by Andy, I know
what it was intended for, but what do I do with it? Oh, well, press on...

Next step, create a view with a list box for all the books in the library. I
fired up View Composer, and built a ShellView with a single ListPresenter
called 'books'. Save it as the default view of LibraryShell.

In the workspace do:

LibraryShell showOn: lb

where lb is the instance of Library I have been using for test. It shows
with the list of books displayed. Hooray. Add a book to the model in the
workspace: it shows in the view. Fantastic.

Excited, I close the view, and reopen it in the View Composer. Add another
list presenter and call it availableBooks.

Go back to the presenter and make the following changes:

Shell subclass: #LibraryShell
    instancevariableNames: 'booksPresenter availableBooksPresenter'
    classVariableNames: ''
    poolDictionaries: ''
    classinstanceVariableNames: ''

LibraryShell>>#createComponents
    super createComponents.
    booksPresenter := self add: ListPresenter new name: 'books'
    availableBooksPresenter := self add: ListPresenter new name:
'availableBooks'

LibraryShell>>#createSchematicWiring
    "What the hell is this???"
    super createSchematicWiring

LibraryShell>>model: aLibrary
    super model: aLibrary
    booksPresenter model: (aLibrary books)
    availableBooksPresenter model: (aLibrary availableBooks)

I didn't expect this to work because this attribute is not a ListModel. In
fact, it is a derived attribute...But what the heck, run the test...

The shell shows, with two lists, correctly showing all books and available
books. Check a book out in the workspace, though, and the view does not
update. This is what I expected.

Now we are really deep into my ignorance, and the only lead I can find is
PersonalMoney. None of the other examples really make use of MVP. Personal
MoneyShell has:

currentBalancePresenter model: (aPersonalAccount aspectValue:
#currentBalance) aspectTriggers: #currentBalanceChanged

My understanding is that the method that results in a model value changing
should trigger an event like:

PersonalAccount>>currentBalance: aNumber
    ...
    self triggers: currentBalanceChanged

Now, in my model, the method that triggers the change in the model is
checkOut:to: But the change is actually in the value of availableBooks.

The only way I can see around this (and I have tried and it works) is to
have an instance variable to represent availableBooks, with getters and
setters for it. But I don't want that. Surely I don't have to compromise my
design to the extent that every value I want to have in a view must be an
instance variable in the model? I suspect not, but I just cannot figure out
where to go next. I have tried all kinds of combinations using
model:aspectTriggers:, but everything I tried gives a walkback.

Eventually I realized the walkback was saying that model:aspectTriggers is
not understood. And true enough, it is not there in any class in the
heirarchy. Intrigued, I do:

PersonalMoneyShell show

Clicking New (for new account) gives exactly the same error!

I really need some info about MVP now. I have read all the references given
by Andy Bower in his first 'night school' post, but the problem there is
that all the writing is either abstract, or it only covers basic easy uses.
The examples are all really simple, except for personal money, and that
doesn't work.

I know that Dolphins MVP is not broken, because it is being used by the tool
itself! I have tried to read some of the tool source, but it is all very
complex, and I can't begin  to figure it in the short time I have.

If someone can show me just how to get a working view on the model I have
given here (i.e. with the derived attribute, not with all instance
variables) I think that may give me the breakthrough I need.

If I could get a working copy of PersonalMoney, that would also be a great
help.

Thanks

Phil


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Peter van Rooijen
Phil,

I'd say your example is perfectly sound, and your approach is certainly
reasonable. I hope it gives good info to those in the know about what could
be made clearer in the introduction to MVP.

I'd give you the answers if I had them, but I don't (I haven't done any GUI
stuff in Dolphin). And I'm sure curious to hear what they are.

Thanks,

Peter van Rooijen

"Phil Lewis" <[hidden email]> wrote in message
news:91qvub$qa$[hidden email]...
> OK. I've been whining about not understanding MVP for a few weeks now, and
> last night I thought 'This is daft, you've successfully designed and coded
> [x number of complex sytems], and you can't work out a few little
classes?'.
> So I sat down to suss it out.
>
> The following is a very full description of what I tried to do, and the
> compromises I had to make to get it working.
>
> If anyone who knows Dolphin MVP can read it and let me know where I am
going
> wrong, I would be immeasurably grateful.
>
> Obvious thing: Pick a simple domain, and implement a tiny app to just try
> out the MVP stuff.
>
> A library. People can add books, check books out and return them. A book
has
> a name. I want to know what books are in the library, and which are
> currently available (i.e. not on loan).
>
> NOTE: The following code was transcribed by hand. My machine with Dolphin
is
> my works laptop, and my machine with ng access is my personal box. The two
> will not speak to each other yet, so I am writing this with my laptop
beside
> my desktop, and copying the code by hand. Any syntax errors are a result
of
> this process. I have the code running, honest. If you want a package, I
can

> send it.
>
> I always start with domain classes, so I did this:
>
> Object sublass: #Library
>     instanceVariableNames: 'books'
>     classVariableNames: ''
>     poolDictionaries: ''
>     classInstanceVariableNames: ''
>
> Library(class)>>#new
>     ^super new initialize
>
> Library>>#initialize
>     books := ListModel with:
>                         (SortedCollection sortBlock: [:x :y | x title <= y
> title])
>
> I chose ListModel because I had it in mind that I would be using a
> ListPresenter later to view the books. As I understand, the ListModel does
> things with events, so we don't have to, and I like the sound of that. I
> chose a SortedCollection because its as easy as anything else in
Smalltalk.

>
> Then we need to be able to add books to the Library, and check books out.
>
> Library>>#addBook: aBook
>     books add: aBook
>
> Library>>#checkOut: aBook to: aPerson
>     aBook onLoanTo: aPerson
>
> I figured the easiest way to approach this is to tell a book that it is on
> loan. Book will come later (I'm trying to program by intention just a
little
> bit).
> Returning books then also becomes easy:
>
> Library>>#return: aBook
>     aBook beAvailable
>
> The only other thing the Library needs to be able to do is tell us about
all

> its books, and about its currently available books:
>
> Library>>#books
>     ^books
>
> Library>>#availableBooks
>     ^books select: [:each | each isAvailable]
>
> All we need now is a book that knows how to be on loan, and be availble.
>
> Object sublass: #Book
>     instanceVariableNames: 'title borrower'
>     classVariableNames: ''
>     poolDicationaries: ''
>     classInstanceVariableNames: ''
>
> Book needs some basic things:
>
> Book(Class)>>#title: aString
>     ^super new title: aString
>
> Book>>#title: aString
>     title := aString
>
> Book>>#title
>     ^title
>
> Book>>#printOn: aStream (also #displayOn the same)
>     aStream nextPutAll: title
>
> Now it needs its specific behaviour. According to the Library, we need it
to

> be able to be on loan to a person, make itself available, and answer if it
> is available:
>
> Book>>#onLoanTo: aPerson
>     borrower := aPerson
>
> Book>>#isAvailable
>     ^borrower isNil
>
> Book>>#beAvailable
>     borrower := nil
>
> This was the simplest way I could imagine to fulfil the requirements.
>
> Using a workspace to create a few books, add them to the library check a
> couple out, and view availableBooks gives correct results. There is a
> caveat. This depends on a book being returned as the same object that was
> checked out. Its not prime time code, for sure. I'm not looking for
> robustness, just a simple working model. A book is always checked to
'Object
> new' as its person. (I was hoping get as far as adding people, but,
well...)
>
> Now we are going to need a Presenter. As I am unsure about MVP, I decided
to

> play (what I think is) safe, and I dragged my two classes onto Model, so
> that they become subclasses of model. Run the test again. Everything cool.
>
> Define a Presenter. I am going to start with one listbox just for the
> collection of all books:
>
> Shell subclass: #LibraryShell
>     instancevariableNames: 'booksPresenter'
>     classVariableNames: ''
>     poolDictionaries: ''
>     classinstanceVariableNames: ''
>
> I did a defaultModel method, eventhough I won't use it (bad boy).
>
> LibraryShell(Class)>>#defaultModel
>     ^Library new
>
> Then the guess work starts.
>
> LibraryShell>>#createComponents
>     super createComponents.
>     booksPresenter := self add: ListPresenter new name: 'books'
>
> LibraryShell>>#createSchematicWiring
>     "What the hell is this???"
>     super createSchematicWiring
>
> LibraryShell>>model: aLibrary
>     super model: aLibrary
>     booksPresenter model: (aLibrary books)
>
> I can figure what createComponents is doing (binding an element from the
> view to an instance variable in the presenter?) and model: appears to be
> setting the model of the view element to a particular attribute from the
> model. But createSchematicWiring? From the docs suggested by Andy, I know
> what it was intended for, but what do I do with it? Oh, well, press on...
>
> Next step, create a view with a list box for all the books in the library.
I

> fired up View Composer, and built a ShellView with a single ListPresenter
> called 'books'. Save it as the default view of LibraryShell.
>
> In the workspace do:
>
> LibraryShell showOn: lb
>
> where lb is the instance of Library I have been using for test. It shows
> with the list of books displayed. Hooray. Add a book to the model in the
> workspace: it shows in the view. Fantastic.
>
> Excited, I close the view, and reopen it in the View Composer. Add another
> list presenter and call it availableBooks.
>
> Go back to the presenter and make the following changes:
>
> Shell subclass: #LibraryShell
>     instancevariableNames: 'booksPresenter availableBooksPresenter'
>     classVariableNames: ''
>     poolDictionaries: ''
>     classinstanceVariableNames: ''
>
> LibraryShell>>#createComponents
>     super createComponents.
>     booksPresenter := self add: ListPresenter new name: 'books'
>     availableBooksPresenter := self add: ListPresenter new name:
> 'availableBooks'
>
> LibraryShell>>#createSchematicWiring
>     "What the hell is this???"
>     super createSchematicWiring
>
> LibraryShell>>model: aLibrary
>     super model: aLibrary
>     booksPresenter model: (aLibrary books)
>     availableBooksPresenter model: (aLibrary availableBooks)
>
> I didn't expect this to work because this attribute is not a ListModel. In
> fact, it is a derived attribute...But what the heck, run the test...
>
> The shell shows, with two lists, correctly showing all books and available
> books. Check a book out in the workspace, though, and the view does not
> update. This is what I expected.
>
> Now we are really deep into my ignorance, and the only lead I can find is
> PersonalMoney. None of the other examples really make use of MVP. Personal
> MoneyShell has:
>
> currentBalancePresenter model: (aPersonalAccount aspectValue:
> #currentBalance) aspectTriggers: #currentBalanceChanged
>
> My understanding is that the method that results in a model value changing
> should trigger an event like:
>
> PersonalAccount>>currentBalance: aNumber
>     ...
>     self triggers: currentBalanceChanged
>
> Now, in my model, the method that triggers the change in the model is
> checkOut:to: But the change is actually in the value of availableBooks.
>
> The only way I can see around this (and I have tried and it works) is to
> have an instance variable to represent availableBooks, with getters and
> setters for it. But I don't want that. Surely I don't have to compromise
my
> design to the extent that every value I want to have in a view must be an
> instance variable in the model? I suspect not, but I just cannot figure
out

> where to go next. I have tried all kinds of combinations using
> model:aspectTriggers:, but everything I tried gives a walkback.
>
> Eventually I realized the walkback was saying that model:aspectTriggers is
> not understood. And true enough, it is not there in any class in the
> heirarchy. Intrigued, I do:
>
> PersonalMoneyShell show
>
> Clicking New (for new account) gives exactly the same error!
>
> I really need some info about MVP now. I have read all the references
given
> by Andy Bower in his first 'night school' post, but the problem there is
> that all the writing is either abstract, or it only covers basic easy
uses.
> The examples are all really simple, except for personal money, and that
> doesn't work.
>
> I know that Dolphins MVP is not broken, because it is being used by the
tool

> itself! I have tried to read some of the tool source, but it is all very
> complex, and I can't begin  to figure it in the short time I have.
>
> If someone can show me just how to get a working view on the model I have
> given here (i.e. with the derived attribute, not with all instance
> variables) I think that may give me the breakthrough I need.
>
> If I could get a working copy of PersonalMoney, that would also be a great
> help.
>
> Thanks
>
> Phil
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ian Bartholomew-3
In reply to this post by Phil Lewis-2
Phil,

If nobody beats me to it I'll reply to the rest of your post tomorrow.

> currentBalancePresenter model: (aPersonalAccount aspectValue:
> #currentBalance) aspectTriggers: #currentBalanceChanged

> If I could get a working copy of PersonalMoney, that would also be a great
> help.

The package has got a set of brackets missing. It should read

currentBalancePresenter model: ((aPersonalAccount aspectValue:
#currentBalance) aspectTriggers: #currentBalanceChanged)

Ian


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ian Bartholomew-3
In reply to this post by Phil Lewis-2
Phil,

OK. Let's have a go then. The following uses your original Book class and a
slightly modified Library class. We'll come to the LibraryShell later.

I think the best way to proceed is to take a step back and return to a basic
MVP set-up. Your original version shared a ListModel between your Library
and the LibraryShell's bookPresenter. While this is fine, and we will return
to that technique later, for explanation purposes it somewhat obscures what
is going on.

I should also point out that by choosing Lists, rather than simple Strings
or Integers, for your example you have probably made it a bit more difficult
to understand. Anyway....

The Book class is the same as your original

==== Book class

title: aString
    ^super new title: aString

==== Book

isAvailable
    ^borrower isNil

onLoanTo: aPerson
    borrower := aPerson

beAvailable
    borrower := nil

printOn: aStream
    aStream nextPutAll: title

title
    ^title

title: anObject
    title := anObject

The Library classes has some changes - detailed after each method

==== Library

books
    ^books

getBook: aString
    ^self books
        detect: [:each | each title = aString]
        ifNone: [self error: 'Missing book']

This is an addition to get over the problem you had of needing to specify
the actual book object in the list. Now, when you borrow or return a book
you can just specify the title and the correct Book object is located from
the library.

initialize
    super initialize.
    books := SortedCollection sortBlock: [:a :b | a title <= b title]

Major change. We make no assumptions about who will be using the Library so
rather than using a ListModel we store the Books in a SortedCollection
Small change - I find it advisable that all initialize methods supersend
first to prevent surprises.

checkOut: aString to: aPerson
    (self getBook: aString) onLoanTo: aPerson.
    self trigger: #libraryStatusChanged

When a book is loaned out we locate the book, signal it is on loan and
trigger a #libraryStatusChanged event (see below)

addBook: aBook
    books add: aBook.
    self trigger: #libraryChanged

When the full library is changed we trigger a #libraryChanged event

availableBooks
    ^books select: [:each | each isAvailable]

return: aString
    (self getBook: aString) beAvailable.
    self trigger: #libraryStatusChanged

When a book is returned out we trigger a #libraryStatusChanged event

Why the triggers?. The Library is now a stand alone module (model). It has
no knowledge about who owns it or what they need to know. By using the
triggered events the Library is just letting anybody who is interested know
that it has changed in some way. It is up to the observers to register an
interest in an event that the library triggers and then be prepared to deal
with that event in an appropriate way.


==== LibraryShell class

defaultModel
    ^Library new

As you did I've used a default model but, because we will pass a fully
initialized Library to the LibraryShell when it is opened this default model
is not used. It would probably be better to have a singleton Library object
in the image and use that here but that can wait for later.

==== LibraryShell

createSchematicWiring
    super createSchematicWiring.
    self model
        when: #libraryChanged
            send: #onLibraryChanged
            to: self;
        when: #libraryStatusChanged
            send: #onLibraryStatusChanged
            to: self

So what the hell does this do? This connects our model, an instance of the
Library class, to the receiver, an instance of LibraryShell - it connects
the Model and the Presenter parts of the MVP triad. The LibraryShell knows
about the Library, it has to be able to register for the events that the
Library triggers and request information from it, but the Library has no
knowledge about the LibraryShell - it just triggers the appropriate events
and returns the current information when asked.

The above is simply saying that when the LibraryShell triggers an
#libraryChanged event (see Library>>addBook:) then the
LibraryShell>>onLibraryChanged method must be evaluated

createComponents
    super createComponents.
    booksPresenter := self add: ListPresenter new name: 'books'.
    availableBooksPresenter := self add: ListPresenter new name:
'availableBooks'.

onLibraryStatusChanged
    availableBooksPresenter list: self model availableBooks

When the Library made a change to the borrowed status of one of the books it
triggered a #libraryStatusChanged event. The
LibraryShell>>connectSchematicWiring method registered the LibraryShell for
that event and set the resulting action to be the evaluation of this method.
We know the list of available books has changed (because the Library
triggered the event) so we just ask the Library for the new list and pass
that on to the availableBooksPresenter. _It is up to the availableBooks MVP
triad (ListModel/ListView/ListPresenter) to arrange for the display to be
updated_

onViewOpened
    super onViewOpened.
    self onLibraryChanged.
    self onLibraryStatusChanged

This is an addition. We have to arrange it so that when the LibraryShell is
opened that the lists held by the booksPresenter and availableBooksPresenter
are initialised with the current state of the Library. We could do that by
sending #list: to the presenter but, complying with the "do it once"
principle, we will just simulate the triggering of the #libraryChanged and
#libraryStatusChanged events which we know will update the list.

onLibraryChanged
    booksPresenter list: self model availableBooks.
    self onLibraryStatusChanged

See the explanation for #onLibraryStatusChanged. The only difference is that
the Library has triggered the #libraryChanged event rather than the
#libraryStatusChanged event
We also recognise here that as we have two presenters displaying the list of
books we need to update both when the Library changes. The Library has no
knowledge of this, it just knows that the books it contains has changed and
triggered the #libraryChanged event. It is up to us, by simulating a
#libraryStatusChanged event to ensure the other list is also updated.

Testing. Evaluate is a workspace
lib := Library new.
lib
    addBook: (Book title: 'Jude The Obscure');
    addBook: (Book title: 'Far From The Madding Crowd');
    addBook: (Book title: 'The Return Of The Native');
    addBook: (Book title: 'Under The Greenwood Tree').
LibraryShell showOn: lib.

Add a new book to the Library
lib addBook: (Book title: 'The Trumpet Major')

Borrow a book and it should disappear from the appropriate list
lib checkOut: 'The Return Of The Native' to: 'Me'

Return it and it should reappear
lib return: 'The Return Of The Native'

What I was trying to illustrate here is the way that events are used to
communicate between a Model and a Presenter, informing the Presenter that a
change of state has occurred and that it should perform any actions in feels
necessary.

The next episode will show how you can replace the SortedCollection in
Library with a ListModel shared between Library and the booksPresenter MVP
triads. This will mean that the #libraryChanged event and associated methods
in Library can be removed.

Ian


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Stefan Elisa Kapusniak
In reply to this post by Phil Lewis-2
In comp.lang.smalltalk.dolphin, "Phil Lewis" <[hidden email]> wrote:
[...stuff...]

  I think changes something like the following should do
  the job.  I don't guarantee it's good style tho', and
  I haven't tested it _at all_...


>Library>>#checkOut: aBook to: aPerson
>    aBook onLoanTo: aPerson.
     self trigger: #availableChanged.

>Library>>#return: aBook
>    aBook beAvailable.
     self trigger: #availableChanged.


>LibraryShell>>#createSchematicWiring
>    "What the hell is this???"
     "As I understand it it's for specifying where triggered
      events are routed to for handling"

>    super createSchematicWiring

    "we're going to handle any #availableChanged events triggered
     on our model (which should be a Library) ourselves"

     self model
        when: #availableChanged send: #onAvailableChanged to: self.


 LibraryShell>>#onAvailableChanged
    "The available books have been changed so the relevant
     sub-presenter needs refreshing"

    "Simple brute force and ignorance approach, we just
     refetch the availableBooks and plug them in again."

     availableBooksPresenter model: (self model availableBooks).


-- Kapusniak, Stefan e


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ted Bracht-2
In reply to this post by Phil Lewis-2
Hi Phil,

Looks good so far, let me try to give some hints.

> But createSchematicWiring? From the docs suggested by Andy, I know
> what it was intended for, but what do I do with it? Oh, well, press on...
>
The #createSchematicWiring can be seen as a guard on watch, waiting to see
if anything happens. If something happens in the environment (really,
everything, keyboard events, to mouse events, window size changes, window
focus changes, data coming in from an external source, model changes and so
on), he (the guard) checks his list of things (triggers) he has to react to.
If the event is on the list, he will fire off the specified method.

>
> LibraryShell>>model: aLibrary
>     super model: aLibrary
>     booksPresenter model: (aLibrary books)
>     availableBooksPresenter model: (aLibrary availableBooks)

The #model: method is run once when you open the view, therefore the
Library>>availableBooks method is run and returns the available books at the
time of opening the view.

>
> currentBalancePresenter model: (aPersonalAccount aspectValue:
> #currentBalance) aspectTriggers: #currentBalanceChanged
>

Like Ian said, this should have extra brackets, otherwise the message
#model:aspectTriggers: is sent to the presenter.

The intention of this line of code is:

Connect the currentBalancePresenter to the result of:
((aPersonalAccount aspectValue: #currentBalance) aspectTriggers:
#currentBalanceChanged)

And this line of code can be taken apart as:
1. (aPersonalAccount aspectValue: #currentBalance)
and
2. (anAspect) aspectTriggers: #currentBalanceChanged

In line 1. the currentBalance instance variable is wrapped into an Aspect.
The advantage of using aspects is that they have a common language,
independent of what they contain, they can be set with the message #value:
and can be read with the message #value. They basically convert the
accessors in the model (like #currentBalance and #currentBalance:) to
#value:/#value. On top of that they automatically trigger any changes to the
ir contents. The limitations of the Aspects are that they can only wrap
around 'simple' objects, like strings, numbers, dates.

Line 2 tells the aspect that when the specified trigger is broadcasted, the
value wrapped in the aspect is changed. This is basically a shortcut to what
happens automatically anyway, but as we know exactly when the balance
changes (namely when we create a transaction), we can explicitly inform the
aspect, which saves the system to have to go through all the aspects and see
if they map to the one that has changed.

> My understanding is that the method that results in a model value changing
> should trigger an event like:
>
> PersonalAccount>>currentBalance: aNumber
>     ...
>     self triggers: currentBalanceChanged
>
> Now, in my model, the method that triggers the change in the model is
> checkOut:to: But the change is actually in the value of availableBooks.
>
> The only way I can see around this (and I have tried and it works) is to
> have an instance variable to represent availableBooks, with getters and
> setters for it. But I don't want that. Surely I don't have to compromise
my
> design to the extent that every value I want to have in a view must be an
> instance variable in the model? I suspect not, but I just cannot figure
out
> where to go next.

You certainly don't have to have an instance variable for it. You just want
the #availableBooks method to be run whenever something happens with any of
the books in the library.

You could for example add a method #updateAvailableBooks to the presenter:

LibraryShell>>updateAvailableBooks
    availableBooksPresenter model: (aLibrary availableBooks)

This would run the #availableBooks again and reconnect it to the presenter.

Then you need to tell the shell when to run this code. We can use our guard
for this:

LibraryShell>>createSchematicWiring
    super createSchematicWiring.
    self model when: #availableBooksChanged send: #updateAvailableBooks to:
self.

The last thing you then need to do is fire off the trigger whenever the
available books might have changed, like with adding books, borrowing and
returning. They all need to have the following line:
    self trigger: #availableBooksChanged

This is about how it should work, I haven't tested it, there might be some
details missing, but it should give you some idea.

Hope this helps,

Ted


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ian Bartholomew
In reply to this post by Ian Bartholomew-3
Stage 2 - replace the SortedCollection in the Library class with a ListModel
and share the ListModel with the LibraryShell's bookPresenter. This will
mean that we can wash our hands of all responsibility for ensuring the
Library and bookPresenter stay in sync.

Changes to Library

addBook: aBook
    books add: aBook.
    self trigger: #libraryStatusChanged

Originally this triggered a #libraryChanged event but we are not now using
that, see later. We still need to know when the library has changed though
as LibraryShell has to update the available list as well. NB This is a
temporary workaround and will be removed in the next version

initialize
    super initialize.
    books := ListModel with: (SortedCollection
        sortBlock: [:a :b | a title <= b title])

We now wrap the books SortedCollection in a ListModel. A side effect of
this, naturally, is that the unchanged #books method now answers a ListModel
rather than a SortedCollection

LibraryShell has a few more changes.

createSchematicWiring
    super createSchematicWiring.
    self model
        when: #libraryStatusChanged
        send: #onLibraryStatusChanged
        to: self

We have just removed references to the #libraryChanged event that is no
longer triggered by Library

model: aLibrary
    super model: aLibrary.
    booksPresenter model: aLibrary books

When we created out booksPresenter, in the createComponents method, it used
the ListPresenter>>defaultModel method to create the Model for the MVP
triad. What we are doing here is replacing _that_ ListModel with the one
from our Library - remember #books now answers a ListModel.

_Important_ When we send #model: to the booksPresenter it, via a few methods
starting at Presenter>>model:) eventually arrives at
IconicListAbstract>>connectModel. This registers itself as an interested
party for the events triggered by the ListModel. When, for example, our
Library adds a book to the Library the ListModel (books) will trigger an
#item:addedAtIndex: event. Because the ListPresenter (booksPresenter)  has
now registered for this event it will be informed and can arrange for the
associated ListView (the ListModel/ListView/ListPresenter MVP triad it is
part of) to be updated. We need to do nothing as this will happen
automatically due to the link between the ListPresenter and Library
ListModel that we set up above.

onLibraryChanged

Removed as Library no longer triggers this event

onViewOpened
    super onViewOpened.
    self onLibraryStatusChanged

We still need to initialise the availableBooksPresenter

So what we have is three MVP triads working together

#1 A ListModel/Shell/ShellView. This is comprised of the ListModel held in
the Library instance and the Shell/ShellView that we created for
LibraryShell

#2 A ListModel/ListPresenter/ListView. This is comprised of the ListModel
held in the Library instance and the ListPresenter/ListView created in the
LibraryShell>>createComponents method and referenced by the booksPresenter
instVar. Because it shares a model with #1 then we need have no further
dealings with it - it will sort out all the event triggering itself.

#3 A ListModel/ListPresenter/ListView. This is comprised of a separate MVP,
the ListModel is the default created by the ListPresenter we created in the
LibraryShell>>createComponents method and referenced by the
availableBooksPresenter instVar. Because there is NO connection between the
Library and this MVP we have to do all the triggering and updating (of the
ListModel's list) ourselves. However the MVP triad sorts out the triggers
between _it's_ ListModel/ListPresenter/ListView by itself.

By sharing a ListModel between Library and #2 we have removed the need for
any intervention by us. However the Library still has some knowledge that it
does not need. It is not the job of the Library to notify the
LibraryShell, via the trigger in Library>>addBook:, that LibraryShell
needs to update it's availableBooksPresenter. That linkage will be removed
in episode three.

Ian


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

plewis66
Ian, This is Phil - original asker - at a diggrent machine again!

What you have said so far is brilliant. Thanks. It is beginning to sink
in. I haven't tied any of it yet (I'm reading this in my 10 minute
lunch break :-( XMas soon :-)

I have one question so far, and this is a bit I have never managed to
get an insight into.

The model is raising events, and createSchematicWiring is basically
sort of mutating those into other events with when:send:to:, eg:

self model
        when: #libraryStatusChanged
        send: #onLibraryStatusChanged
        to: self

So, the Library raises #libraryStatusChanged, and this is caught
somewhere up the chain by cSW, which then sends #onLibraryStatusChanged
to Self, where self is the LibraryShell. So what does the LibraryShell
do with #onLibraryStatusChanged? Do we write some kind of message
handler? Thisbit is really unclear to me...

Really, thanks for this.

Phil


Sent via Deja.com
http://www.deja.com/


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ian Bartholomew-3
In reply to this post by Ian Bartholomew
Stage 3 - Remove the #libraryStatusEvent trigger from Library and replace
it's function with events triggered from the ListModel.

A bit of explanation first. The ListModel that Library holds, containing the
collection of Books, can have two main operations performed on it.

#1 The collection of books is changed - a book is either added or a book is
removed from the ListModel's collection

#2 A change is made to one of the Books contained within the ListModel - a
book is loaned and it's internal state changes to reflect the borrowing.

#1 is easy for the ListModel to cope with. The only way to change the
ListModel's collection (without changing the complete list) is to go through
it's #addXXX or #removeXXX methods. The ListModel can then trigger an
appropriate event for anybody who is interested.

#2 is not so easy for ListModel as a Book can be changed without going
through the ListModel itself. e.g.

x := ListModel with: aCollectionOfBooks.
(aCollectionOfBooks at: 1) beAvailable

Because of this ListModel provides a method that can be called to tell it
that a Book's state has been changed and the ListModel should trigger an
appropriate event.

x := ListModel with: aCollectionOfBooks.
(aCollectionOfBooks at: 1) beAvailable.
x updateAtIndex: 1

Changes to the package -

Book - as before

Library

addBook: aBook
    books add: aBook

Removed the #libraryStatusEvent trigger

checkOut: aString to: aPerson
    | book |
    book := self getBook: aString.
    book onLoanTo: aPerson.
    books updateAtIndex: (books indexOf: book)

See the comments above, We are performing the same operation on the Book as
before, setting it's loan state, but we now also inform the ListModel that
an item in it's list has changed internally. We don't care what it does with
that information or whether it needs to know - we just tell it that it has
occured.

return: aString
    | book |
    book := self getBook: aString.
    book beAvailable.
    books updateAtIndex: (books indexOf: book)

As above

LibraryShell is now left with just these three instance methods

createComponents
    super createComponents.
    booksPresenter := self add: ListPresenter new name: 'books'.
    availableBookPresenter := self add: ListPresenter new name:
'availableBooks'

Just as before

model: aLibrary
    super model: aLibrary.

    booksPresenter model: aLibrary books.
    self libraryChanged.

    booksPresenter model
        when: #item:addedAtIndex: send: #libraryChanged to: self;
        when: #itemUpdatedAtIndex: send: #libraryChanged to: self

I've moved the code that does the initial update of the
availableBooksPresenter's list from the #onViewOpened method to here. This
is the first place it can be done (we need to know the booksPresenter model
to be able to get its list) so we might as well do it here.

The other two lines connect this Presenter to events triggered from the
ListModel. When either an item is updated or an item is changed we want to
ensure that the availableBooksPresenter is updated as well.

Note that other interested parties may already be monitoring the ListModel's
events, the booksPresenter's associated ListView for one. Adding event
receptors doesn't affect them, we are now notified of the event in _addition
to anyone else.

libraryChanged
    availableBooksPresenter list: self model availableBooks

The previous #onViewOpened method, renamed for clarity

And that is about it. When the Library adds or changes an item in the list
it, either automatically (add/remove) or by request (update) causes the
ListModel to trigger an event. The LibraryShell receives notification of
these events and updates the availableBooksPresenter.

The final step will add a couple of simple TextViews to the Library, showing
the last book borrowed and the last book returned, to illustrate the use of
#triggersEvent

Ian

Out of interest - is anone reading these?


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Stefan Elisa Kapusniak
In reply to this post by plewis66
In comp.lang.smalltalk.dolphin, [hidden email] wrote:

>The model is raising events, and createSchematicWiring is basically
>sort of mutating those into other events with when:send:to:, eg:
>
>self model
>        when: #libraryStatusChanged
>        send: #onLibraryStatusChanged
>        to: self


   Not _exactly_.

   Bear in mind that although #createSchematicWiring sets
   up the routing tables, it leaves the _actual execution_
   of that routing (at the point you use #trigger:) to the
   object to which you send #when:send:to.

   An interesting method to look at is Object>>#when:perform:
   which #when:send:to uses.  If you look at the two arguments
   of that method, you'll notice that they're called
   'anEventSymbol' and 'aMessageSend' respectively.

   This means that yes, #libraryStatusChanged in the
   example above is an 'event', but #onLibraryStatusChanged
   is instead a completely normal message selector which
   if you trace your way through Object>>#notify: all the
   way down into Message>>#value: you'll see eventually gets
   #perform-ed.

   Therefore in the example above Ian's commited himself
   to implementing a method called 'onLibraryStatusChanged'
   in 'LibraryShell'.

   If instead Ian had written:

   self model
        when: #libraryStatusChanged
        send: #onPigsFly
        to: self


   ...he'd have to implement an 'onPigsFly' method
   instead.

   There's also nothing to stop you from specifying a
   message to a method that already exists if that
   method does what you want.


-- Kapusniak, Stefan e


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ian Bartholomew-3
In reply to this post by plewis66
Phil,

> The model is raising events, and createSchematicWiring is basically
> sort of mutating those into other events with when:send:to:, eg:
>
> self model
>         when: #libraryStatusChanged
>         send: #onLibraryStatusChanged
>         to: self
>
> So, the Library raises #libraryStatusChanged, and this is caught
> somewhere up the chain by cSW,

Nope. cSW is an _initialisation_ method. It is only called once, when the
LibraryShell is created. What it does is add an item to a collection held in
an instVar called 'events' maintained by the receiver - self model (a
Library instance) in this case. This events collection stores the trigger
(#libraryStatusChanged), the method selector to be used
(#onLibraryStatusChanged) and the object that the selector should be sent
to.

Aside - add a self halt to the above and when LibraryShell halts evaluate
the following, in the debugger source view, to inspect the 'events' instVar

self model events inspect

At some later stage the Library will evaluate one of the trigger statements.
This will be evaluated by Object>>trigger which, after some contortions,
will locate all the items in the events collection _associated with the
triggerer's instance_ that relate to the trigger that is being fired. It
will then go through all of the class/selector pairs associated with the
event and, for each pair, perform the selector on it's associated receiver
class - in the above it will send #onLibraryStatusChanged to self (which
will have been stored as the instance of LibraryShell in which the cSW
method above was evaluated).

So every object that has registered an interest in an event trigger will be
notified as part of the evaluation of the #trigger statement by the object
triggering the event.  _Any statements following the trigger statement will
not be evaluated until all the parties interested in the triggered event
have been notified_

There's a bit more to it, there are other classes involved in storing the
events, you can add arguments and it has to cope with objects that have
disappeared but the above is a rough idea.


>                                   which then sends #onLibraryStatusChanged
> to Self, where self is the LibraryShell. So what does the LibraryShell
> do with #onLibraryStatusChanged? Do we write some kind of message
> handler? Thisbit is really unclear to me...

Yes. A bit later on in the message is a definition for a
LibraryShell>>onLibraryStatusChanged method which just updates the list
belonging to the availableBooksPresenter.

NB: It is pretty standard in Dolphin to try to name events and the methods
evaluated when the event occurs in a reasonably consistent way. So, as an
example, a #modelChanged event will evaluate a method named
#onModelChanged. It's not compulsory, and there are a few reasons for
occasionally not following it, but it is a useful rule of thumb.

Ian


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ted Bracht-2
In reply to this post by Ian Bartholomew-3
Hi Ian,

>
> Out of interest - is anone reading these?
>
>


Maybe wrap it all up and put it on your web site as a MVP tutorial?

Ted


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Ian Bartholomew-3
In reply to this post by Ian Bartholomew-3
Stage 4 - Add a couple of TextPresenters and views that will display the
last book checked out and the last book returned.  The main purpose is to
show how non-list objects are connected.

Changes to the package

Book - as before

Library

Add two instance variables to the class 'lastCheckOut' and 'lastReturn'

checkOut: aString to: aPerson
    | book |
    book := self getBook: aString.
    book onLoanTo: aPerson.
    books updateAtIndex: (books indexOf: book).
    self lastCheckOut: book title

As before except for the last line - see below

initialize
    super initialize.
    books :=- ListModel with: (SortedCollection
        sortBlock: [:a :b | a title <= b title]).
    lastCheckOut := String new.
    lastReturn := String new

Just adds the initial state for the two lastXXX values. NB Use 'String new'
and not ''

lastCheckOut
    ^lastCheckOut

lastCheckOut: aString
    lastCheckOut := aString.
    self trigger: #lastCheckOutChanged

lastReturn
    ^lastReturn

lastReturn: aString
    lastreturn := aString.
    self trigger: #lastReturnChanged

The four accessors for the two new instVars. The setters trigger events, in
this case the instVar name followed by 'Changed'. This is a pretty standard
naming convention but you can use anything you like. As before, the methods
don't care if nobody responds to the triggered event, their job is just to
indicate a change has occurred.

return: aString
    | book |
    book := self getBook: aString.
    book beAvailable.
    books updateAtIndex: (books indexOf: book).
    self lastReturn: book title

Added a last line to set the lastReturn value

LibraryShell - two new instVars named #lastCheckOutPresenter and
#lastReturnPresenter.  Edit the defaultView and add two TextPresenter.Static
text views and name them 'lastCheckOut' and 'lastReturn'

createComponents
    super createComponents.
    booksPresenter := self add: ListPresenter new name: 'books'.
    availableBooksPresenter := self add: ListPresenter new name:
'availableBooks'.
    lastCheckOutPresenter := self add: TextPresenter new name:
'lastCheckOut'.
    lastReturnPresenter := self add: TextPresenter new name: 'lastReturn'.

Just added the creation of the two TextPresenters to the end

onBooksUpdated
    availableBooksPresenter list: self model availableBooks

No change

model: aLibrary
    super model: aLibrary.
    booksPresenter model: aLibrary books.
    self onBooksUpdated.

    booksPresenter model
        when: #item:addedAtIndex:
            send: #onBooksUpdated
            to: self;
       when: #itemUpdatedAtIndex:
            send: #onBooksUpdated
            to: self.

    lastCheckOutPresenter
        model: ((self model aspectValue: #lastCheckOut)
                aspectTriggers: #lastCheckOutChanged).

    lastReturnPresenter
        model: ((self model aspectValue: #lastReturn)
            aspectTriggers: #lastReturnChanged).

Added the last two lines. To break them down bit by bit -

    self model aspectValue: #lastCheckOut

This answers an instance of the class ValueAspectAdaptor. It has one main
purpose in life - to provide an adaptor between the receiver - self model, a
Library instance - and something that is expecting to be able to access this
object using the #value/#value: accessors.  To explain that further.

In the Red corner (figuratively speaking) we have a Library object that, for
it's lastCheckOut aspect, expects to be sent the #lastCheckOut: message to
set it's value and the #lastCheckOut message to get it's value.

In the Blue corner (the lastCheckOutPresenter whose model we are trying to
set) is an object that expects to be able to set it's associated model
object by sending it the #value: selector and retrieve the current setting
by sending the #value message.  It needs to have this fixed interface
because it wants to be usable, without any extra wiring, by instances of any
class that needs it.

These obviously won't fit together so what we need is another object that
can sit between the two and convert sends to #value: from the right hand
side into calls to #lastCheckOut: to the left hand side and calls to: #value
into calls to #lastCheckOut.  That's exactly what a ValueAspectAdaptor does.
Have a browse round the class and you can see where it receives the name of
the aspect (the #lastCheckOut argument in the line above) and constructs a
put and get selector from it.

What about the other direction I hear you cry. How does an instanceVariable
indicate that it has been changed and what the new value is.

The answer to the first part is

aspectTriggers: #lastCheckOutChanged

This just states that when the object whose value we want to adapt changes
it will trigger a #lastCheckOutChanged event. The adaptor registers for this
event with the receivers class and when the instVar does change the Adaptor
is notified. All the Adaptor then does is (see the ValueModel>>notifyChanged
method) trigger an event of it's own - #valueChanged which will itself have
been the target of registrations by interested parties.

The answer to the second part of the question is that, in this case, it
doesn't. It is up to any interested parties to wait for a notification that
the adaptee has changed and then come and fetch the value (by sending #value
to the adaptor which converts it into #lastCheckOut to the adaptee of
course) for itself. It _is_ possible for the triggered event to pass the new
state of the adaptee with the trigger but we don't use that here.

To summarise - A ValueAspectAdaptor
Converts message sends to #value: sent to it into the appropriate message to
set the adaptee (and then triggers a #valueChanged event).
Converts message sends to #value into the appropriate getter message for the
adaptee
Optionally, is alert for a specified event being triggered from the adaptee
to say that it's value has changed and then triggering a #valueChanged event
itself.

So, finally, what does the following actually do

    lastCheckOutPresenter
        model: ((self model aspectValue: #lastCheckOut)
                aspectTriggers: #lastCheckOutChanged).

It sets the model for lastCheckOutPresenter to a ValueAspectAdaptor which is
wrapping around the Library instance held in the LibraryShells's model. The
TextPresenter is expecting to converse with it's model using #value/#value:
and be informed when the model changes by way of the #valueChanged event.
The Adaptor provides this interface towards the TextPresnter but knows that
to actually converse with the Library it has to convert those interface
words into #lastCheckOut:/#lastCheckOut and #lastCheckOutChanged.

That's enough for now. Questions will, of course, be welcome but will have
to wait until I've been shopping and wrapping presents - which is what I
_really_ should have been doing today instead of pontificating so much.

Requests to shut the hell up will also be considered very carefully <g>

Ian


Reply | Threaded
Open this post in threaded view
|

The Event System Explained (was: Re: Specific MVP troubles (Long))

Peter van Rooijen
In reply to this post by plewis66
> I have one question so far, and this is a bit I have never managed to
> get an insight into.
>
> The model is raising events, and createSchematicWiring is basically
> sort of mutating those into other events with when:send:to:, eg:
>
> self model
>         when: #libraryStatusChanged
>         send: #onLibraryStatusChanged
>         to: self
>
> So, the Library raises #libraryStatusChanged, and this is caught
> somewhere up the chain by cSW, which then sends #onLibraryStatusChanged
> to Self, where self is the LibraryShell. So what does the LibraryShell
> do with #onLibraryStatusChanged? Do we write some kind of message
> handler? Thisbit is really unclear to me...

Perhaps I can help by explaining some things that often aren't being
explained when events are introduced:

1. The event system (i.e. the mechanism that makes event observation work)
is completely implemented with Smalltalk code. So, there is no magic, and
you could look at all the code that makes it work if you want to. A
corrollary of this is that the event system is completely synchronous, i.e.,
events are being handled (i.e., event handlers - methods that are called
when an event is triggered - are being executed) in the same thread of
execution as the code that that triggered the event.

2. #createSchematicWiring is a name that would mean nothing to me if I
didn't already know the essential constructs of programming with events.
Perhaps a more intention revealing name would be #setUpModelEventHandlers,
but I'm quite open to the possibility that that wouldn't actually be correct
(perhaps, because it is too specific), not having done any Dolphin GUI
coding.

3.
a) Events are most intuitively seen as objects that represent the fact that
a certain type of thing (for lack of a better word) has happened (at a
certain point in time). An EventType would then represent the type of
'thing' that has happened. And an Event could be seen as a sort of instance
of an EventType.

b) But it never works that way. It could work that way, but a more
lightweight (no need for Event and EventType classes) system does the job as
well (in Smalltalk). The idea is exactly the same, though.

c) How it is done is that <event type>s are represented by Symbols
(representing the 'name' attribute an EventType object might have), and
<event>s aren't represented by regular objects at all. Instead, they only
'live' on the execution stack (starting at #triggerEventOfType:
anEventTypeSymbol - or some such, such as #trigger:, signalEvent:), while
their event handlers are being executed.

d) When all the event handlers have completed execution, the execution stack
frame representing the execution of #triggerEventOfType: anEventTypeSymbol
is dropped from the execution stack, and there is no trace left in the
system of the <event>. Only the effects of the invoked event handler code
remind us that the event has occurred (has been triggered).

e) So, a way of looking at this might be to think that <event>s are
anonymous sub-objects.

4.
a) Question: Why would you want to program with events? Answer: There are
situations where you are aware that something might need to be done at a
specific point in code you are writing, but you can't know in advance who
(which object(s)) is/are going to do it (or even if any object is going to
do anything).

b) The only option available to you is (coding by intention) to simply state
that some type of 'thing' has occurred. You can't do anything else. You'll
have to rely on other code (than you are writing at the moment), to make
sure all the code that must be executed is in fact executed.

c) So, all you can do is write self trigger: #myThingHasHappened. And it has
to be the method #trigger:, which is inherited, that has to make sure
everything that needs to be done, is done.

5.
a) Question: So how does #trigger: do that? Answer: Every object that
understands #trigger: has access to its own list of messages it needs to
send, for each <event type>. Since <event type>s are represented by Symbols,
this can be done easily by having an instance variable called (say)
'eventHandlers', in which a Dictionary is kept with Symbol keys (for the
<event type>s), and values which are (ordered) collections of <evaluable
object>s, often <message>s (say instances of DirectedMessage, which know at
least the receiver and the selector - a selector is a Symbol which is also
used as the name of a method).

b) There is often a class (like Model) in your image, that has just such an
instance variable, and its implementation of #trigger: looks up the
collection of <message>s associated with the Symbol key that is passed as
the argument of #trigger:, and sends #evaluate, or some such, to every
element in the collection. When an instance of DirectedMessage receives
#evaluate, it sends its selector to its receiver.

c) This sending of the selector to the receiver is done using the #perform:
mechanism, which is built into all Smalltalks. It is easiest explained with
examples:
- '3 class' yields the same result as '3 perform: #class'
- '3 + 4' yields the same result as '3 perform: #+ with: 4'
- '3 between: 2 and: 4' yields the same result as '3 perform: #between:and:
withArguments: #(2 4)'
As you might expect, #perform: is executed by the virtual machine directly.
All objects understand it.

d) Other objects can usually also trigger events, but their list of messages
to send is not stored in one of their instance variables, but in a (global)
IdentityDictionary, kept somewhere in the system (e.g., in a class variable
of Object). Of course, this takes extra indirection to find the correct list
of things to do. So, this is slower, but saves space, because objects that
don't trigger any events, don't require any storage for their list(s) of
things to do.

6.
a) Question: But how does the triggering object know what messages need to
be sent? Answer: It is informed of messages to be sent in the time period
between its creation (when its lists of messages to send are empty), and the
time (each time) it executes #trigger:. That time period may be significant;
it could be a microsecond, but it could also be ten days.

b) Question: So how is the triggering object informed? Answer: Any object
(can be many, and it can do it to itself) can send it messages of the form
#when: eventTypeSymbol send: selector to: receiver. By doing that, they ask
the (observed) object to make sure the specified <message> is sent when the
specified <event type> is triggered. Adding a proper DirectedMessage object
to the proper list of things to do is the typical reaction. In jargon, the
proper event handler is registered.

I guess this is about all there is to it. To summarize:
- All objects can trigger events, by sending self #trigger: eventTypeSymbol.
- Objects that trigger events, allow other objects to tell them to evaluate
an evaluable object if and when they trigger an event, specifying the type
of event.
- If an object actually triggers an event, it first evaluates all the
evaluable objects it has been asked to evaluate, then continues on its merry
way.

If anything is still unclear, let me know.

Final point:
The event system is different from the exception system, but also similar in
some ways. Exception types and Exception instances are typically first class
objects. With exceptions the issue is not that you don't know what should be
done when a particular type of situation occurs, but you don't know *if* it
will occur (and/or at what specific point in the code). Exception handling
can easily be combined with event handling by triggering a event in the
exception handler code, and in that way you can also deal with uncertainty
regarding who is going to need to do something. This final note just so you
won't confuse the two.

Hope this helps.

Regards,

Peter van Rooijen


Reply | Threaded
Open this post in threaded view
|

...by any other name (was: The Event System Explained)

Stefan Elisa Kapusniak
In comp.lang.smalltalk.dolphin,
"Peter van Rooijen" <[hidden email]> wrote:

>2. #createSchematicWiring is a name that would mean nothing to me if I
>didn't already know the essential constructs of programming with events.

    A quite horrible name.  I agree.

    I seem to recall OA called it that, back in the mists of
    time when they were part of Intuitive, in order to match
    a once-planned GUI tool for wiring up events, that has
    never actually existed.

    Deprecating it, and bringing in a better named
    replacement, would certainly get my vote.

    But then again I don't personally have reams and
    reams of GUI code I'd need to update...


>Perhaps a more intention revealing name would be #setUpModelEventHandlers,
>but I'm quite open to the possibility that that wouldn't actually be correct
>(perhaps, because it is too specific), not having done any Dolphin GUI
>coding.

    Yes, a little too specific I think.  One also tends
    to set up handlers for events triggered on either
    the Presenter or the sub-Presenters in #cSW, not
    just models.


-- Kapusniak, Stefan e


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Phil Lewis-2
In reply to this post by Ian Bartholomew-3
<HUGE SNIP>

> Ian:
>
> Out of interest - is anone reading these?

Oh, yes, I'm readsing them OK.

Earlier, I just overlooked the method for OnLibraryStatusChanged. I was
reading in a hurry.

Now I am at home, I have hard copy, and I going to read them toroughly.

It would be really valuable to newcomers to have something like this
availabe easily....

Thanks for you efforts Ian. I'm pretty sure I'm beginning to see it now,
but its things like the explanation of the event mechanism that are vital.

One of the problems with tutorials is that they assuse that the reader is a
new
programmer, and that the the tricky bits can be explained as magic. I'm not
comfortable
with this. I want to know eactly what is going on.

However your exaplanations look clear, and I think penny isDropping

Thanks loads

Phil


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

pablo digonzelli
In reply to this post by Ian Bartholomew-3
YES I DO !!!!
IT'S A BRILLIANT EXPLANATION OF MVP.
THANKS
"Ian Bartholomew" <[hidden email]> wrote in message
news:91t11p$o5m$[hidden email]...
> Stage 3 - Remove the #libraryStatusEvent trigger from Library and replace
> it's function with events triggered from the ListModel.
>
> A bit of explanation first. The ListModel that Library holds, containing
the
> collection of Books, can have two main operations performed on it.
>
> #1 The collection of books is changed - a book is either added or a book
is
> removed from the ListModel's collection
>
> #2 A change is made to one of the Books contained within the ListModel - a
> book is loaned and it's internal state changes to reflect the borrowing.
>
> #1 is easy for the ListModel to cope with. The only way to change the
> ListModel's collection (without changing the complete list) is to go
through

> it's #addXXX or #removeXXX methods. The ListModel can then trigger an
> appropriate event for anybody who is interested.
>
> #2 is not so easy for ListModel as a Book can be changed without going
> through the ListModel itself. e.g.
>
> x := ListModel with: aCollectionOfBooks.
> (aCollectionOfBooks at: 1) beAvailable
>
> Because of this ListModel provides a method that can be called to tell it
> that a Book's state has been changed and the ListModel should trigger an
> appropriate event.
>
> x := ListModel with: aCollectionOfBooks.
> (aCollectionOfBooks at: 1) beAvailable.
> x updateAtIndex: 1
>
> Changes to the package -
>
> Book - as before
>
> Library
>
> addBook: aBook
>     books add: aBook
>
> Removed the #libraryStatusEvent trigger
>
> checkOut: aString to: aPerson
>     | book |
>     book := self getBook: aString.
>     book onLoanTo: aPerson.
>     books updateAtIndex: (books indexOf: book)
>
> See the comments above, We are performing the same operation on the Book
as
> before, setting it's loan state, but we now also inform the ListModel that
> an item in it's list has changed internally. We don't care what it does
with

> that information or whether it needs to know - we just tell it that it has
> occured.
>
> return: aString
>     | book |
>     book := self getBook: aString.
>     book beAvailable.
>     books updateAtIndex: (books indexOf: book)
>
> As above
>
> LibraryShell is now left with just these three instance methods
>
> createComponents
>     super createComponents.
>     booksPresenter := self add: ListPresenter new name: 'books'.
>     availableBookPresenter := self add: ListPresenter new name:
> 'availableBooks'
>
> Just as before
>
> model: aLibrary
>     super model: aLibrary.
>
>     booksPresenter model: aLibrary books.
>     self libraryChanged.
>
>     booksPresenter model
>         when: #item:addedAtIndex: send: #libraryChanged to: self;
>         when: #itemUpdatedAtIndex: send: #libraryChanged to: self
>
> I've moved the code that does the initial update of the
> availableBooksPresenter's list from the #onViewOpened method to here. This
> is the first place it can be done (we need to know the booksPresenter
model
> to be able to get its list) so we might as well do it here.
>
> The other two lines connect this Presenter to events triggered from the
> ListModel. When either an item is updated or an item is changed we want to
> ensure that the availableBooksPresenter is updated as well.
>
> Note that other interested parties may already be monitoring the
ListModel's
> events, the booksPresenter's associated ListView for one. Adding event
> receptors doesn't affect them, we are now notified of the event in
_addition

> to anyone else.
>
> libraryChanged
>     availableBooksPresenter list: self model availableBooks
>
> The previous #onViewOpened method, renamed for clarity
>
> And that is about it. When the Library adds or changes an item in the list
> it, either automatically (add/remove) or by request (update) causes the
> ListModel to trigger an event. The LibraryShell receives notification of
> these events and updates the availableBooksPresenter.
>
> The final step will add a couple of simple TextViews to the Library,
showing
> the last book borrowed and the last book returned, to illustrate the use
of

> #triggersEvent
>
> Ian
>
> Out of interest - is anone reading these?
>
>
>
>
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Richard A. Harmon
In reply to this post by Ian Bartholomew-3
On Thu, 21 Dec 2000 13:34:44 -0000, "Ian Bartholomew"
<[hidden email]> wrote:

>Stage 3 - Remove the #libraryStatusEvent trigger from Library and replace
>it's function with events triggered from the ListModel.
[snip]
>Out of interest - is anone reading these?

With great interest.  I'll have to go through the motions of actually
adding the code and running it to get the information to stick in my
brain.

Thanks.


--
Richard A. Harmon          "The only good zombie is a dead zombie"
[hidden email]           E. G. McCarthy


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

John Clonts-2
In reply to this post by Ian Bartholomew-3
Ian Bartholomew wrote:
>
> Stage 3 - Remove the #libraryStatusEvent trigger from Library and

[snip]

>
> Ian
>
> Out of interest - is anone reading these?


Certainly!  I have found it helpful!  Thank you and Merry Christmas!

Cheers,
John


Reply | Threaded
Open this post in threaded view
|

Re: Specific MVP troubles (Long)

Louis Sumberg
In reply to this post by Ian Bartholomew-3
> Out of interest - is anone reading these?

Bravo!
Bravo!!
Bravo!!!

Ian, Thanks so much for putting the time and effort into this -- a great
explanation of MVP and application building.  I'm reading it alright, though
I might be reading (and digesting) a lot slower than you were writing *s*.

-- Louis


123