Pyjamas (Python GWT) - Introduction and Creating Widgets


If you are a Python developer and don’t like JS a lot then look at Pyjamas desktop it will run on Web as well as desktop applications. It’s based on google webkit, pyjamas is a cross-browser web application development API. it looks like a desktop API, but underneath, pyjamas is an AJAX library and a comprehensive widget set (implemented as AJAX but you never go anywhere near JavaScript, because at the core of pyjamas is a python-to-javascript compiler).

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
  • even execute bits of javascript

So, how you can do it? answer is pretty simple because it’s based on webkit. so you get access to the DOM model, you get full HTML compliance, wickedly-fast javascript execution, media plugins, CSS stylesheets - everything.

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

1
2
3
4
5
6
7
8
9
<html>
    <head>
        <meta name="pygwt:module" content="Hello">
        <title>Hello</title>
    </head>
    <body bgcolor="white">
        <script language="javascript" src="pygwt.js"></script>
    </body>
</html>
View Code PYTHON
1
2
3
4
5
6
7
8
9
10
11
12
 
from ui import Button, RootPanel, Label
import Window
 
def greet(sender): Window.alert("Hello, AJAX!")
 
class Hello: 
   def onModuleLoad(self): 
                          b = Button("Click me", greet) 
                          l = Label("hello world") 
                          RootPanel().add(b) 
                          RootPanel().add(l)

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.

Vertical Slider

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:

View Code PYTHON
1
2
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.

View Code PYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
class VerticalDemoSlider(FocusWidget):
 
    def __init__(self, min_value, max_value, start_value=None):
 
        element = DOM.createDiv()
        FocusWidget.__init__(self, element)
 
        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:

View Code PYTHON
1
2
3
4
5
 DOM.setStyleAttribute(element, "position", "relative")
        DOM.setStyleAttribute(element, "overflow", "hidden") 
 
        self.handle = DOM.createDiv()
        DOM.appendChild(element, self.handle)

Then, as this is just a demonstration, we’re going to hand-code the slider handle with some attributes, making it 10 pixels high, a border of 1 pixel, fixing it to be the same width as the Widget, and making it a grey colour. A much better way to do this would be to set a CSS stylesheet where people could over-ride all these settings. Note that we don’t use DOM.setAttribute() to set the border, width and height. You should consult HTML specifications: you will find that “border” is an attribute for DOM tags such as “table”. So, if you try to call DOM.setAttribute() on a DIV tag, you’ll find that it silently fails in the browser - or if you remember, and examine the Javascript Console, you might be lucky and find a warning. However, if you try the same thing under Pyjamas-Desktop you will be rewarded with a much more useful run-time error. The upshot is: pay attention to the underlying DOM model, and remember to simultaneously develop your app using both Pyjamas and Pyjamas-Desktop, to save yourself a great deal of time. If you want to set a border on a “div” tag, you must set it as a CSS Style attribute:

View Code PYTHON
1
2
3
4
DOM.setStyleAttribute(self.handle, "border", "1px")
    DOM.setStyleAttribute(self.handle, "width", "100%")
    DOM.setStyleAttribute(self.handle, "height", "10px")
    DOM.setStyleAttribute(self.handle, "backgroundColor", "#808080")

Testing

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.

View Code PYTHON
1
2
3
4
5
6
7
8
9
10
11
""" testing our demo slider
"""
from pyjamas.ui import RootPanel
from pyjamas.Controls import VerticalDemoSlider
 
class ControlDemo:
    def onModuleLoad(self):
        b = VerticalDemoSlider(0, 100)
        RootPanel().add(b)
        b.setWidth("20px")
        b.setHeight("100px")

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:

View Code PYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def onClick(self, sender, event):
 
            # work out the relative position of cursor
            mouse_y = DOM.eventGetClientY(event) - \
                       DOM.getAbsoluteTop(sender.getElement())
            self.moveSlider(mouse_y)
 
        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:

View Code PYTHON
1
self.addClickListener(self)

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():

View Code PYTHON
1
2
3
val_diff = self.max_value - self.min_value
        new_value = ((val_diff * relative_y) / height_range) + self.min_value
        self.setValue(new_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.

View Code PYTHON
1
2
3
4
5
6
7
8
9
10
11
12
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):
        self.valuechange_listeners.append(listener)
 
    def removeControlValueListener(self, listener):
        self.valuechange_listeners.remove(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:

View Code PYTHON
1
2
3
4
5
6
b.addControlValueListener(self)
        self.label = Label("Not set yet")
        RootPanel().add(self.label)
 
    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.

Share:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google
  • DotNetKicks
  • DZone
  • Furl
  • IndianPad
  • Live
  • Netvouz
  • Propeller
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • TwitThis
  • YahooMyWeb
  • Fark
  • NewsVine
  • Blogosphere News
  • Blogsvine
  • LinkedIn
  • Pownce
  • Upnews
  • BlinkList
  • Global Grind
  • Kirtsy
  • PlugIM
  • ppnow Pyjamas (Python GWT)   Introduction and Creating Widgets
  • Socialogs
  • ThisNext
  • Webride
  • Meneame
  • Faves
  • MySpace
  • Yahoo! Buzz

Related posts

Tagged with: [ ]
You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
  • Good, can learn Python syntax for those who are interested.
blog comments powered by Disqus