How to update a Seaside component without a page refresh

Smalltalk Seaside has Ajax support, which can help you create web applications with great user experience. However, there is a lack of documentation so sometimes it is hard to have things done. In this article, I will show you how to implement a page with a progress bar which shows and constantly updates a state of server process. The component will pool the server via ajax calls and update it's state withouth a page refresh in the browser.

ProgressBar component

Let write a seaside component, which renders a progress bar. It has one field: an integer "progress" which can take values from 0 to 100:

WAComponent subclass: #WAProgressBar
	instanceVariableNames: 'progress'
	classVariableNames: ''
	category: 'Pillarhubarticle'

Render for the component is very straightforward with Bootstrap for Seaside package:

renderContentOn: html
	self renderProgressBarOn: html

renderProgressBarOn: html
	html tbsProgress: [ 
		html tbsProgressbar  
			valueNow: self progress 
	].

Let's make it fun by changing the progress of this component in a background process. It can be easly done by super-cool fork method. Consider the following code:

initialize
	super initialize.
	progress := 0.
      
	[| delay | 
		delay := Delay forSeconds: 1. 
		[ self progress < 100 ] whileTrue: [ 
			self progress: self progress + 1.  
			delay wait. 
			Transcript show: self progress; cr.
			] 
	] fork

At this point, you may want to register this component in Seaside. You need to add jQuery and Bootstrap libraries to the Seaside application. You may use following code:

app := WAAdmin register:  WAProgressBar asApplicationAt: '/test'.
app addLibrary: TBSDeploymentLibrary.
app addLibrary: JQ2DeploymentLibrary 

If you open http://localhost:8080/test in your browswer you should see an empty progress bar which obviosly do not update itself as it is quite static.

Adding ajax magic

Ajax is very easy in Seaside. There is a special interface for generating calls which do the most work for you. In our case we need to add a div tag around our progress bar and attach a script which will ask Seaside to re-render html of this component. The new renderContentOn: of our component should look like this:

renderContentOn: html
	html div 
		script: (html jQuery this load
			html: [:r | self renderProgressBarOn: r ];
			interval: 1 second
			);
		with: [ self renderProgressBarOn: html ]

Here we use script: call to attach a script to div tag. Script itself consists of load html: statement which tell Seaside to generate a javascript code which replaces div content with html rendered by the callback.

Please note that load html: callback has it's own renderer object r which should be used to produce an html.

interval: statement adds a javascript interval object around this call thus making browser to run the load every 1 second.

And this is it! It should work by now! If you open the page in your browser you will see that the component is constantly updated once in a second. This should look like this:

This is a progress bar in a browser

Please note that the communication is quite efficient as it consists only of the render of this particular component not all the page. Moreover, what is amazing is that the we can use the same render utilities which are used in the "general" rendering process for our Ajax call. Therefore, Ajax re-rendering does not have any special routines.

WAAjaxUpdaterDecoration a decoration that automates this stuff

You may notice that the code is quite generic and it can be applied to any html inside. If you download WebAnnouncement framework you may find WAAjaxUpdaterDecoration class, which provides a ready-to-use implementation of the same idea. And it is even more convinient to use as it is a Decoration which can be attached to any component by using standard addDecoration: call.

Moreover, the decoration provides an interface to set "stop condition" which is used to stop update requests from a client. So if the process is finished on the server then browser will stop pool for updates.

Other convenient feature is onStop: handler which allows you to set a callback on the stop condition triggered. For example you may want to run self answer on stop callback thus making a component which can be used via call: method as it will return the flow back when the stop condition is triggered.

The complete example with a progress bar you may find bellow:


WAExampleComponent>>doAutoUpdate
	self addDecoration: 
	    (WAAjaxUpdaterDecoration new 
			stopCondition: [self remoteTask isRunning]; 
			onStop: [self answer])

That is it! Thank you for your attention.

Further reading

Here some usefull links you may be interested in. Please add your own links to the comments below.