Dynamic example for spec

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

Dynamic example for spec

Stephan Eggermont-3
While trying to understand spec, I’ve written an example of using dynamic specs.
  https://github.com/StephanEggermont/documentation/blob/master/SpecDynamicExample.pier
A list of parties, where instances of different subclasses can be added.
Comments & improvements are welcome.

Stephan













!!Parties, a dynamic example
In an address book we find addresses of both people and organizations.
The generalization of both is called a party ('Analysis Patterns', Martin Fowler).
In this example we'll make an address book where we can add both persons and companies.
They will have different attributes.
We show how to do add them with a dynamic user interface, and minimize duplication.

!!! The party model
Create the abstract superclass ==Party==
[[[
Object subclass: #DOParty
        instanceVariableNames: ''
        classVariableNames: ''
        category: 'Domain-Parties'
]]

A party responds to the ==fullName== message.

[[[
DOParty>fullName
        ^'Full name'
]]]

The subclasses are going to override this message.
Create a subclass for persons:

[[[
DOParty subclass: #DOPerson
        instanceVariableNames: 'firstName lastName'
        classVariableNames: ''
        category: 'Domain-Parties'
]]]

Create accessors for ==firstName== and ==lastName==. We don't want to need to handle ==nil==
as a special case, so return the empty string if the instVars are nil.

[[[
DOPerson>firstName
        ^firstName ifNil: [ '' ]

DOPerson>firstName: aString
        firstName := aString

DOPerson>lastName
        ^lastName ifNil: [ '' ]

DOPerson>lastName: aString
        lastName := aString
]]]

We can now override the ==fullName==.

[[[
DOPerson>fullName
        ^self firstName, ' ', self lastName
]]]

Create a subclass for companies

[[[
DOParty subclass: #DOCompany
        instanceVariableNames: 'companyName'
        classVariableNames: ''
        category: 'Domain-Parties'
]]]

And its accessors and the overridden method

[[[
DOCompany>companyName
        ^ companyName ifNil: ['']

DOCompany>companyName: anObject
        companyName := anObject

DOCompany>fullName
        ^ self companyName
]]]

In this example we will simply keep all parties in the image.
We create a class to hold parties

[[[
Object subclass: #DOPartiesModel
        instanceVariableNames: 'parties'
        classVariableNames: ''
        category: 'Domain-Parties'
]]]

And lazily initialize  with a collection

[[[
DOPartiesModel>parties
        ^parties ifNil: [ parties := OrderedCollection new ]

DOPartiesModel>parties: aCollection
        parties := aCollection
]]]

On the class side we add an instanceVariable ==default== as the singleton
and two methods to access and reset it.

[[[
DOPartiesModel class
        instanceVariableNames: 'default'

DOPartiesModel>>default
        ^default ifNil: [ default := self new ]

DOPartiesModel>>reset
        default := nil
]]]

!!! A dynamic editor
To edit a single party, we create a subclass of ==DynamicComposableModel==

[[[
DynamicComposableModel subclass: #DOPartyEditor
        instanceVariableNames: 'partyClass'
        classVariableNames: ''
        category: 'Domain-Parties'
]]]
When we instantiate this editor, we'll tell it on what kind of party it operates and store that in the
==partyClass==. On the instance side we add accessors and on the class side we use that in a constructor.
A ==DynamicComposableModel== has a complex initialization proces, so we use a separate ==basicNew==
and ==initialize== to set the ==partyClass== early enough.
[[[
DOPartyEditor>partyClass: aPartyClass
        partyClass := aPartyClass
       
DOPartyEditor>partyClass
        ^partyClass

DOPartyEditor>>on: aPartyClass
        ^self basicNew
                partyClass: aPartyClass;
                initialize;
                yourself
]]]
This class has no ==defaultSpec==, as it is only created with a dynamic spec.
The editor is going to be a separate window. The window title is dependent of the class.
Party and subclasses define it at the class side.

[[[
DOParty>>title
        "override in subclasses"
        ^'Party'

DOCompany>>title
        ^'Company'

DOPerson>>title
        ^'Person'

DOPartyEditor>title
        ^ partyClass title
]]]

The editor needs to know what fields need to be created. On the class side of the Party subclasses
we return an array of symbols representing the fields. This will do for the example, for a real
application with differnt kinds of fields Magritte descriptions are much more suitable.

[[[
DOParty>>fields
        ^self subclassResponsibility

DOCompany>>fields
        ^#(#companyName)

DOPerson>>fields
        ^#(#firstName #lastName)
]]]

Now we can initialize the widgets. ==instantiateModels== expects pairs of field names and field types
and adds them to the widgets dictionary.
They are then laid out in one column, given some default values and added in focus order.

[[[
DOPartyEditor>initializeWidgets
        |models|
        models := OrderedCollection new.
        partyClass fields do: [ :field | models add: field; add: #TextInputFieldModel  ].
        self instantiateModels: models.

        layout := SpecLayout composed
                newColumn: [ :col |
                        partyClass fields do: [ :field |
                                col add: field height: self class inputTextHeight]];
                yourself .

         self widgets keysAndValuesDo:  [ :key :value |
                value autoAccept: true;
                        entryCompletion:nil;
                        ghostText: key.
                self focusOrder add: value] .

]]]

The last thing needed is to calculate how large the window should be. It is going to be used
as a dialog with ok and cancel that take up a height of about three input fields.

[[[
DOPartyEditor>initialExtent
        ^ 300@(self class inputTextHeight*(3+partyClass fields size))
]]]

Now we can test the editor with ==(DOPartyEditor on: DOCompany) openDialogWithSpec== and
==(DOPartyEditor on: DOPerson) openDialogWithSpec==

!!! The adres book

We can now make the address book with a search field and buttons to add persons and companies.
Add a class

[[[
ComposableModel subclass: #DOPartiesList
        instanceVariableNames: 'search addPerson addCompany list'
        classVariableNames: ''
        category: 'Domain-Parties'
]]]

As soon as something is typed in the search field, the list should show the list of parties
having a fullName containing the search term, ignoring case.

[[[
DOPartiesList>refreshItems
        |searchString|
        searchString := search text asLowercase.
        list
                items: (DOPartiesModel default parties select: [: each |
                        searchString isEmpty or: [each fullName asLowercase includesSubstring: searchString]]);
                displayBlock: [ :each |  each fullName].
]]]

We can now create the widgets and the default layout (class side)

[[[
DOPartiesList>initializeWidgets
        search := self newTextInput.
        search autoAccept: true;
                entryCompletion:nil;
                ghostText: 'Search'.
        addPerson := self newButton.
        addPerson label: '+Person'.
        addCompany := self newButton.
        addCompany label: '+Company'.
        list := self newList.

        self refreshItems.
        self focusOrder
                add: search;
                add: addPerson;
                add: addCompany;
                add: list.

DOPartiesList>>defaultSpec
        <spec: #default>

        ^SpecLayout composed
                newColumn: [ :col |
                                col newRow: [:row |
                                        row add: #search;
                                                add: #addPerson;
                                                add: #addCompany]
                                        height: ComposableModel toolbarHeight;  
                                        add: #list];
                yourself
]]]

]]]

When the user clicks on the ok button of the party editor, we need to create an instance of the right
subclass, read the field values out of the editor and assign them to the attributes of the new instance.
We do that using meta-programming (==perform:== and ==perform:with:==).
Then we add the instance to the model and need to refresh the list. Add a method setting the okAction
block of the editor.

[[[
DOPartyList>addPartyBlockIn: anEditor
        anEditor okAction: [ |party|
                party := anEditor model partyClass new.
                anEditor model partyClass fields do: [ :field |
                        party perform: (field asMutator) with: (anEditor model perform: field) text ].
                DOPartiesModel default parties add: party.
                self refreshItems ].
]]]

Now we can initialize the presenter

[[[
DOPartyList>initializePresenter
        search whenTextChanged: [ :class | self refreshItems  ].

        addPerson action: [  |edit|
                edit := (DOPartyEditor on:DOPerson) openDialogWithSpec.
                self addPartyBlockIn: edit].
        addCompany action: [  |edit|
                edit := (DOPartyEditor on: DOCompany) openDialogWithSpec.
                self addPartyBlockIn: edit ]
]]]
Don't forget the accessors
[[[
DOPartyList>addCompany
        ^addCompany

DOPartyList>addPerson
        ^addPerson

DOPartyList>items: aCollection
        list items: aCollection

DOPartyList>list
        ^list

DOPartyList>search
        ^search
]]]
protocol
[[[
DOPartyList>resetSelection
        list resetSelection

DOPartyList>title
        ^ 'Parties'
]]]
and protocol-events
[[[
DOPartyList>whenAddCompanyClicked: aBlock
        addCompany whenActionPerformedDo: aBlock

DOPartyList>whenAddPersonClicked: aBlock
        addPerson whenActionPerformedDo: aBlock

DOPartyList>whenSelectedItemChanged: aBlock
        list whenSelectedItemChanged: aBlock

]]]
This can be tested with ==DOPartiesList new openWithSpec==


PastedGraphic-6.png (18K) Download Attachment
PastedGraphic-4.png (14K) Download Attachment
PastedGraphic-5.png (15K) Download Attachment
Domain-Parties.st (12K) Download Attachment
kmo
Reply | Threaded
Open this post in threaded view
|

Re: Dynamic example for spec

kmo
No one has replied to this post, so I thought I would add my two cents of wit.

Many thanks for the example - any addition to the Spec documentation is really useful and I've certainly learned some stuff from your code. But I think if you are aiming to help people learn Spec, then you should add as many comments to the code as you can. Also, allowing the user to edit existing list entries would make the example much more complete.

Beyond that, i have to say that the example just confirms my decision not to use Spec at all - at least in its present state.

Though this is supposed to be a dynamic Spec example, it's really not that dynamic, is it?. You are creating separate instances of a data entry screen here and each separate instance has its own layout - but a truly dynamic Spec example would be one which dynamically changed an existing instance. But this cannot be done currently in Spec as there is no way to change the size of the Spec window once it is created (Bug 13059).

It is this limitation I believe that has made you take the separate instances route using a class method. The code for this just seems unnatural to me:

DOPartyEditor>>on: aPartyClass
        ^self basicNew
                partyClass: aPartyClass;
                initialize;
                yourself

A more natural approach would be to construct the instance as normal with DOPartyEditor>>new and then set the party class. Changing the party class could then trigger the layout change, allowing the  instance to rebuild itself. But this is impossible because of the window resizing issue. I think this is a clear case of Spec's limitations having a detrimental effect on code quality.

One thing I had not considered before seeing your code was the consequence of Spec's decision to call its widgets models. This causes (at least in my mind) a confusion between the ideas of a model as a problem domain object and a Spec widget. So, in your example, you have a PartiesModel class. So I have to look to see if this inherits from Object or Composable Model because we now have two different ideas of model in the application.

So in your code you have:

DOPartyEditor>initializeWidgets
        |models|
        models := OrderedCollection new.

when I read this I was thinking about models as in MVC, not as in ComposableModel. To my mind these lines would be better written as:

DOPartyEditor>initializeWidgets
        |inputWidgets|
        inputWidgets := OrderedCollection new.


Finally, we have the finished product - which I would not show to anyone as an example of a Pharo user interface. You know as well as I do that as soon as the user types in anything they will immediately hit bug 13013 - the font size is far too small and cannot be changed. Can you imagine in what a Java or C# developer would think of this: Good God these guys are so lame they can't implement the most basic component of a data entry interface - a text entry field. No wonder they can't get Pharo to open up two windows! And yet they claim they have a better way of programming. What are they smoking!.

Who knows what they would think if they knew that an even simpler user interface component - the label - was buggy as well. A LabelModel really only has two properties: label and emphasis and one of them doesn't work!

I really think some of this needs to be sorted out before Pharo 3 is released, otherwise the problems with Spec are going to give a very bad impression to any developers who take a look at Pharo hoping to find something better than Java or C#. A possible public relations disaster?



 









 
Reply | Threaded
Open this post in threaded view
|

Re: Dynamic example for spec

jfabry
(responses inline)

On Apr 27, 2014, at 10:17 AM, kmo <[hidden email]> wrote:

> No one has replied to this post, so I thought I would add my two cents of
> wit.

Sadly, I did not have time to study the example because I’m traveling. There are some items in this mail that however I would like to quickly respond to. (Hopefully I will have network along the way somewhere.)

> Beyond that, i have to say that the example just confirms my decision not to
> use Spec at all - at least in its present state.

From your comments below it is not 100% clear to me why you choose not to use Spec at all. Sure, there are some features that are missing. Are these showstoppers for you, and if so, why? Please list and explain.

> Though this is supposed to be a /dynamic /Spec example, it's really not that
> dynamic, is it?. You are creating separate instances of a data entry screen
> here and each separate instance has its own layout - but a truly dynamic
> Spec example would be one which dynamically/ changed an existing instance/.
> But this cannot be done currently in Spec as there is no way to change the
> size of the Spec window once it is created (Bug 13059).

I do not agree with your generalization. Still it is possible to add and remove widgets on the fly, as we present in section 5.1 of the Spec chapter in the Pharo For The Enterprise book,

[…]

> One thing I had not considered before seeing your code was the consequence
> of Spec's decision to call its widgets /models/. This causes (at least in my
> mind) a confusion between the ideas of a model as a problem domain object
> and a Spec widget.

As we say in the documentation in the book: a widget is something you see on the screen with all its associated state and behavior. But with Spec you don’t code the stuff you see on the screen, you just think about it’s state and behavior, i.e. the model *of the user interface*.

> So, in your example, you have a /PartiesModel/ class. So
> I have to look to see if this inherits from Object or Composable Model
> because we now have two different ideas of model in the application.
>
> So in your code you have:
>
> DOPartyEditor>initializeWidgets
>        |models|
>        models := OrderedCollection new.
>
> when I read this I was thinking about models as in MVC, not as in
> ComposableModel. To my mind these lines would be better written as:
>
> DOPartyEditor>initializeWidgets
>        |inputWidgets|
>        inputWidgets := OrderedCollection new.

Or instead call them uiModels.

> Finally, we have the finished product - which I would not show to anyone as
> an example of a Pharo user interface. You know as well as I do that as soon
> as the user types in anything they will immediately hit bug 13013 - the font
> size is far too small and cannot be changed. Can you imagine in what a Java
> or C# developer would think of this: /Good God these guys are so lame they
> can't implement the most basic component of a data entry interface - a text
> entry field. No wonder they can't get Pharo to open up two windows! And yet
> they claim they have a better way of programming. What are they smoking!./

There is only so much that a group of, essentially volunteer, developers can do. Comparing it to the multi-million dollar investment that Sun/Oracle/Microsoft/ … have performed is in getting their infrastructure right, inevitably will find issues. The question is: are they showstoppers? What can we do, given our situation?

> Who knows what they would think if they knew that an even simpler user
> interface component - the label - was buggy as well. A /LabelModel /really
> only has two properties: /label /and /emphasis /and one of them doesn't
> work!

Can you clarify the problem? Is there a bugreport? (I’m without network ATM, otherwise I’d check).

> I really think some of this needs to be sorted out before Pharo 3 is
> released, otherwise the problems with Spec are going to give a very bad
> impression to any developers who take a look at Pharo hoping to find
> something better than Java or C#. A possible public relations disaster?

I think you are exaggerating. Spec is already an improvement on the UI builders that precede it, and no PR disasters have happened because of these previous frameworks.

---> Save our in-boxes! http://emailcharter.org <---

Johan Fabry   -   http://pleiad.cl/~jfabry
PLEIAD lab  -  Computer Science Department (DCC)  -  University of Chile


Reply | Threaded
Open this post in threaded view
|

Re: Dynamic example for spec

Stephan Eggermont-3
In reply to this post by Stephan Eggermont-3
Hi kmo,

Thanks for your detailed response. You seem to have run into a number of
issues I haven’t seen yet while learning spec. I’ll take a look at resizing
windows and see how to fix the issue. I have noticed the font size issue
and assumed it is to be fixed real soon now. I’ll extend the example
to include editing of existing instances.

It was suggested that it would be good to have other examples,
so I decided to try building a more business-application related one.
I am used to dealing with domain models, and will revisit the names.

Stephan







kmo
Reply | Threaded
Open this post in threaded view
|

Re: Dynamic example for spec

kmo
It was suggested that it would be good to have other examples,
so I decided to try building a more business-application related one.


Stephan -

That's exactly what is needed. It's pet peeve of mine that so many of the Spec examples tend to be browsers. If Pharo wants to position itself as a general purpose application development tool then it has to demonstrate that it can create basic data entry commercial applications. Otherwise we might as well tell business developers to forget about Spec and Morphic and just use Seaside.  

Ken
Reply | Threaded
Open this post in threaded view
|

Re: Dynamic example for spec

kilon.alios
In reply to this post by jfabry
as far as me is concerned two GUI libraries are what I would call "public relations disaster" or "an example of worst design". C++ Window's MFC and Java Swing. At the very top I put Delphi's VLC (C# winforms are a child of it) the best GUI I have dealt with and QT coming second. 

Spec looks fairly good from the tutorials I have seen, the ability to work hand in hand with Morphic is a big plus. However its clearly an ongoing effort, its a new library that it needs its time to mature and as Johan said Spec lacks the manpower of MFC and Swing . We should not forget that few months ago Spec was not even documented. 

Nonetheless I think pharo people have been doing an amazing job so far with the resources they have in their hands. It scares me to imagine the potential if Pharo community was as big and well funded as Microsoft and Sun.  

In the end of the day Pharo needs all the help it can get. If people start giving up every time they saw that it lacked a feature found in other libraries of other programming languages then pharo would be dead in a few days at most. Of course the same applies for every other language however popular.

I completely agree that Spec is definitely an improvement and I plan to help the project with documenting it with video tutorials.  
Reply | Threaded
Open this post in threaded view
|

Re: Dynamic example for spec

stepharo
In reply to this post by kmo
Please push that :)

Stef

On 27/4/14 21:27, kmo wrote:

> /It was suggested that it would be good to have other examples,
> so I decided to try building a more business-application related one. /
>
> Stephan -
>
> That's exactly what is needed. It's pet peeve of mine that so many of the
> Spec examples tend to be browsers. If Pharo wants to position itself as a
> general purpose application development tool then it has to demonstrate that
> it can create basic data entry commercial applications. Otherwise we might
> as well tell business developers to forget about Spec and Morphic and just
> use Seaside.
>
> Ken
>
>
>
> --
> View this message in context: http://forum.world.st/Dynamic-example-for-spec-tp4756370p4756696.html
> Sent from the Pharo Smalltalk Users mailing list archive at Nabble.com.
>
>


Reply | Threaded
Open this post in threaded view
|

Re: Dynamic example for spec

Stephan Eggermont-3
In reply to this post by Stephan Eggermont-3
continued

A next step is the editing of existing instances.
In the DOPartiesList, we need to use a NewListModel instead of the ListModel,
as that understands doubleClick actions.

DOPartiesList>initializeWidgets
- list := self newList.
+ list := self instantiate: NewListModel.
 
To edit a party, we modify addPartyBlockIn: anEditor to create editParty:in:.
We set the data values and the title. In the okAction we don't have to add the party.

DOPartiesList>editParty: aParty in: anEditor
        aParty class fields do: [ :field |
                (anEditor model perform: field) text: (aParty perform: field) ].

        anEditor title: 'Edit ',aParty fullName.

        anEditor okAction: [
                anEditor model partyClass fields do: [ :field |
                        aParty perform: (field asMutator) with: (anEditor model perform: field) text ].
                self refreshItems ].

In initializePresenter, we can then add an edit action. The list currently needs
to know that it should handle doubleClicks, and then call a partyeditor

DOPartiesList>initializePresenter
+ list handlesDoubleClick: true.
+ list doubleClickAction: [ |party edit|
+ party := list selectedItem.
+ edit := (DOPartyEditor on:  party class) openDialogWithSpec.
+ self editParty: party in: edit]