Skip to content

Commit

Permalink
Merge pull request #63 from SquareBracketAssociates/kdh-review-chapte…
Browse files Browse the repository at this point in the history
…r-18

Improvements to chapter 18 (A Simple Contact Book)
  • Loading branch information
Ducasse authored Apr 16, 2024
2 parents ff8a422 + 06283a6 commit de71382
Showing 1 changed file with 69 additions and 63 deletions.
132 changes: 69 additions & 63 deletions Chapters/ContactBook2/ContactBook.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ The model for the domain of our example is composed of two classes: Contact and
The class modeling a contact is defined as follows.

```
Object << #EgContact
Object << #Contact
slots: {#name . #phone};
package: 'EgContactBook'
package: 'ContactBook'
```


It just defines a `printOn:` method and a couple of accessors (not shown in the text).

```
EgContact >> printOn: aStream
Contact >> printOn: aStream
super printOn: aStream.
aStream nextPut: $(.
Expand All @@ -42,13 +42,14 @@ EgContact >> printOn: aStream
```

```
EgContact >> hasMatchingText: aString
Contact >> hasMatchingText: aString
^ name includesSubstring: aString caseSensitive: false
```


```
EgContact class >> name: aNameString phone: aPhoneString
Contact class >> name: aNameString phone: aPhoneString
^ self new
name: aNameString;
Expand All @@ -59,17 +60,16 @@ EgContact class >> name: aNameString phone: aPhoneString

#### ContactBook

Now we define the class modeling the contact book.
As for the contact class, it is simple and quite straightforward.
Now we define the class modeling the contact book. As for the contact class, it is simple and quite straightforward.

```
Object << #EgContactBook
Object << #ContactBook
slots: { #contacts };
package: 'EgContactBook'
package: 'ContactBook'
```

```
EgContactBook >> initialize
ContactBook >> initialize
super initialize.
contacts := OrderedCollection new
Expand All @@ -78,37 +78,42 @@ EgContactBook >> initialize
We add the possibility to add and remove a contact

```
EgContactBook >> addContact: aContact
ContactBook >> addContact: aContact
contacts add: aContact
```


```
EgContactBook >> removeContact: aContact
ContactBook >> removeContact: aContact
contacts remove: aContact
```


```
EgContactBook >> addContact: newContact after: contactAfter
ContactBook >> addContact: newContact after: contactAfter
contacts add: newContact after: contactAfter
```


We add a simple testing method in case one wants to write some tests \(which we urge you to do\).

```
EgContactBook >> includesContact: aContact
ContactBook >> includesContact: aContact
^ contacts includes: aContact
```


And now we add a method to create a contact and add it to the contact book.

```
EgContactBook >> add: contactName phone: phone
ContactBook >> add: contactName phone: phone
| contact |
contact := EgContact new name: contactName; phone: phone.
contact := Contact new name: contactName; phone: phone.
self addContact: contact.
^ contact
```
Expand All @@ -117,39 +122,35 @@ EgContactBook >> add: contactName phone: phone
Finally, some facilities to query the contact book.

```
EgContactBook >> findContactsWithText: aText
ContactBook >> findContactsWithText: aText
^ contacts select: [ :e | e hasMatchingText: aText ]
```


```
EgContactBook >> size
ContactBook >> size
^ contacts size
```


#### Pre-filling up the contact book
```
ContactBook >> contents
^ contacts
```

Since we want to have some contacts and we way to keep them without resorting
to a database or file we set some class instance variables.
#### Pre-filling up the contact book

We define two class instance variables: `family` and `coworkers` and define
some class method accessors as follows:

```
EgContactBook class >> family
^family ifNil: [
family := self new
add: 'John' phone: '342 345';
add: 'Bill' phone: '123 678';
add: 'Marry' phone: '789 567';
yourself]
```
Since we want to have some contacts and we want to keep them without resorting to a database or file, we set some class instance variables.

We define a class instance variable `coworkers` and define a class method accessor as follows:

```
EgContactBook class >> coworkers
ContactBook class >> coworkers
^coworkers ifNil: [
coworkers := self new
add: 'Stef' phone: '112 378';
Expand All @@ -163,10 +164,10 @@ We add one method to be able to reset them if necessary.
The `<script>` pragma tells the system browser to add a small button to execute `reset` method easily.

```
EgContactBook class >> reset
ContactBook class >> reset
<script>
coworkers := nil.
family := nil
coworkers := nil
```


Expand All @@ -179,43 +180,47 @@ The targeted GUI is shown in Figure *@firstFullUI@*.

![A rudimentary contact book application.](figures/firstFullUI.png width=60&label=firstFullUI)

We define the class `EgContactBookPresenter`.
We define the class `ContactBookPresenter`.
It holds a reference to a contact book and it is structured around a table.

```
SpPresenter << #EgContactBookPresenter
SpPresenter << #ContactBookPresenter
slots: { #table . #contactBook};
package: 'EgContactBook'
package: 'ContactBook'
```


We define an accessor for the contact book and the table.

```
EgContactBookPresenter >> contactBook
ContactBookPresenter >> contactBook
^ contactBook
```


```
EgContactBookPresenter >> table: anObject
ContactBookPresenter >> table: anObject
table := anObject
```


```
EgContactBookPresenter >> table
ContactBookPresenter >> table
^ table
```


#### Initializing the model


We specialize the method `setModelBeforeInitialization:` that is invoked by the framework to assign the `contactBook` instance variable to the object passed during the execution of the expression `(EgContactBookPresenter on: EgContactBook coworkers) open`.
We specialize the method `setModelBeforeInitialization:` that is invoked by the framework to assign the `contactBook` instance variable to the object passed during the execution of the expression `(ContactBookPresenter on: ContactBook coworkers) open`.

```
EgContactBookPresenter >> setModelBeforeInitialization: aContactBook
ContactBookPresenter >> setModelBeforeInitialization: aContactBook
super setModelBeforeInitialization: aContactBook.
contactBook := aContactBook
```
Expand All @@ -225,7 +230,7 @@ EgContactBookPresenter >> setModelBeforeInitialization: aContactBook


```
EgContactBookPresenter class >> defaultSpec
ContactBookPresenter >> defaultLayout
^ SpBoxLayout newVertical add: #table; yourself
```
Expand All @@ -234,12 +239,11 @@ EgContactBookPresenter class >> defaultSpec
#### Widget initialization


We initialize the table to display two columns for the name and the phone.
The respective accessor messages will be sent to the elements to fill up the columns.
Finally, the table content is set using the contact book contents.
We initialize the table to display two columns for the name and the phone. The respective accessor messages will be sent to the elements to fill up the columns. Finally, the table content is set using the contact book contents.

```
EgContactBookPresenter >> initializePresenters
ContactBookPresenter >> initializePresenters
table := self newTable.
table
addColumn: (StringTableColumn title: 'Name' evaluated: #name);
Expand All @@ -249,17 +253,18 @@ EgContactBookPresenter >> initializePresenters


Now we can start opening the UI by executing the following snippet
`(EgContactBookPresenter on: EgContactBook coworkers) open`
`(ContactBookPresenter on: ContactBook coworkers) open`

We define a class method to be able to easily re-execute the setup.
```
EgContactBookPresenter class >> coworkersExample
ContactBookPresenter class >> coworkersExample
<example>
^ (self on: EgContactBook coworkers) open
^ (self on: ContactBook coworkers) open
```


You should obtain the following GUI as shown in Figure *@first@*.
You should obtain the GUI as shown in Figure *@first@*.

![First version of the GUI without menus and toolbar.](figures/firstVersion.png width=60&label=first)

Expand All @@ -270,7 +275,7 @@ We now implement the method that will open a window to ask the user to create
a new contact for the contact book.

```
EgContactBookPresenter >> newContact
ContactBookPresenter >> newContact
| rawData split |
rawData := self
request: 'Enter new contact name and phone (split by comma)'
Expand All @@ -280,44 +285,45 @@ EgContactBookPresenter >> newContact
(split size = 2 and: [ split allSatisfy: [ :each | each isNotEmpty ]])
ifFalse: [ SpInvalidUserInput signal: 'Please enter contact name and phone (split by comma)' ].
^ EgContact new
^ Contact new
name: split first;
phone: split second;
yourself
```


To test it, we can get access to the presenter as follows
To test it, we can get access to the presenter with
```
(EgContactBookPresenter on: EgContactBook coworkers)
(ContactBookPresenter on: ContactBook coworkers)
open presenter inspect
```


and you can send the `newContact` message as shown in Figure *@inspector@*.
and you can send the `newContact` message to open the GUI shown in Figure *@inspector@*.

![Playing inside the inspector.](figures/inspector.png width=80&label=inspector)


#### Some extra methods


We will also define the methods `isContactSelected` and `selectedContact` to know if a contact is currently selected and to return it.
It will help us later to add contact just after the currently selected contact.
We will also define the methods `isContactSelected` and `selectedContact` to know if a contact is currently selected and to return it. It will help us later to add a contact just after the currently selected contact.

```
EgContactBookPresenter >> isContactSelected
ContactBookPresenter >> isContactSelected
^ self table selectedItems isNotEmpty
```


```
EgContactBookPresenter >> selectedContact
ContactBookPresenter >> selectedContact
^ table selection selectedItem
```


### Conclusion


We have a little contact book manager now that we can use to explain other topics.
Now we have a little contact book manager that we can use to explain other topics.

0 comments on commit de71382

Please sign in to comment.