-
Notifications
You must be signed in to change notification settings - Fork 2
/
documentation.pier.tex
708 lines (495 loc) · 22.1 KB
/
documentation.pier.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
\documentclass[a4paper,10pt,twoside]{book}\usepackage{graphicx}\usepackage[normalem]{ulem}\newcommand{\ct}[1]}\usepackage{listings}\lstnewenvironment{code}[1]{% \lstset{% % frame=lines, frame=single, framerule=0pt, mathescape=false }}{}\lstnewenvironment{script}[2][defaultlabel]{%\renewcommand{\lstlistingname}{Script}% \lstset{ % frame=lines, frame=single, framerule=0pt, mathescape=false, name={Script}, caption={\emph{#2}}, label={scr:#1} }}{}\usepackage{fixltx2e}\usepackage[ colorlinks=true, linkcolor=black, urlcolor=black, citecolor=black]{hyperref}\begin{document}
\begin{todo}
I need to make it clear that presenters are on the Pharo side only and Proxies in Amber.
\end{todo}
\begin{todo}
explain what are state and actions
\end{todo}
\chapter{Introduction}
Have you ever wanted to develop advanced client side applications without having to deal with the asynchronous
nature of Javascript? Wouldn't you love to share your models between your client and your server? Then Tide
is made for you. Tide is a client-side web framework developed in Pharo and Amber. \href{https://github.com/tide-framework/tide}{Tide} offers seamless communications between \href{http://amber-lang.net}{Amber} and \href{http://pharo-project.org}{Pharo}.
An application developed in Tide is composed of two parts: one part in Pharo to define what we call a presenter model and one part in Amber for the client-side widgets. Tide ensures the communication between these two parts. Tide exchanges information using the \ct{JSON} format. The \ct{JSON} is built from Pharo objects and sent through the network to Amber. Having both data and actions sent to Amber makes Tide a very good communication protocol between the client and server.
But there is something fundamentally different in Tide: The \ct{JSON} contains data exposed from objects in Pharo, but it also contains callback information to perform actions from Amber to Pharo objects. Let us look at the implications raised. Generating JSON is trivial and used more and more as a exchange format, replacing XML. However, having operations (callbacks) describing how to interact with data is a key difference: It lets you factor out the logic of your application. You do not get just data and are forced to
recreate on the client-side some logic. You get fully described data, therefore it is easier to build generic and reusable client-side widgets manipulating data.
This documentation will teach how to install and use Tide through examples.
We will also learn its architecture and more advanced topics.
\chapter{ Installing Tide}\section{ Prerequisites}
Tide requires several libraries. It of course depends on Pharo and Amber. Amber
itself requires \ct{nodejs} and \ct{bower} to install its own dependencies. The
Pharo-side of Tide requires Zinc, which is part of the default image since
Pharo 2.0. Tide however has only been tested with Pharo 3.0.
\subsection{ NodeJs}
Go to \href{http://nodejs.org}{nodejs.org} and install \ct{node} (and \ct{npm}) for your
platform.
\subsection{ Bower}
Bower is a package manager for the web, and Amber uses Bower to manage
dependencies. The simplest way to use bower is to install it globally as
follows:
\begin{code}{}
$ npm install -g bower
\end{code}
\subsection{ Pharo}
Tide requires Pharo 3.0. The simplest way to install it is to evaluate the
following:
\begin{code}{}
$ curl get.pharo.org/30+vm | bash
\end{code}
To start the Pharo image, evaluate:
\begin{code}{}
$ ./pharo-ui Pharo.image
\end{code}
\subsubsection{ Preparing the Pharo image}
\begin{todo}
We can also do it from the command line and it would be good to show how.
\end{todo}
\begin{todo}
Would be good to have one release of tide
\end{todo}
to avoid development.
\begin{todo}
use the pharo cli with the config option to load tide.
\end{todo}
Once you get the Pharo window open, you have to install the Tide backend part.
This means bringing the Pharo code you cloned from GitHub?? into the Pharo image.
\begin{itemize}
\item Click on the background of the Pharo window
\item In the World menu that appears, click on \ct{Workspace}
\item In that window, evaluate: (you type the thing, select the text and then right
\end{itemize}
click and select \symbol{34}Do It\symbol{34} from the menu).
\begin{code}{}
Metacello new
configuration: 'Tide';
version: #development;
repository: 'http://www.smalltalkhub.com/mc/Pharo/MetaRepoForPharo30/main';
load.
\end{code}
SD: Why the following is not in the troubleshooting session and bower install is not executed before?
\begin{todo}
we should have a simpler way to install it.
\end{todo}
When this is finished, evaluate:
\begin{code}{}
TDDispatcher tideIndexPageUrl inspect
\end{code}
When first executed, you will get an error saying you must execute bower
install in a particular directory. Open a terminal, change to the right
directory, and execute:
\begin{code}{}
$ bower install
\end{code}
Back in the Pharo window, close the error message and evaluate the same instruction
again:
\begin{code}{}
TDDispatcher tideIndexPageUrl inspect
\end{code}
This should give you the URL at which your web browser should be pointed to.
Now copy this URL, open your web browser and paste it in the browser's address bar.
\subsection{ Starting the server}
The \ct{TDServer} class provides a convenient way to start$/$stop a Tide server, using
Zinc behind the scenes:
\begin{code}{}
TDServer startOn: 5000. "Start the server on port 5000"
TDServer stop. "Stop any running server"
\end{code}
\chapter{ A first example: the traditional counter}
To get started with Tide, we will implement the traditional counter example as shown in Figure \ref{tideCounter}.
Note that Tide already includes such an example in the \ct{Tide-Examples} package that you can refer to.
But better follow step by step the example.
\begin{figure}
\begin{center}
\includegraphics[width=1.0\textwidth]{images/tide-counter.png}\caption{A tide counter.\label{tideCounter}}\end{center}
\end{figure}
SD: picture should be cut in horizontal
Developing an application in Tide consists in two main elements, the presenter part developed in Pharo subclassing some Tide classes
and the client side developed in Amber.
A counter application should contain two buttons, one to increase and the other one to decrease a count. It should also display the count value to the user. While this application might seem extremely simplistic, it already shows some of the
core principles behind Tide: Presenters and Proxies.
\section{ The Pharo presenter part}
We start by creating the \ct{MyCounter} class in Pharo by subclassing \ct{TDPResenter}.
\begin{code}{}
TDPresenter subclass: #MyCounter
instanceVariableNames: 'count'
classVariableNames: ''
category: 'MyCounter'
\end{code}
Note that not all \symbol{34}exposed\symbol{34} objects have to be subclasses of \ct{TDPresenter}. As we will
see later, any object can be exposed to Amber using a \ct{TDModelPresenter} instance
on the domain object. \ct{TDPresenter} is the root class of the presenter hierarchy. We will
in \ref{presenters}.
Our class has one instance variable \ct{count} that we initialize to \ct{0}:
\begin{code}{}
MyCounter >> initialize
super initialize.
count := 0
\end{code}
To display the count value to the user, we will need to expose \ct{count} using an accessor.
We also add two methods to increase and decrease our counter:
\begin{code}{}
MyCounter >> count
^ count
MyCounter >> increase
count := count + 1
MyCounter >> decrease
count := count - 1
\end{code}
The final step we need to add the our counter is pragmas. Pragmas are
method metadata. Tide uses pragmas to expose data (called state in Tide)
and callbacks (called actions) to Amber. Here's our final version of the
counter class:
\begin{code}{}
MyCounter >> count
<state>
^ count
MyCounter >> increase
<action>
count := count + 1
MyCounter >> increase
<action>
count := count - 1
\end{code}
\section{ Registering applications with handlers}
We now have to create an entry point with our counter presenter in the Tide server.
To register the entry point, evaluate:
\begin{code}{}
MyCounter registerAt: 'my-counter'.
\end{code}
If we perform a request at \ct{http:$/$$/$localhost:5000$/$my-counter}, we get the following
\ct{JSON} data back:
\begin{code}{}
{
"__id__":"bwv8m74bhgzmv0dgvzptuy4py",
"actions":{
"increase":"/my-counter?_callback=359446426",
"decrease":"/my-counter?_callback=523483752"
},
"state":{
"count":0
}
}
\end{code}
\section{ Stepping back}
We can learn a couple of points from the preceding example:
\begin{itemize}
\item Presenter classes are registered as handlers, not instances. Tide will create \symbol{34}per session\symbol{34} instances of the registered class meaning that presenters are not shared between user sessions.
\item The entry point will have a \ct{handler} associated with a fixed entry point url \ct{'$/$my-counter'}. When someone will query that registered url, the presenter will generate \ct{JSON} data corresponding to its state and actions, and the handler to send it back in a response to the request.
\item Sending JSON is common and trivial using a Pharo package such as NeoJSON. What is much more interesting with Tide is the fact that exchanged data is described with the operations that can be applied to it. It provides an object-oriented view on the data. You get serialized active objects and not plain dead data.
\end{itemize}
\section{ The Amber part of the application}
The next step in our example is to create the Amber-side of this counter application.
We will use Amber to render an HTML widget of our counter, and perform actions using Tide proxies of the Pharo counter.
\subsection{ The client-side API}
On the client-side, root presenters exposed as handler can be accessed by creating proxies:
\begin{code}{}
myProxy := TDClientProxy on: '/my-counter'.
\end{code}
Interacting with proxies is performed via messages. However we have two kinds of messages. Messages sent to proxies will be resolved using their \textbf{state} and \textbf{actions} as defined on the server-side.
\begin{itemize}
\item Calls to state methods are resolved locally and synchronously, because the state is passed over to Amber as we previously say in the JSON data.
\end{itemize}
\begin{itemize}
\item Calls to action methods perform requests that will result in performing the corresponding
\end{itemize}
method on the Pharo object asynchronously. Once the action is performed, the proxy will
be automatically updated with possible new state and actions.
\begin{todo}
have a convention for state vs actions. Have a discussion about how to name action methods and state methods. The developer in Pharo could use a convention to make this explicit. Is it wise to have countState?
\end{todo}
\subsection{ Handling asynchronous calls}
Since action calls are not synchronous, Tide proxies have a special method \ct{then:} used
to perform actions only when and if the action is resolved and the proxy updated.
Sending a message that activates a state method is synchronous as shown in the following snippet.
\begin{code}{}
"synchronous state call"
myProxy count. "=> 0"
\end{code}
Now sending a message that activates a callback is asynchronous and as such we should use the \ct{then:} message when we want to access the state as shown below:
\begin{code}{}
"async action call"
myProxy increase; then: [
myProxy count "=> 1" ]
\end{code}
\subsection{ The widget class}
In Amber's IDE, create a new class \ct{MyCounterWidget}.
\begin{code}{}
Widget subclass: #MyCounterWidget
instanceVariableNames: 'counter header'
package: 'Tide-Amber-Examples'
\end{code}
The widget class has two instance variables: \ct{counter}, which holds a proxy over the
Pharo counter object, and \ct{header} which holds a reference on the header tag brush to
update the UI.
To initialize our counter widget, we connect it to the Pharo counter presenter as follows:
\begin{code}{}
MyCounterWidget >> initialize
super initialize.
counter := TDClientProxy on: '/my-counter'
\end{code}
Note that \ct{'$/$my-counter'} is the path to the server-side handler for our counter presenter.
We can now create the rendering methods as follows
\begin{todo}
explain \#connect
\end{todo}
\begin{todo}
explain \#appendToJQuery, explain that it renders the widget and calls \#renderOn:
\end{todo}
\begin{todo}
show renderOn: before and explain the connect and appendToJQuery after that.
\end{todo}
\begin{code}{}
MyCounterWidget >> render
counter connect then: [
self appendToJQuery: 'body' asJQuery ]
MyCounterWidget >> renderOn: html
header := html h1 with: counter count asString.
html button
with: '++';
onClick: [ self increase ].
html button
with: '--';
onClick: [ self decrease ]
MyCounterWidget >> update
header contents: [ :html |
html with: counter count asString ]
\end{code}
The \ct{render} method waits for the counter to be connected, then appends the widget to the
\ct{body} element of the page (using the \ct{renderOn:} method).
\ct{renderOn:} is a typical widget rendering method using the builtin Amber \ct{HTMLCanvas}.
The \ct{count} message send to the \ct{counter} proxy will be resolved as a state accessor as
defined on the server-side.
Finally instead of updating the entire HTML contents of the counter, \ct{update} will only
update the relevant part, the header.
We still miss two methods to actually increase and decrease our counter:
\begin{code}{}
MyCounterWidget >> increase
counter increase.
counter then: [ self update ]
MyCounterWidget >> decrease
counter decrease.
counter then: [ self update ]
\end{code}
Here's a screenshot of the final counter application:
\begin{figure}
\begin{center}
\includegraphics[width=1.0\textwidth]{images/tide-counter.png}\caption{file:$/$$/$images$/$tide-counter.png.\label{images/tide-counter.png}}\end{center}
\end{figure}
\begin{todo}
show the url of the application and show another state of the counter
\end{todo}
\chapter{ Actions}
We have seen in a nutshell in the previous sections how Tide actions work. They allow callbacks
to be performed from Amber to Pharo objects.
What we haven't discussed yet is how action callbacks in Tide can pass arguments to Pharo objects.
\section{ Action arguments}\subsection{ Literals}
Literal objects can be send as arguments to Tide actions. They will be converted to JSON and back in
Pharo. Any literal that can be serialized to JSON can be send as an argument:
\begin{itemize}
\item Numbers
\item Booleans
\item Strings
\item Dictionaries
\item Arrays (and OrderedCollections)
\end{itemize}
As an example, we can improve the counter to be able to increase it by any number instead of one:
\begin{code}{}
MyCounter >> increaseBy: anInteger
<action>
count := count + anInteger
\end{code}
On the Amber side, we can change the \ct{increase} method to increase the counter by 8:
\begin{code}{}
MyCounterWidget >> increase
self counter increaseBy: 8; then: [
self update ]
\end{code}
\subsection{ References}
While sending literals from Amber to Pharo is definitely useful and convenient, it is barely enough for more
complicated scenarios, where more complex objects have to be sent as arguments.
To manage this use case, Tide allows references to presenters to be used as action arguments too.
This means that any presenter proxy in Amber can be used as an argument to an action argument, and
that identity will be preserved on the Pharo side when the action message will be sent.
\section{ Chaining actions}
Tide actions can be easily chained without breaking the sequential flow of the application code,
using promises. This is an important property of action callbacks, because all requests done in
the client must be asynchronous, quickly leading to \symbol{34}spaghetti callbacks\symbol{34} code.
\begin{todo}
here we should have a JS example of async and show how we can express it in tide.
\end{todo}
\subsection{ Back to the counter example}
The following code snippet shows how \ct{increase} calls to our counter are chained.
\begin{code}{}
myCounter := TDClientProxy on: '/my-counter'.
myCounter increase; increase.
myCounter then: [ myCounter count ]. "=> 2"
10 timesRepeat: [ myCounter decrease ].
myCounter then: [ myCounter count ]. "=> -8"
\end{code}
\begin{todo:}
explain here the state vs action and async nature of actions
\end{todo:}
\chapter{ Presenters}\label{presenters}
Tide makes it easy to create presenters on domain objects.
\section{ Root presenters}
In any Tide application, some presenters must be always accessible at a fixed url. They are called
in Tide terminology root presenters. Root presenters are the entry points of Tide application.
We already saw one root presenter, the \ct{MyCounter} class.
To register a presenter class, use \ct{TDPresenter$>$$>$registerAt:}. Tide will register the presenter
class with the corresponding path and create instances per session.
Note that only one instance of registered root presenter class will be created per session. This
ensures that all actions will be performed on the same object.
\section{ Answering new presenters from action callbacks}
\begin{todo}
explain that if the action doesn't have an explicit return, it will update the proxy
\end{todo}
One important aspect of Tide presenters is the ability to answer new presenters from action methods.
From one root presenters several other presenters are accessed by reachability, allowing you to define
the flow of your application from one root presenter.
Any object answered from an action method that is not a presenter is converted using \ct{\#asPresenter}.
\subsection{ Example: a login presenter}
To illustrate the flow and security implied by actions and presenter, we will write a small login
application.
Let's start with the login presenter class itself. In order to minimize unnecessary complexity, the
\ct{TDLoginPresenter} class will hold a class variable \ct{Users} containing all users
\begin{code}{}
TDPresenter subclass: #MyLoginPresenter
instanceVariableNames: 'currentUser'
classVariableNames: 'Users'
category: 'Examples'
\end{code}
\begin{code}{}
MyLoginPresenter class >> initialize
Users := OrederedCollection new
MyLoginPresenter class >> addUser: anUser
^ Users add: anUser
\end{code}
To login an user, we validate an username and password against the \ct{Users} collection, and expose
the \ct{loginUsername:password:} method as a Tide action:
\begin{code}{}
MyLoginPresenter >> loginUsername: username password: password
<action>
^ currentUser := Users
detect: [ :each |
each username = username and: [ each password = password ] ]
ifNone: [ nil ]
\end{code}
Note that the \ct{\#login} method answers the \ct{currentUser} if any. The user object will be converted
into a presenter (instance of \ct{TDModelPresenter}) automatically and sent back to Amber as the
response of the action call.
We now only miss an user class to fill the login presenter:
\begin{code}{}
Object subclass: #MyUser
instanceVariableNames: 'username password'
classVariableNames: '
category: 'Examples'
MyUser >> username
<state>
^ username
MyUser >> password
<state>
^ password
MyUser >> username: aString
<action>
username := aString
MyUser >> password: aString
<action>
password := aString
\end{code}
Now we can register our login presenter class and add an user:
\begin{code}{}
MyLoginPresenter registerAt: 'my-login'.
MyLoginPresenter addUser: (MyUser new
username: 'John';
password: 'pass';
yourself)
\end{code}
To try out the login presenter, we can create a login proxy in an Amber workspace and inspect it.
\begin{code}{}
(TDClientProxy on: '/my-login') connect; inspect
\end{code}
Once connected, we can try to login:
\begin{code}{}
self
loginUsername: 'John' password: 'pass';
then: [ :user | user inspect ]
\end{code}
Since the login credentials are valid, Tide will create and answer a presenter on our \ct{MyUser}
instance, with all four methods defined as state and action.
In the user proxy inspector, we can now query and change the password, which would not have been
possible since the \ct{MyUser} presenter would not have been answered from the \ct{MyLoginPresenter}
instance, thus making the user out of our reach.
\begin{code}{}
self password "=> 'pass'".
self password: 'another_password'
\end{code}
\section{ Builtin presenter classes}
Tide contains convenient presenter classes builtin. Builtin presenter classes can be divided into
two categories:
\begin{itemize}
\item builtin base presenters (literal and collection presenters)
\item model presenters
\end{itemize}
\subsection{ Builtin presenters}
Tide provides the following default presenter classes:
\begin{itemize}
\item \ct{TDCollectionPresenter}, used by default by all collections but \ct{Dictionary}
\item \ct{TDDictionaryPresenter}, the default presenter for dictionaries
\item \ct{TDLiteralPresenter}, the default presenter for booleans, numbers and strings
\item \ct{TDModelPresenter}, the default presenter for all other objects
\end{itemize}
\section{ Custom presenters}\section{ Presenters and security}
\begin{todo}
explain how actions answering new presenters are important for security
\end{todo}
\chapter{ Managing sessions}\section{ Handling session expiration}
\begin{todo}
talk about the hook when sessions expire
\end{todo}
\section{ TDSessionManager}\section{ Using custom session classes}\chapter{ Handlers}
\begin{todo}
should it be there already? It seems too early to talk about that, but I need to introduce the concept in order to talk about the file upload handler.
\end{todo}
\chapter{ Managing file uploads}
Managing file uploads in the context of a flat-client application can be cumbersome.
The reason is that file uploads with the HTTP protocols were not made for asynchronous
uploads. Tide tries to solve this problem by abstracting away the implementation details
of an AJAX-friendly file upload with the \ct{TDFileHandler} class.
\section{ Creating file upload entry points}\chapter{ Handling exceptions}
\begin{todo}
talk about handling exceptions happening in the Pharo-side from Amber.
\end{todo}
\section{ TDPresenterExceptionHandler}\chapter{ A more advanced example: }
We should use the example of the book of Olivier: a comix collection.
\end{document}