Skip to content

Latest commit

 

History

History
855 lines (405 loc) · 21.1 KB

documentation.pier.md

File metadata and controls

855 lines (405 loc) · 21.1 KB

I need to make it clear that presenters are on the Pharo side only and Proxies in Amber\.

 

explain what are state and actions

##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. Tide offers seamless communications between Amber and 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 JSON format. The 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 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.

Installing Tide

###1. Prerequisites

Tide requires several libraries. It of course depends on Pharo and Amber. Amber itself requires nodejs and 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.

####1.1. NodeJs

Go to nodejs.org and install node (and npm) for your platform.

####1.2. 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:

 

$ npm install -g bower

####1.3. Pharo

Tide requires Pharo 3.0. The simplest way to install it is to evaluate the following:

 

$ curl get.pharo.org/30+vm | bash

To start the Pharo image, evaluate:

 

$ ./pharo-ui Pharo.image

#####1.3.1. Preparing the Pharo image

 

We can also do it from the command line and it would be good to show how\.

 

Would be good to have one release of tide

to avoid development\.

 

use the pharo cli with the config option to load tide\.

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\.

 

  • Click on the background of the Pharo window
  • In the World menu that appears, click on Workspace
  • In that window, evaluate: (you type the thing, select the text and then right click and select "Do It" from the menu).

 

Metacello new
  configuration: 'Tide';
  version: #development;
  repository: 'http://www.smalltalkhub.com/mc/Pharo/MetaRepoForPharo30/main';
  load.

SD: Why the following is not in the troubleshooting session and bower install is not executed before?

 

we should have a simpler way to install it\.

When this is finished, evaluate:

 

TDDispatcher tideIndexPageUrl inspect

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:

 

$ bower install

Back in the Pharo window, close the error message and evaluate the same instruction again:

 

TDDispatcher tideIndexPageUrl inspect

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.

####1.4. Starting the server

The TDServer class provides a convenient way to start/stop a Tide server, using Zinc behind the scenes:

 

TDServer startOn: 5000. "Start the server on port 5000"
TDServer stop. "Stop any running server"

A first example: the traditional counter

To get started with Tide, we will implement the traditional counter example as shown in Figure 1.1. Note that Tide already includes such an example in the Tide-Examples package that you can refer to. But better follow step by step the example.

tideCounter

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.

###2. The Pharo presenter part We start by creating the MyCounter class in Pharo by subclassing TDPResenter.

 

TDPresenter subclass: #MyCounter
	instanceVariableNames: 'count'
	classVariableNames: ''
	category: 'MyCounter'

Note that not all "exposed" objects have to be subclasses of TDPresenter. As we will see later, any object can be exposed to Amber using a TDModelPresenter instance on the domain object. TDPresenter is the root class of the presenter hierarchy. We will in 7.1.

Our class has one instance variable count that we initialize to 0:

 

MyCounter >> initialize
    super initialize.
    count := 0

To display the count value to the user, we will need to expose count using an accessor. We also add two methods to increase and decrease our counter:

 

MyCounter >> count
    ^ count

MyCounter >> increase
    count := count + 1

MyCounter >> decrease
    count := count - 1

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:

 

MyCounter >> count
    <state>
    ^ count

MyCounter >> increase
    <action>
    count := count + 1

MyCounter >> increase
    <action>
    count := count - 1

###3. 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:

 

MyCounter registerAt: 'my-counter'.

If we perform a request at http://localhost:5000/my-counter, we get the following JSON data back:

 

{
  "__id__":"bwv8m74bhgzmv0dgvzptuy4py",
  "actions":{
    "increase":"/my-counter?_callback=359446426",
    "decrease":"/my-counter?_callback=523483752"
  },
  "state":{
    "count":0
  }
}

###4. Stepping back

We can learn a couple of points from the preceding example:

 

  • Presenter classes are registered as handlers, not instances. Tide will create "per session" instances of the registered class meaning that presenters are not shared between user sessions.
  • The entry point will have a handler associated with a fixed entry point url '/my-counter'. When someone will query that registered url, the presenter will generate JSON data corresponding to its state and actions, and the handler to send it back in a response to the request.
  • 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.

###5. 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.

####5.1. The client-side API

On the client-side, root presenters exposed as handler can be accessed by creating proxies:

 

myProxy := TDClientProxy on: '/my-counter'.

Interacting with proxies is performed via messages. However we have two kinds of messages. Messages sent to proxies will be resolved using their state and actions as defined on the server-side.

 

  • 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.

 

  • Calls to action methods perform requests that will result in performing the corresponding method on the Pharo object asynchronously. Once the action is performed, the proxy will be automatically updated with possible new state and actions.

 

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?

####5.2. Handling asynchronous calls

Since action calls are not synchronous, Tide proxies have a special method 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.

 

"synchronous state call"
myProxy count. "=> 0"

Now sending a message that activates a callback is asynchronous and as such we should use the then: message when we want to access the state as shown below:

 

"async action call"
myProxy increase; then: [
    myProxy count "=> 1" ]

####5.3. The widget class

In Amber's IDE, create a new class MyCounterWidget.

 

Widget subclass: #MyCounterWidget
	instanceVariableNames: 'counter header'
	package: 'Tide-Amber-Examples'

The widget class has two instance variables: counter, which holds a proxy over the Pharo counter object, and 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:

 

MyCounterWidget >> initialize
    super initialize.
    counter := TDClientProxy on: '/my-counter'

Note that '/my-counter' is the path to the server-side handler for our counter presenter.

We can now create the rendering methods as follows

 

explain \#connect

 

explain \#appendToJQuery, explain that it renders the widget and calls \#renderOn:

 

show renderOn: before and explain the connect and appendToJQuery after that\.

 

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 ]

The render method waits for the counter to be connected, then appends the widget to the body element of the page (using the renderOn: method).

renderOn: is a typical widget rendering method using the builtin Amber HTMLCanvas. The count message send to the 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, update will only update the relevant part, the header.

We still miss two methods to actually increase and decrease our counter:

 

MyCounterWidget >> increase
	counter increase.
	counter then: [ self update ]

MyCounterWidget >> decrease
	counter decrease.
	counter then: [ self update ]

Here's a screenshot of the final counter application:

 

show the url of the application and show another state of the counter

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.

###6. Action arguments

####6.1. 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:

 

  • Numbers
  • Booleans
  • Strings
  • Dictionaries
  • Arrays (and OrderedCollections)

As an example, we can improve the counter to be able to increase it by any number instead of one:

 

MyCounter >> increaseBy: anInteger
    <action>
    count := count + anInteger

On the Amber side, we can change the increase method to increase the counter by 8:

 

MyCounterWidget >> increase
    self counter increaseBy: 8; then: [
        self update ]

####6.2. 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.

###7. 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 "spaghetti callbacks" code.

 

here we should have a JS example of async and show how we can express it in tide\.

####7.1. Back to the counter example

The following code snippet shows how increase calls to our counter are chained.

 

myCounter := TDClientProxy on: '/my-counter'.

myCounter increase; increase.
myCounter then: [ myCounter count ]. "=> 2"

10 timesRepeat: [ myCounter decrease ].
myCounter then: [ myCounter count ]. "=> -8"

 

explain here the state vs action and async nature of actions

Presenters

Tide makes it easy to create presenters on domain objects.

###8. 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 MyCounter class.

To register a presenter class, use 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.

###9. Answering new presenters from action callbacks

 

explain that if the action doesn't have an explicit return, it will update the proxy

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 #asPresenter.

####9.1. 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 TDLoginPresenter class will hold a class variable Users containing all users

 

TDPresenter subclass: #MyLoginPresenter
	instanceVariableNames: 'currentUser'
	classVariableNames: 'Users'
	category: 'Examples'

 

MyLoginPresenter class >> initialize
    Users := OrederedCollection new

MyLoginPresenter class >> addUser: anUser
    ^ Users add: anUser

To login an user, we validate an username and password against the Users collection, and expose the loginUsername:password: method as a Tide action:

 

MyLoginPresenter >> loginUsername: username password: password
    <action>
    ^ currentUser := Users 
        detect: [ :each | 
            each username = username and: [ each password = password ] ]
        ifNone: [ nil ]

Note that the #login method answers the currentUser if any. The user object will be converted into a presenter (instance of 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:

 

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

Now we can register our login presenter class and add an user:

 

MyLoginPresenter registerAt: 'my-login'.
MyLoginPresenter addUser: (MyUser new
    username: 'John';
    password: 'pass';
    yourself)

To try out the login presenter, we can create a login proxy in an Amber workspace and inspect it.

 

(TDClientProxy on: '/my-login') connect; inspect

Once connected, we can try to login:

 

self 
    loginUsername: 'John' password: 'pass';
    then: [ :user | user inspect ]

Since the login credentials are valid, Tide will create and answer a presenter on our 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 MyUser presenter would not have been answered from the MyLoginPresenter instance, thus making the user out of our reach.

 

self password "=> 'pass'".
self password: 'another_password'

###10. Builtin presenter classes

Tide contains convenient presenter classes builtin. Builtin presenter classes can be divided into two categories:

 

  • builtin base presenters (literal and collection presenters)
  • model presenters

####10.1. Builtin presenters

Tide provides the following default presenter classes:

 

  • TDCollectionPresenter, used by default by all collections but Dictionary
  • TDDictionaryPresenter, the default presenter for dictionaries
  • TDLiteralPresenter, the default presenter for booleans, numbers and strings
  • TDModelPresenter, the default presenter for all other objects

###11. Custom presenters

###12. Presenters and security

 

explain how actions answering new presenters are important for security

Managing sessions

###13. Handling session expiration

 

talk about the hook when sessions expire

###14. TDSessionManager

###15. Using custom session classes

Handlers

 

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\.

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 TDFileHandler class.

###16. Creating file upload entry points

Handling exceptions

 

talk about handling exceptions happening in the Pharo\-side from Amber\.

###17. TDPresenterExceptionHandler

A more advanced example:

We should use the example of the book of Olivier: a comix collection.