Dynamic Web Development with Seaside

22.3The Counter Explained

CTCounter is a subclass of WAComponent:

WAComponent subclass: #CTCounter
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Comet-Examples'

It has an initialize method to register it as an application.

CTCounter class>>initialize
| application |
application := WAAdmin register: self asApplicationAt: 'comet/counter'.
application addLibrary: JQDeploymentLibrary; addLibrary: CTLibrary

There are two JavaScript libraries included here. The first is JQDeploymentLibrary which is the jQuery library that we saw in Chapter 21. The second library is the Comet JavaScript library CTLibrary. CTLibrary does not depend on functionality provided by the jQuery library, but we will use some of that functionality later to update the value of the counter.

Unlike the counter application we saw in previous chapters, the Comet counter requires global state. That is, the model is not part of the component which is local to a single session but global to all sessions so that it is shared among all users. To do this, we keep the model — an instance of CTCounterModel — on the class side of the CTCounter component. Furthermore we need a dedicated pusher object — an instance of CTPusher — that is responsible for managing the communication channel between the server and many clients.

CTCounter class
instanceVariableNames: 'model pusher'
CTCounter class>>model
^ model ifNil: [ model := CTCounterModel new ]
CTCounter class>>pusher
^ pusher ifNil: [ pusher := CTPusher new ]

Its worth re-emphasizing that the pusher requires global state so that all browsers connecting to the page share the same model. Thus we store the pusher and its model on the class-side.

The method renderContentOn: is unremarkable. First we render the current count in a heading we give the ID count. Then we have two anchors with JavaScript actions attached to the click event, that call the methods increase and decrease. Finally, we append a small script to the bottom of the component that connects the pusher we defined on the class side to this session and component.

CTCounter>>renderContentOn: html
html heading
id: 'count';
with: self class model count.
html anchor
onClick: (html jQuery ajax
callback: [ self class decrease ]);
with: '--'.
html space.
html anchor
onClick: (html jQuery ajax
callback: [ self class increase ]);
with: '++'.
html script: (html comet
pusher: self class pusher;
connect)

Where does all the magic happen now? Where are all the connected components updated? Obviously this must happen in the methods increase and decrease, which both call the method update.

CTCounter>>increase
self class model increase.
self update
CTCounter>>decrease
self class model decrease.
self update
CTCounter>>update
self class pusher javascript: [ :script |
script << (script jQuery: #count)
text: self class model count ]

Nothing surprising happens in increase and decrease, both methods just delegate the action to their model. The interesting thing happens in update right after the model has been changed. As you can see, we tell the pusher that we want to push a script to the client. We do this by sending a block to the method javascript: that gives us a script object. This script updates the element with the ID count to the current count of the element. The pusher ensures that the script is automatically sent to all connected components.

Copyright © 19 March 2024 Stéphane Ducasse, Lukas Renggli, C. David Shaffer, Rick Zaccone
This book is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 license.

This book is published using Seaside, Magritte and the Pier book publishing engine.