What is Pyjamas desktop?
It is a cross-platform application development API. like pygtk2, like python-qt4 and wxWidgets, is very powerful as you can do the followings:
- load complete html pages
- complete stylesheets
You should have exposure of developing a 10 line “Loader” HTML page and an optional CSS, thats cool isn’t it?. Even the CSS file is optional because you are provided via the Pyjamas API with access to some of the more useful CSS features such as setting the width and height, and you can if you wish to directly manipulate the CSS style properties from your application.
in short, you get to write apps that look like they ought to be running on a desktop, and pyjamas takes care of all the nasty browser tricks that you would normally perform in order to make it cross browser compatible for safari, opera, IE7, IE6, firefox, mozilla, midori etc.
Lets take a Hello World Example
<meta name="pygwt:module" content="Hello">
from ui import Button, RootPanel, Label
def greet(sender): Window.alert("Hello, AJAX!")
b = Button("Click me", greet)
l = Label("hello world")
Now, lets look at Pyjamas Desktop Widgets
An important part of a widget toolkit is being able to write your own widgets. In many widget sets, you are confronted immediately with a quite complex set of unusual-looking functions – paint, draw, refresh and other manipulations. Pyjamas has none of that: in both Pyjamas and Pyjamas-Desktop you’re manipulating the DOM model – an HTML page – as if it was an XML document. Pyjamas provides a module which makes the job of controlling the underlying DOM model that much easier, and this tutorial shows step-by-step how to go about creating your own widget.
We start off by importing the DOM model and, because the slider will receive mouse (and later keyboard) events, we base it on FocusWidget. FocusWidget has the means to add keyboard and event listeners, set a “tab order” index, and to set and clear focus:
from pyjamas import DOM
from pyjamas.ui import FocusWidget
So, we derive our class from FocusWidget. We don’t declare a width and height as parameters, because Pyjamas Widgets are based on HTML principles: DOM models. So, you either set the CSS “Class” with setStyleName(), or you use the Pyjamas Widget functions setWidth() and setHeight(). We do however want to pass in the slider’s minimum, maximum and default values, and we may also want to keep track of who might be interested to know that the slider’s value has changed.
def __init__(self, min_value, max_value, start_value=None):
element = DOM.createDiv()
self.min_value = min_value
self.max_value = max_value
if start_value is None:
start_value = min_value
self.value = start_value
self.valuechange_listeners = 
Here also is the first actual bit of underlying HTML / DOM model showing through: we’re basing the widget on a “div” tag, hence we call DOM.createDiv() and set that as the FocusWidget’s element. (Immediately, therefore, you can see that the Pyjamas Widgets are effectively… “guardian” classes that look after and manipulate bits of the underlying DOM model, making the whole process of creating and maintaining your application just that little bit easier to understand). We’re also going to copy what AbsolutePanel.__init__() does, making the container DIV free-moving, and we’re also going to throw in a second hard-coded “div” for the actual slider handle:
DOM.setStyleAttribute(element, "position", "relative")
DOM.setStyleAttribute(element, "overflow", "hidden")
self.handle = DOM.createDiv()
DOM.setStyleAttribute(self.handle, "border", "1px")
DOM.setStyleAttribute(self.handle, "width", "100%")
DOM.setStyleAttribute(self.handle, "height", "10px")
DOM.setStyleAttribute(self.handle, "backgroundColor", "#808080")
With the basic beginnings, it’s enough to test out, to see if we have it working. If all we wanted was a little grey box in our widget, we’d be entirely done.
""" testing our demo slider
from pyjamas.ui import RootPanel
from pyjamas.Controls import VerticalDemoSlider
b = VerticalDemoSlider(0, 100)
One thing I love about Pyjamas: this is enough code to do exactly what you want: create our slider, add it to the root panel, set its width to 20 pixels and the height to 100. Couldn’t get any easier. A quick run of this code shows that yes, indeed, we have a little grey box, which is very exciting. Next on the list is to make it move, and for that, we’ll add a “click listener”.
Making it move
To receive a click event, we use FocusWidget.addClickListener(). We’re going to make the widget itself receive the mouse click event. Looking at FocusWidget.onBrowserEvent(), we can see that we must add a function called onClick() to our VerticalDemoSlider. As we want to know where the mouse was clicked, we will need to add two arguments to the onClick() function, in order to receive the mouse event object as the second. Then, we simply take the mouse event y position, the absolute location of the container, and the “offset height” of the widget, do a little math and, copying some lines of code from AbsolutePanel.setWidgetPosition, we can change the location of the slider handle:
def onClick(self, sender, event):
# work out the relative position of cursor
mouse_y = DOM.eventGetClientY(event) - \
def moveSlider(self, mouse_y):
relative_y = mouse_y - DOM.getAbsoluteTop(self.getElement())
widget_height = self.getOffsetHeight()
# limit the position to be in the widget!
if relative_y < 0:
relative_y = 0
height_range = widget_height - 10 # handle height is hard-coded
if relative_y >= height_range:
relative_y = height_range
# move the handle
DOM.setStyleAttribute(self.handle, "top", "%dpx" % relative_y)
DOM.setStyleAttribute(self.handle, "position", "absolute")
Okay – let’s test it! Save, run… lights, camera, action, aaand… nothing. huh. What have we done wrong? Oh yes, we forgot a very important line. Go back to VerticalDemoSlider.__init__ and add this, at the end, and try again:
Amazing! We have a slider widget! A single-click moves the slider to where you clicked the mouse. Notice how the slider centre moves to where your mouse pointer actually points to: this is entirely a fluke, and is probably due to bugs in the CSS style implementation of your browser. Notice also that we haven’t actually set the value of the “slider”, but there’s enough maths to calculate it. We can add these extra lines on to the end of moveSlider():
val_diff = self.max_value - self.min_value
new_value = ((val_diff * relative_y) / height_range) + self.min_value
Then, we also add a setValue() function, which not only records the new value but also notifies any listeners. Copying the style of Label and other widgets’ addClickListener() and removeClickListener() functions, we’re doing addControlValueListener() and removeControlValueListener() to match.
def setValue(self, new_value):
old_value = self.value
self.value = new_value
for listener in self.valuechange_listeners:
listener.onControlValueChanged(self, old_value, new_value)
def addControlValueListener(self, listener):
def removeControlValueListener(self, listener):
Now we should really see if that works. In the “test code”, add these extra lines to ControlDemo.onModuleLoad() and also add the additional function onControlValueChanged:
self.label = Label("Not set yet")
def onControlValueChanged(self, slider, old_value, new_value):
self.label.setText("Value: %d" % int(new_value))
Leave me a comment and let me hear your opinion. If you’ve got any thoughts, comments or suggestions for things we could add, leave a comment! Also please Subscribe to our RSS for latest tips, tricks and examples on cutting edge stuff.