pyjamas the book

61
"Copyright (C) 2009 Luke Kenneth Casson Leighton ([email protected]) and Erik Westra" Introduction Computer Applications help us with the presentation, creation and manipulation of information and knowledge. Deployment of applications on the Internet has seen an explosive rise in complexity. Programming languages and frameworks have struggled to keep up, imposing legacy infrastructure and ways of working that were never designed to cope with the ways in which they are now being used. Attempts are being made to provide better infrastructure that suits the increasing Internet-led demand for access to information and knowledge, yet at the same time provide an easier way to reuse the same source code to write applications that run stand-alone on the Desktop. Adobe AIR, Silverlight and its free-software counterpart, Moonlight, are proprietary-driven frameworks that blur the distinction between "Desktop" and "Internet". Google's Web Toolkit (GWT) is a toolkit exclusive to Java that allows you to write applications that look like they are Java Desktop applications, but actually they are being run in a web browser (as AJAX). You cannot, however, take the same GWT Java application and actually run it as a desktop application (unless it's under Adobe AIR, as AJAX). In 2006, GWT was ported to python, by James Tauber, in a successful experiment called Pyjamas. The 80,000 or so lines of source code that made up GWT 1.2 were dramatically reduced to only 8,000, for Pyjamas. Thus, in exactly the same way that GWT is a java-to-javascript compiler and AJAX Widget set, Pyjamas is a python-to-javascript compiler and AJAX Widget set (with an identical API to that of GWT's Widget API). In 2008, Pyjamas was ported to the Desktop, bypassing all of the javascript completely. The underlying technology used was Webkit, which is the same technology behind Adobe AIR, Google Chrome, Safari, Midori and the iPhone browser. Thanks to Webkit, Pyjamas, by keeping such exulted company, becomes the world's first free-software Applications framework that is both cross-browser and cross-platform. Also, by using Webkit, the Pyjamas-Desktop port is cross-widget-set as well. The strategic significance of the independence of the Pyjamas User-Interface API is easily underestimated. A free software python-based application can be written - once - and can run on any web browser, including IE6, IE7, Opera, Mozilla, Firefox and Safari, and also it can be run on embedded platforms such as smartphones and PDAs, as well as Desktop OSes such as Linux, Windows, FreeBSD and MacOSX. Also, the Pyjamas API is effectively a new type of Widget set, and is not only functionally superior in its scope and features to its competitors, Qt4 and Gtk2, but is also easier to use, extend and understand. The reason for this is quite straightforward: Pyjamas is based on DOM model - browser - manipulation, and browser technology has had to mature much, much faster than Desktop technology. Browsers have to be far more tolerant, forgiving and flexible when it comes to layout and presentation, whereas the Desktop application APIs are really quite rigid; advise developers not to fix the width and height of widgets, for fear of interfering with the framework's limited ability to cope with resizing and layouts; extension of either of the competitor frameworks is far from straightforward; Rich text (such as HTML) support is limited or severely limited, and is broken by design. By contrast, the features that Pyjamas comes with by default, even in Pyjamas Desktop, includes not only full and complete CSS and HTML support, but also includes support for things like SVG 2D canvas and NPAPI plugins such as Adobe Flash. The future of Pyjamas on Desktops includes, using Webkit, the tantalising possibility of supporting other languages, such as c++ (Pyjamas++) and even Java. It would be very interesting to see Google's Web Tookit ported to the Desktop, for example. And it would be as equally interesting to see Pyjamas ported to Ruby, by using rb2js (http://rb2js.rubyforge.org/ ), as well as other languages. So, although this book covers the Python programming language, it's definitely worthwhile raising these future language possibilities, as it would make the Pyjamas API a truly cross-language, cross-platform, cross-desktop and cross-widget-set Free Software Applications Development API. Scope of this book Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum... 1 of 61 08-03-2009 18:11

Upload: simplydragons

Post on 27-Mar-2015

367 views

Category:

Documents


6 download

TRANSCRIPT

Page 1: Pyjamas the Book

"Copyright (C) 2009 Luke Kenneth Casson Leighton

([email protected]) and Erik Westra"

Introduction

Computer Applications help us with the presentation, creation and manipulation of information and

knowledge. Deployment of applications on the Internet has seen an explosive rise in complexity.

Programming languages and frameworks have struggled to keep up, imposing legacy infrastructure andways of working that were never designed to cope with the ways in which they are now being used.

Attempts are being made to provide better infrastructure that suits the increasing Internet-led demand foraccess to information and knowledge, yet at the same time provide an easier way to reuse the same

source code to write applications that run stand-alone on the Desktop. Adobe AIR, Silverlight and its

free-software counterpart, Moonlight, are proprietary-driven frameworks that blur the distinction between

"Desktop" and "Internet". Google's Web Toolkit (GWT) is a toolkit exclusive to Java that allows you towrite applications that look like they are Java Desktop applications, but actually they are being run in a

web browser (as AJAX). You cannot, however, take the same GWT Java application and actually run it

as a desktop application (unless it's under Adobe AIR, as AJAX).

In 2006, GWT was ported to python, by James Tauber, in a successful experiment called Pyjamas. The

80,000 or so lines of source code that made up GWT 1.2 were dramatically reduced to only 8,000, forPyjamas. Thus, in exactly the same way that GWT is a java-to-javascript compiler and AJAX Widget set,

Pyjamas is a python-to-javascript compiler and AJAX Widget set (with an identical API to that of GWT's

Widget API). In 2008, Pyjamas was ported to the Desktop, bypassing all of the javascript completely. The

underlying technology used was Webkit, which is the same technology behind Adobe AIR, GoogleChrome, Safari, Midori and the iPhone browser. Thanks to Webkit, Pyjamas, by keeping such exulted

company, becomes the world's first free-software Applications framework that is both cross-browser and

cross-platform. Also, by using Webkit, the Pyjamas-Desktop port is cross-widget-set as well.

The strategic significance of the independence of the Pyjamas User-Interface API is easily

underestimated. A free software python-based application can be written - once - and can run on any

web browser, including IE6, IE7, Opera, Mozilla, Firefox and Safari, and also it can be run on embeddedplatforms such as smartphones and PDAs, as well as Desktop OSes such as Linux, Windows, FreeBSD

and MacOSX.

Also, the Pyjamas API is effectively a new type of Widget set, and is not only functionally superior in its

scope and features to its competitors, Qt4 and Gtk2, but is also easier to use, extend and understand.

The reason for this is quite straightforward: Pyjamas is based on DOM model - browser - manipulation,

and browser technology has had to mature much, much faster than Desktop technology. Browsers haveto be far more tolerant, forgiving and flexible when it comes to layout and presentation, whereas the

Desktop application APIs are really quite rigid; advise developers not to fix the width and height of

widgets, for fear of interfering with the framework's limited ability to cope with resizing and layouts;

extension of either of the competitor frameworks is far from straightforward; Rich text (such as HTML)

support is limited or severely limited, and is broken by design. By contrast, the features that Pyjamas

comes with by default, even in Pyjamas Desktop, includes not only full and complete CSS and HTML

support, but also includes support for things like SVG 2D canvas and NPAPI plugins such as Adobe

Flash.

The future of Pyjamas on Desktops includes, using Webkit, the tantalising possibility of supporting other

languages, such as c++ (Pyjamas++) and even Java. It would be very interesting to see Google's Web

Tookit ported to the Desktop, for example. And it would be as equally interesting to see Pyjamas portedto Ruby, by using rb2js (http://rb2js.rubyforge.org/), as well as other languages. So, although this book

covers the Python programming language, it's definitely worthwhile raising these future language

possibilities, as it would make the Pyjamas API a truly cross-language, cross-platform, cross-desktop

and cross-widget-set Free Software Applications Development API.

Scope of this book

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

1 of 61 08-03-2009 18:11

Page 2: Pyjamas the Book

The purpose of this book is to assist Python developers who are either Web developers, PyQt4 or

PyGtk2 Desktop developers to write applications that will run both on the Desktop and in a Web browser,

depending on whether they choose to run the application using Pyjamas-Desktop or to compile it to

AJAX, using the Pyjamas Javascript compiler.

The book will introduce the beginner to the alien concept of "easy web application development"; it will

serve as a reference for both beginners and experienced developers; also covered will be how to createyour own widgets, and how to port a Javascript widget library (such as extjs, prototype or YUI - Yahoo

User Interface) to Pyjamas. Creation of Pyjamas widgets is something that is infinitely easier than it is

for Gtk2 and Qt4, thanks to the simple concept of manipulating a browser-based DOM model.

In only thirty minutes, even the average Python developer should be up and running, creating ubiquitous

rich media applications, and be dreaming one day of writing their own version of a gmail client...

Dive Straight in

Not only for the impatient, but also getting right to the point, start by downloading the Pyjamas source

code, from either http://pyjs.org or http://code.google.com/p/pyjamas and, if you already have the

standard Python 2.5 interpreter installed, you can begin immediately. Pyjamas is self-contained and doesnot even require to be "installed".

Unpack the downloaded archive, and browse to the examples directory. You will find a script called

"buildall.sh". If you just want to compile the "helloworld" example, browse to the examples/helloworld

directory, and you will see a "build.sh" file and a README. Follow the instructions, there, which instructyou to run the following command:

python ../../builder/build.py Hello.py

You will then be able to open your web browser, browsing to the examples/helloworld/output/Hello.html file,

and, assuming that you have Javascript enabled, you should see a single button, "Click me!". Click it, andyou should get an alert "Hello Ajax!".

Congratulations, you have compiled and tested your first Pyjamas application.

If you accidentally opened examples/helloworld/Hello.html in yourweb browser, this is a "stub loader" that requires the pygwt.jsand the compiled application to be in the same subdirectory.Double-check that you have opened examples/helloworld/output/Hello.html,not examples/helloworld/Hello.html

Debugging

If you didn't get anything displayed, then it is of vital importance now and forever that you examine the

Javascript Console of your browser of choice - right now. Get used to doing that, even at this early

stage: your development cycle depends critically on you understanding the link between the Python codeyou are writing in and the Javascript that it becomes.

Also of critical importance will be to install a Javascript Debugger tool. "Venkman" is the codename of theFirefox plugin that you will need; for Internet Explorer you can get away with the stand-alone Script

Debugger, although you can also use the Script Editor that comes with Office or Visual Studio if you

prefer. The key tool that is absolutely essential is to be able to see a stack trace (Opera will display astack trace, by default).

The alternative is to run your application under Pyjamas-Desktop, and you will then get a standard python

stack trace error printed on the console output. So, if you get absolutely stuck, running the code in a

browser, you can always run it under Pyjamas-Desktop.

More advice on debugging applications will be given later.

Editing your first application

Lovely as it is to run, it's more useful to be able to add things, so start by modifying Hello.py so that it

looks like this:

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

2 of 61 08-03-2009 18:11

Page 3: Pyjamas the Book

from pyjamas.ui import Button, RootPanel, HTMLfrom pyjamas import Window

def greet(sender): Window.alert("Hello, AJAX!")

class Hello: def onModuleLoad(self): b = Button("Click me", greet) RootPanel().add(b) hw = HTML("Hello <b>World</b>") RootPanel().add(hw)

The changes made are the addition of the import of HTML, from pyjamas.ui; the declaration of a

variable, hw, and its addition to the RootPanel. Re-run the build command, refresh the browser window,

and, underneath the Button there should now be the words "Hello World", with "World" being in bold.Again, if it doesn't appear, double-check the Javascript Console for error messages - for example, if you

see "HTML is undefined", it'll be because you missed out the line "from pyjamas.ui import HTML".

if you are unfamiliar with Python, and you get a build error, read up onPython application editing: double-check that you have used the same"indent" level of whitespace, when you added the two extra lines in theonModuleLoad() function.

Horizontal Layout

Next, let's try editing the application so that the Button and the HTML are laid out horizontally, rather thanvertically. Try this:

from pyjamas.ui import Button, RootPanel, HTMLfrom pyjamas.ui import HorizontalPanelfrom pyjamas import Window

def greet(sender): Window.alert("Hello, AJAX!")

class Hello: def onModuleLoad(self): b = Button("Click me", greet) hw = HTML("Hello <b>World</b>")

p = HorizontalPanel() p.add(b) p.add(hw)

RootPanel().add(p)

Here, we have imported HorizontalPanel, then we have declared an instance (p) to which we have addedboth the Button and the HTML. Then, we have added the HorizontalPanel, p, into the RootPanel.

Remember to recompile, and remember to refresh the browser.

It's worth emphasising that Pyjamas applications really are this straightforward, and that quite complex

layouts can be built up very quickly and in a straightforward manner, with the simple deployment ofHorizontalPanel, VerticalPanel, Grid and HTMLTable. However, at this early stage, although our

application is "functional", it doesn't look too pretty. We should probably add in some CSS styling, next.

CSS Styling

Pyjamas being an HTML-capable development platform, you have complete access to CSS. Start off bycreating a subdirectory called "public", in the examples/helloworld directory, and create a file, Hello.css,

as follows:

.panel { margin: 20px;

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

3 of 61 08-03-2009 18:11

Page 4: Pyjamas the Book

padding: 20px; background-color: #80ff80; width: 50%; height: 200px;}.helloworldwords { font-size: 200px; border: 1px solid #aaaaff; margin: 10px; padding: 10px;}

Next, create a matching Hello.html file, again in the subdirectory called "public", If there is already one in

the examples/helloworld directory, copy it into "public" to save yourself some time, and edit it to add a linkto the stylesheet:

<html> <head> <meta name="pygwt:module" content="Hello"> <link rel='stylesheet' href='Hello.css'> <title>Hello</title> </head> <body bgcolor="white"> <script language="javascript" src="pygwt.js"></script> </body></html>

All we've done, here, is add a link to our stylesheet, Hello.css - there's no "voodoo magic" going on, at

this stage. Our next step, however, is to tell the HorizontalPanel instance and the HTML instance thatthey should be using the two CSS style classes we've created. Add the two lines, shown, to the Hello.py

file:

p.add(b) p.add(hw)

p.setStyleName("panel") # same name as in Hello.css hw.setStyleName("helloworldwords") # style declared in Hello.css

RootPanel().add(p)

The HorizontalPanel gets a CSS style class name of "panel", and the HTML instance gets one of

"helloworldwords". Again, recompile, and refresh the browser, and you should find that theHorizontalPanel now takes up half the screen width, is an ugly light green colour; that the size of the

words "Hello World" have doubled in size and are surrounded by a pretty light-blue border.

You're now well on your way to causing much grief and eye-burn to all but the most colour-blind of

computer users.

Random things to try

If you're feeling adventurous, you might like to add in two buttons, or perhaps add in a CheckBox or

RadioButton (remember to import them!). Also, it's highly illustrative to add things to the RootPanel from

the greet() function, as this will definitely emphasise that this is not "static HTML".

from pyjamas.ui import CheckBoxdef greet(sender): RootPanel().add(HTML("Not in Kansas no more")) y = CheckBox("yellow road") r = CheckBox("red road") RootPanel().add(y) RootPanel().add(r)

Remember to click the button several times, and you should find that the text and two checkboxes are

added on each click. You might wish to create a global variable that counts up by one - x += 1 - that you

tack on to the end of the string - "No of times not in Kansas: %d" % x as this will make it

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

4 of 61 08-03-2009 18:11

Page 5: Pyjamas the Book

clearer:

x = 0

def greet(sender): global x x += 1 RootPanel().add(HTML("No of times not in Kansas: %d" % x))

What we have done

To recap what's been achieved, in this section, we have:

Downloaded and unpacked the Pyjamas Compiler.Compiled and run the "Hello World" application (yippee!)

Optionally encountered our first Javascript Console error

Added an HTML widget to display "Hello World" under the button

Optionally discovered that widgets need to be importedChanged the layout from vertical to horizontal

Added some eye-burning colour using a CSS Stylesheet.

Learned in a few minutes that Pyjamas apps are dead-easy.

Discovered that we're definitely not in Kansas.

So we have gained the confidence that Pyjamas applications can be written in very few lines of code, andalso now have a feel for what the development cycle will be like. The only thing is that the "Hello World"

application is, after all, very much like a "Static HTML" page, despite it being in Javascript. The reason is

because we're not exactly interacting with the rest of the world.

Quite sophisticated single-user applications can still be written either using SVG 2D Canvas (see the

examples/addonsgallery Canvas Tab) for games and entertainment purposes, but it's only when we startusing AJAX, to communicate with a Web Server, that we can really begin to create powerful "rich media"

interactive - and useful - applications.

AJAX - Gateway to the World

Javascript, when run in browsers, is restricted - for example, access to user's files is off-limits. So, evenwhen running Pyjamas-Desktop, your gateway to the rest of the world in Pyjamas apps should be with

AJAX - Asynchronous Javascript and XML. The implications are that applications need to be divided

down into a front-end and a back-end, with the back-end running on a Web Server. Even for stand-alone

applications, applications still need to talk to a Web Server, which will need to be installed locally.

Pyjamas includes a library - HTTPRequest - that provides convenient access to AJAX. The differencesbetween Internet Explorer and other browsers when using AJAX, that you'd normally be faced with, is

taken care of by the library, so that, on all platforms, the HTTPRequest library is functionally identical.

The simplest use of HTTPRequest is to obtain text files or HTML content from the server, whilst more

involved ones will use HTTPRequest to provide a Remote Procedure Call framework, such as

JSONRPC or XMLRPC. Here are some Pyjamas examples, included with the Pyjamas distribution, thatuse AJAX / HTTPRequest:

The slideshow example loads its slides in text format, and then applies some simple Wiki-like

markup rules to convert the text file into HTML.

The TemplatePanel addons module can be used as the basis of a Content Manager, as it

downloads HTML content, allows it to be edited (with FSCK Rich Text Editor) and uploaded - allusing AJAX.

The JSONRPCService example shows how powerful AJAX can be, as the JSONRPC

infrastructure transparently makes function calls look like they were being made on the server.

This latter example - the JSONRPCService one - is one which is going to be explored in more detail,

later, but first it's worth illustrating with the simplest example, the Slideshow Loader.

Simple use of HTTPRequest

Here is the basis of the Slideshow example, which you should save as Slideshow.py:

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

5 of 61 08-03-2009 18:11

Page 6: Pyjamas the Book

from pyjamas.ui import HTML, RootPanelfrom pyjamas.HTTPRequest import HTTPRequest

class SlideLoader: def __init__(self, panel): self.panel = panel

def onCompletion(self, text): self.panel.setSlide(text)

def onError(self, text, code): self.panel.onError(text, code)

def onTimeout(self, text): self.panel.onTimeout(text)

class Slideshow:

def onModuleLoad(self): self.slide = HTML() RootPanel().add(self.slide)

HTTPRequest().asyncPost("test_slide.txt", "", SlideLoader(self))

def setSlide(self, content): self.slide.setHTML("<pre>%s</pre>" % content)

def onError(self, text, code): self.slide.setHTML(text + "<br />" + code)

Create a matching Slideshow.html, saving it in a folder called public:

<html> <head> <meta name="pygwt:module" content="Slideshow"> <title>Test Slideshow!</title> </head> <body bgcolor="white"> <script language="javascript" src="pygwt.js"></script>

<iframe id='__pygwt_historyFrame' style='width:0;height:0;border:0'></iframe> </body></html>

Now create a test slide in the public folder, and call it test_slide.txt:

Yippee, we get to see how AJAX works.We put the content here, wrapped with <pre> </pre>so it looks pretty.You could put anything you wanted in here.

You should now have three files on your computer like this:

Slideshow.pypublic/Slideshow.htmlpublic/test_slide.txt

Compile this example to javascript, by running the following command:

python ../../builder/build.py Slideshow.py

You will now need to use a Web browser to access the output, but it is important that you load the output

from a Web Server, not from your local filesystem. There are several free web servers and web

frameworks available, and for the purposes of this exercise, one that serves static web pages is

perfectly adequate.

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

6 of 61 08-03-2009 18:11

Page 7: Pyjamas the Book

AJAX has security restrictions on where the content can be downloaded from,due to malicious misuse. As there is no visual indication, with AJAX,when content is being downloaded, AJAX was used to obtain and run maliciousscripts from web sites other than the one being presented to the user.So, now, when you browse a web site, if AJAX is used, the AJAX content canonly be downloaded from the same web site. Local file system URLs aredefinitely "out".

Configuring Apache2

A recommended way to serve web pages is to install and use Apache2. You should, after installation, set

up a location where your compiled output is made visible. Here is an Apache Config snippet that you can

adapt to your needs, by changing the directory location:

Alias /examples/ "/home/lkcl/src/pyjamas/examples/"<Directory "/home/lkcl/src/pyjamas/examples/"> Options Indexes FollowSymLinks MultiViews AllowOverride None Order deny,allow allow from all</Directory>

The above is pretty standard fare for an Apache configuration: remember however that you must ensure

that the AJAX application matches up with the locations that you set, here, in the Apache2 configuration,

and that the directories you ask the scripts and static content to be loaded from must actually exist!

Remember to change the location to one where your output is compiled!Also, make sure that you place the trailing slashes on the end of thedirectory paths. Also, remember to enable mod_cgi if you want CGIscripts to run. Search on google for advice on how to set up ApacheCGI scripts, for your specific system, if you run into difficulties.

With the above example, once you have started (or restarted) Apache2, you will be able to open up a

web browser on http://localhost/examples/ to run your application. If successful, you should see thecontents of the test slide in the browser.

Remember that if you don't see anything, check the Javascript console,and enable script debugging. Also, double-check that you have loadedoutput/Slideshow.html, not public/Slideshow.html, in your browser.

Using Python's SimpleHTTPServer.py

TODO

python -c "import CGIHTTPServer; CGIHTTPServer.test()"

Using Lighttpd

TODO

server_modules = ( "mod_access", "mod_accesslog", "mod_dirlisting" )

server.port = 3000server.document-root = "/home/Administrator/prg/pyjamas-book"

server.errorlog = "/tmp/errorlog"

accesslog.filename = "/tmp/accesslog"

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

7 of 61 08-03-2009 18:11

Page 8: Pyjamas the Book

dir-listing.activate = "enable"

mimetype.assign = ( ".html" => "text/html", ".txt" => "text/plain", ".jpg" => "image/jpeg", ".png" => "image/png")

Simple enhancements

In the Pyjamas slideshow example, setSlide() munges the AJAX-downloaded content before it gets

displayed, and you could prove to yourself that this is definitely not "static HTML" by modifying the

content in setSlide, by converting the content to uppercase:

def setSlide(self, content): content = content.upper() self.slide.setHTML("<pre>%s</pre>") % content

The extra line is the second one - modifying the content before it is displayed.

Remember to recompile the application, and refresh the browser.

So, in this simple example, we've shown how to load content, dynamically. Which is nice, because loading

the Web page first, and the content separately, is what AJAX is supposed to be all about: saving time

and speeding up the user experience. Of course, loading only the one page isn't that much of animprovement, but it's the thought that counts. definitely.

However, with a small leap in imagination, it should be clear that by specifying a dynamic URL in the

HTTPRequest, instead of a static page, this simple example could be adapted to load user's individual

email messages, or turned into a wiki. The next step is to make the process of loading those email

messages, wiki pages or stock prices that much easier, and that much more like a standard desktopapplication.

JSONRPC: a better AJAX experience

JSONRPC stands for "JavaScript Object Notation / Remote Procedure Calls". To make sense of that

banale sentence, it helps to look up the two anacronyms separately. JSON is simply a way to encode

objects - lists, dictionaries, integers, strings and floating point numbers - in a way that web browsers willfind easy to encode and decode. RPC is simply a way to make function calls on a server. In other words,

whilst the function is being called on the client, the function is actually executed on a (remote) server,

and the parameters and the return result are shipped back-and-forth without the client or the serverhaving to do any particularly hard, or obtuse, work.

The best RPC systems allow the function's parameters to be shipped back to the client, if they were

modified, as well as the return result of the function. However, JSONRPC isn't the brightest bunny on the

block, and so we have to make do with just a return result. Fortunately, JSON is capable of returning

reasonably complex results - lists of dictionaries, dictionaries of lists of strings etc. - so the limitations ofJSONRPC aren't the end of the world.

The crucial difference between running an application that makes"real" function calls rather than using a client-server arrangementto proxy the function parameters and return result back-and-forth isthat, as a developer, you must never forget that the functioncall can fail! The server can, at any time, drop off the network,or fail to respond in a timely fashion. It's absolutely essentialthat you design your application with this in mind. RPC frameworkshelp you out here, and the JSONRPC infrastructure in Pyjamas is noexception.

So, to get started with JSONRPC, the following is needed:

A JSONRPC client (there's one built in to Pyjamas)

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

8 of 61 08-03-2009 18:11

Page 9: Pyjamas the Book

A JSONRPC-aware Web Server Framework

A Web Server (even when running on the desktop)

Discussed first will be the frameworks: some of the Python-based options, such as Twisted Matrix,

Django, Web.py and cgi-bin will be covered. Next, once a framework is set up, connecting to it with theJSONRPC example client will be covered.

JSONRPC Web Server Frameworks

There are dozens of JSONRPC frameworks available, as a quick google search shows. Any one of

them will suffice, however the scope of this book is limited to Python, so the following Python-based

JSONRPC frameworks will be covered:

Pimentech's libcommonDjango - JSON-RPC for Django. libcommonDjango can be downloadedfrom http://lkcl.net/libcommonDjango.tgz and the Django Framework from http://djangoproject.com

Web.py, a minimalist powerful web framework, can also be used. Download the JSONRPC

example here: http://lkcl.net/webpy.jsonrpc.tgz and the Web.py Framework from http://webpy.org

CGI-based JSONRPC - there is an example already included in the Pyjamas distribution,examples/jsonrpc/public/services, called EchoService.py

txJSONRPC - JSON-RPC for Twisted. txJSONRPC can be downloaded from

https://launchpad.net/txjsonrpc and the Twisted Framework from http://twistedmatrix.com

So it is entirely up to you which one you use: the Pyjamas examples even include a PHP-based

JSONRPC service, using a php library called phpolait.

CGI-based JSONRPC

If you have a suitable web server installed, one of the simplest methods to get started has to be CGI

(Common Gateway Interface). It's also one of the slowest, causing the server to load an entire

application just to serve up one and only one web page - even if the response is a couple of lines, whichwill often be the case on JSONRPC responses.

Ideally, you should ensure that CGI is configured correctly, and working. Here is an example Apache2

configuration for making the jsonrpc example services directory executable:

ScriptAlias /services/ /home/lkcl/src/pyjamas/examples/jsonrpc/output/services/<Directory "home/lkcl/src/pyjamas/examples/jsonrpc/output/services/"> AllowOverride None #AddHandler cgi-script .py Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Order allow,deny Allow from all</Directory>

With the above example, once you have started (or restarted) Apache2, you will be able to open up a

web browser on http://localhost/examples/ to run your application. If successful, you should see the

contents of the test slide in the browser.

You can test the CGI example, after restarting or reloading Apache2, by browsing to

http://localhost/services/EchoService.py. If you are offered an opportunity to download

EchoService.py, then you have not correctly set up cgi-bin execution. If, however, you get presented witha blank screen, then congratulations, the EchoService.py is correctly running: you are ready to connect

to it with a JSONRPC client, which will be covered later.

Once you have CGI correctly configured, examine the jsonrpc/public/services/EchoService.py source

code - it's pretty straightfoward:

#!/usr/bin/env python

class Service: def echo(self, msg): return msg

def reverse(self, msg): return msg[::-1]

def uppercase(self, msg):

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

9 of 61 08-03-2009 18:11

Page 10: Pyjamas the Book

return msg.upper()

def lowercase(self, msg): return msg.lower()

from jsonrpc.cgihandler import handleCGIRequest

handleCGIRequest(Service())

The first line tells a unix-based system that when this script is executed, it must be executed as a python

script. Every individual HTTP Request will result in a new process being started, to handle the Request,

and that process will terminate when the Response is sent to the client.

If running Windows, and Apache2 has been chosen as the web server, adding"AddHandler cgi-script .py" to the Apache2 configuration sectionwill make all .py scripts in that section executable. Only add this tothe sections that you need - don't add this Directive globally!

The class "Service" contains our four functions that this JSONRPC service will support. Any JSONRPC

client that has EchoService.py as its HTTP "POST" url will be able to run these four functions.

The next two lines import and use handleCGIRequest to process the JSONRPC query. If you are

interested, take a look in the public/services/jsonrpc directory, at cgihandler.py - it's prettystraightforward, but it's just a library. The reason why it's mentioned is that you may be wondering where

jsonrpc.cgihandler comes from, when you haven't installed a library called "jsonrpc" on your system: it's

being imported from the local path, by the python application, from the examples/public/services

directory.

That's really all there is to it. The dynamic capabilities of Python allow the jsonrpc cgihandler library to

match up the function call names in the incoming HTTP POST with the function names in the Service()

class instance. Adding extra functions is a trivial matter of adding new methods to the Service class.

JSONRPC in Django

This technique is a little more advanced, and relies on python "decorators", so you will need to ensure

that you at least have Python 2.4 or greater (but not Python 3). First, you should download and install both

Django and libcommonDjango, which are available from http://djangoproject.com and http://lkcl.net/libcommonDjango.tgz respectively.

If you are unfamiliar with Django, then first, the standard Django Tutorial should be followed. By stage

three, enough of a polls/view.py will have been created such that the following lines can be added:

from django.pimentech.network import JSONRPCService, jsonremote

testservice = JSONRPCService()

@jsonremote(testservice)def echo(response, msg): try: poll_id = int(msg) except ValueError: return "You must type an integer, you typed: %s" % msg p = Poll.objects.get(pk=poll_id) return p.question

@jsonremote(testservice)def reverse(response, msg): return msg[::-1]

@jsonremote(testservice)def uppercase(response, msg): return msg.upper()

@jsonremote(testservice)def lowercase(response, msg): return msg.lower()

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

10 of 61 08-03-2009 18:11

Page 11: Pyjamas the Book

As can be seen, the libcommonDjango library from http://pimentech.com is the key. The

@jsonremote() decorator turns the four functions into JSONRPC server functions. Note that, unlike

the CGI-based example, you have access to a response instance variable - just like in any other

Django HTTP Response processing function.

The echo function in this example reads from the Poll table, just to demonstrate how interaction between

JSONRPC and a database works. If preferred, or the Django Tutorial is not being used, simply return

msg instead, to maintain consistency with the CGI-based example.

In Django, response-processing functions and classes don't magically get called: it is necessary to mapthem to incoming HTTP urls using urls.py. Edit the Django application's urls.py file, and add the line noted,

below:

urlpatterns = patterns('', # this is the JSONRPC service line that needs to be added: (r'^test-service/$', 'mysite.polls.views.testservice'),

(r'^admin/(.*)', admin.site.root), (r'^polls/$', 'mysite.polls.views.index'),

As can be seen, each line in urlpatterns maps a url to a class or function, and in this example, if your web

server is accessible at http://localhost then any references to http://localhost/test-service/ will be mapped

to the JSONRPCService instance, testservice, in mysite/polls/views.py.

Make sure that the urlpatterns match the application - in other words,the example above is only valid if application is called "mysite". Also,take note of the URL, above, because it will be needed, later, when it comesto connecting the JSONRPC-aware Pyjamas client to the Django server.

When working with Django, any modifications that you make to the application will be picked up

automatically when running Django in "test" mode (python manage.py 8080). If running the

application using mod_python under Apache2, Apache2 will need to either be restarted or reloaded.

Apache2 is quite happy with a reload, if the application has no errors.However, if an error occurs in the application, the thread will bomb outand "stick". As threads are chosen at random, to respond to requests,what happens is that at some point in the future, the "stuck" thread willbe called on again to respond to a request... and it will respond withthe same error message, regardless of the request and regardlessof the requester. So it is unfortunate but vital that you restartApache2 if an error occurs, when using mod_python.

So, again - there's very little involved in setting up a JSONRPC service under Django. There's actually so

little "hard work" that it's almost a let-down to know that it is possible to do Web Development where the

technology doesn't get in the way.

Web.py JSONRPC Server

Web.py is a very simple and straightforward web framework that is used for some very substantial web

sites. The key significance of Web.py is that it does not need to be run from a Web server, it is a Web

Server.

It's very simple to integrate JSONRPC into web.py. First, download and install web.py fromhttp://webpy.org and also download a JSONRPC library from http://lkcl.net/webpy.jsonrpc.tgz which

contains a modified version of Pimentech's JSONRPC service. The modifications are tiny but significant,

allowing the raw data from the HTTP POST to be passed to the JSONRPC Service.

Start by unpacking the archive, and creating a file with the following content. The file should be saved in

the directory where you have unpacked the archive, so that the network library can be imported by it.

#!/usr/bin/env python

from network import JSONRPCService, jsonremoteimport webimport os

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

11 of 61 08-03-2009 18:11

Page 12: Pyjamas the Book

urls = ( '/chat/', 'chatcls', '/chat', 'chatcls', )

# a "wrapper" class around the jsonrpc service "chatservice"class chatcls: def POST(self): print chatservice(web.webapi.data())

chatservice = JSONRPCService()

# the two demo functions

@jsonremote(chatservice)def echo(request, msg): web.debug(repr(request)) return "hello world %s" % msg

@jsonremote(chatservice)def reverse(request, msg): return "hello world %s" % msg[::-1]

@jsonremote(chatservice)def uppercase(request, msg): return "hello world %s" % msg.upper()

@jsonremote(chatservice)def lowercase(request, msg): return "hello world %s" % msg.lower()

# start me!if __name__ == "__main__": web.run(urls, globals())

Once again, we can see that it's really quite straightforward to set up a JSONRPC Service. In Web.py,the mapping between URLs and the handlers is done in a similar fashion to Django (the urls variable); a

key difference between Django and Web.py is that classes must be used, and the classes must have a

GET (or in this case, a POST) function. In this example, a wrapper class has been used, that links the

URL /chat/ to the JSONRPC service.

The last three lines run the built-in web server, which, by default, will run on port 8000. If the above codeis saved as code.py, it can be started with the command python code.py. Starting the application will

allow the JSONRPC service to be accessed on http://localhost:8000/chat/ with a

JSONRPC-aware client. You will need to remember this URL when it comes to connecting the client withthe web.py server.

Twisted Matrix txJSONRPC

Twisted Matrix is a comprehensive framework for networking and networked applications, written in

Python. There is a JSONRPC library, which can be used to create both clients and servers, calledtxJSONRPC: it is available from https://launchpad.net/txjsonrpc.

First, you should download and install the Twisted Matrix framework, from http://twistedmatrix.com.

Second, you should install txJSONRPC. To create a Twisted Matrix JSONRPC Service, start by creating

a file, to be saved as server.tac, with the following contents:

from twisted.web2 import server, staticfrom twisted.web2.channel import httpfrom twisted.application import service, internet

from txjsonrpc.web2 import jsonrpc

class Example(jsonrpc.JSONRPC): """An example object to be published."""

addSlash = True

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

12 of 61 08-03-2009 18:11

Page 13: Pyjamas the Book

def jsonrpc_echo(self, request, x): """Return all passed args.""" return x

def jsonrpc_add(self, request, a, b): """Return sum of arguments.""" return a + b

root = static.File("/var/www/")root.putChild("doc", static.File("/usr/share/doc"))root.putChild("test", Example())site = server.Site(root)

chan = http.HTTPFactory(site)application = service.Application("Example JSON-RPC Server")jsonrpcServer = internet.TCPServer(7080, chan)jsonrpcServer.setServiceParent(application)

Once saved as server.tac, the above can be run with this command:

twistd -noy server.tac

The example is an adaptation of the server example that can be found on https://wiki.ubuntu.com/txJSON-RPC/TwistedWeb2, and it is instructive to compare the two. As it proved quite challenging to

find easy-to-understand documentation on how to set up Twisted Matrix servers, some time will be spent

explaining the above. The key to a JSONRPC service, here, is in creating a class that derives from

txjsonrpc.web2.jsonrpc.JSONRPC. However, the example on the txJSONRPC Wiki serves pages in away which dominates port 7080:

site = server.Site(Example())chan = http.HTTPFactory(site)application = service.Application("Example JSON-RPC Server")jsonrpcServer = internet.TCPServer(7080, chan)jsonrpcServer.setServiceParent(application)

In this example, taken from the txJSONRPC Wiki, the only thing that can be accessed is the JSONRPC

Service, via the following url: http://localhost:7080/. Clearly, this is of no use, in a real

environment, as it will not be possible to serve anything else, such as the index.html file of the site! Theselines, therefore, are key:

from twisted.web2 import static

root = static.File("/var/www/")root.putChild("doc", static.File("/usr/share/doc"))root.putChild("test", Example())site = server.Site(root)

Static HTML can, in this case, be served from /var/www/, which will be the "root" - accessible as

http://localhost:7080/. The contents of /usr/share/doc will be served from

http://localhost:7080/doc/, and, crucially, our JSONRPC Example service will be accessible

via http://localhost:7080/test/.

In this way, an AJAX application's index.html and other content, such as images, can be placed in/var/www, and the AJAX code in the Pyjamas JSONRPC client code, once downloaded from the Web

Server's root and executed in the user's browser, can be instructed to access /test/.

Once again, it is essential to remember the URI where the JSONRPC service will be accessible, as the

Pyjamas JSONRPC client code must be told where, on the Web Server, that service can be found.Lastly: the Wiki example https://wiki.ubuntu.com/txJSON-RPC/TwistedWeb2 has been copied near

verbatim, and so it is missing the functions reverse, uppercase and lowercase. It is left as a simple

exercise to the reader to add these missing functions.

Conclusion on Python JSONRPC Services

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

13 of 61 08-03-2009 18:11

Page 14: Pyjamas the Book

The list covered here, of available Python Frameworks that can be used to run JSONRPC Services, is

by no means complete, but it should be clear that it's not exactly rocket science. If your preferred

framework does not support JSONRPC (yet), then start by examining the difference between the Djangoand the Web.py ports of pimentech's network library: very little change to the library was required, to

adapt it to Web.py.

Additional resources can easily be found using a google query of "Python JSONRPC Server" and

this brings up two additional options not covered here, as well as some other useful links:

http://www.freenet.org.nz/dojo/pyjson/

http://pypi.python.org/pypi/z3c.jsonrpc/http://json-rpc.org

What has been covered in this section is as follows:

How incredibly simple it is to make a Python JSONRPC Service

How to do CGI-based, Django, Web.py and Twisted Matrix JSONRPC

How to specify, in each case, where the service is to be accessed, by web clients.

It's often the case - bizarrely - that there will be more lines of python code to create the front-end

Pyjamas application than there will be in the Web Server back-end that manages the information in thedatabase! This is a testament to the power of the available Python frameworks as much as it is to the

level of complexity of Desktop-like applications development.

When such amazingly powerful technology is available, exactly how and why the development of

comprehensive web applications are still done using server-side HTML-based technology is somewhat

puzzling.

Testing the JSONRPC services, using Python-based JSONRPC

clients

Before progressing to doing JSONRPC in a browser, it's probably a good idea to double-check that theexample Server of choice actually works. Python-based JSONRPC clients are surprisingly difficult to

find, perhaps because they are so simple to write that people forgot to do it. So here are some links to

the clients that do exist (and are easy to find):

http://developer.spikesource.com/wiki/index.php/Article:Accessing_JSON-RPC_with_Pythonhttps://wiki.ubuntu.com/txJSON-RPC/TwistedWeb2

http://pypi.python.org/pypi/z3c.jsonrpc/

Simple JSONRPC client

First, download a very simple client, written by Matt Harrison. Copies are available here:

http://files.blog-city.com/files/F05/96843/b/jsonrpclib.tar.gzhttp://lkcl.net/jsonrpclib.tgz

Then, create the following file, saving it as jsonrpctest.py:

import jsonrpclib

s = jsonrpclib.ServerProxy("http://localhost:7080/test/")reply = s.echo("foo bar")print reply

As can be seen, it really couldn't get any easier, and the code is good enough to help prove that there's

actually something there.

Remember to adapt the URL to point to the JSONRPC Server that you want to test!The example shows how to test the txJSONRPC server: if you want to test theDjango example, you will need to specify a URL of http://localhost/test-service/or wherever it was that you edited your Django app's urls.py to answer from.

Run the script and test it:

$ python jsonrpc_test.py{u'error': None, u'id': 2, u'result': u'foo bar'}

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

14 of 61 08-03-2009 18:11

Page 15: Pyjamas the Book

Whilst this isn't the most user-friendly of answers, it's definitely the right one, and it's better than this:

NameError: global name 'ProtocolError' is not defined

which is what you will encounter if you used the wrong URL. Double-check that you have set the URL

correctly, in whatever framework you have chosen. In the case of connecting to an Apache2-driven CGI

script, first make sure that the script is executable, by typing the URL directly into your browser, for

example, if you have followed verbatim the advised setup, then the Apache2 Directive that makes

"/services/" part of your Apache2 server's CGI-based infrastructure, you can type

http://localhost/services/EchoService.py into your browser, and you should get a blank

screen (rather than downloading EchoService.py as a static file...)

Once you have confirmed that the cgi-bin script is executed, modify jsonrpc_test.py to make the

argument passed to ServerProxy point to the cgi script:

s = jsonrpclib.ServerProxy("http://localhost/services/EchoService.py")

Again, u'foo bar' should be printed out, thus confirming that everything is hunky-dory.

Simple txJSONRPC client

Create a file, called client.py, with the following contents, remembering to edit it to suit the JSONRPC

service to be tested:

from twisted.internet import reactor

from txjsonrpc.web.jsonrpc import Proxy

def printValue(value): print repr(value) reactor.stop()

def printError(error): print 'error', error reactor.stop()

proxy = Proxy('http://127.0.0.1:7080/test/')proxy.callRemote('add', 3, 5).addCallbacks(printValue, printError)reactor.run()

Again, as with the previous example, note that the URL is set in this instance to point to the txJSONRPC

server.tac Service, and, if testing against Django, for example, the URL should be set to

http://127.0.0.1/test-service/.

When run, using python client.py, the number 8 should be printed. If you would like to test the echo

function, use this, instead:

proxy.callRemote('echo', 'hello').addCallbacks(printValue, printError)

This will print out "hello".

It has to be pointed out that, when compared to the best RPC systems, this example's syntax is pretty

user-hostile - obtuse at best. However, that's not what matters, here: the point is that it can be used toprove that the servers actually work - to provide an independent test mechanism in the future, should it

prove necessary to track down the source of errors in the development of Pyjamas JSONRPC

applications.

Purpose of using independent JSONRPC clients

So there are at least two, possibly even four independent JSONRPC clients available, all written in native

Python, that can be used, and two of them are demonstrated here. The purpose of performing

independent testing is firstly to confirm that the JSONRPC server works, but also, during the

development of your application, errors may be encountered that would otherwise be difficult to trackdown.

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

15 of 61 08-03-2009 18:11

Page 16: Pyjamas the Book

Eliminating one source of errors from the equation is always a good idea. Using a Web browser, which

will be difficult to control and perform automated test validations with, is not something that you should

consider. Whereas, although obtuse, the use of the above python clients will allow you at least to runautomated tests on your JSONRPC service from the command-line.

Pyjamas JSONRPCExample

Finally - after all that fussing around - there's a service (at least one) against which we can demonstrate

that the Pyjamas jsonrpc example actually works. Perhaps importantly, it stands a chance of working

against the Web Framework of choice. If the php JSONRPC example is sufficient, download, install andconfigure php5, and configure Apache2 or other Web Server to use it. There is plenty of information on

how to set up Apache2 and php5, by searching online for "LAMP HOWTO" advice.

First, compile the jsonrpc pyjamas example, by changing directory to examples/jsonrpc/ and either

running build.sh or executing the following command:

python ../../builder/build.py JSONRPCExample.py

Then, point a browser at http://localhost/examples/jsonrpc/output

/JSONRPCExample.html and you should have some instructions to follow, a text box, a drop-down

and two buttons. If you have correctly configured php, then clicking on the "Send to PHP Service" button

should result in the words {'Test'} [\"String\"] appearing below the button. If not, an obtuse

error message will be displayed.

If you get an obtuse error message, then it is with very little regret,and quite a bit of glee, to mention that the author cares not one bitabout PHP, and is not inclined to spend time encouraging readers, whoare likely to be Python Programmers, to use it. So, if an obtuseerror message is encountered, great!. Ignore it, and move on...

Once the php test is confirmed as working, or if php causes much boredom or irritation beyond belief, it'sworthwhile moving swiftly on to testing the python Echo Service. For this to work, the

JSONRPCExample.py must be pointing to the correct URL, as has been emphasised in the sections on

setting up a JSONRPC Service. Edit the file JSONRPCExample.py in the examples/jsonrpc/ directory,and change the last few lines:

class EchoServicePHP(JSONProxy): def __init__(self): # ignoring the fact that it says PHP, not Twisted Matrix, # this example works against the txJSONRPC Example server: JSONProxy.__init__(self, "/test/", ["echo", "reverse", "uppercase", "lowercase"])

class EchoServicePython(JSONProxy): def __init__(self): # this example works against the Django JSONRPC Example: JSONProxy.__init__(self, "/test-service/", ["echo", "reverse", "uppercase", "lowercase"])

Remember to recompile, after saving, using build.sh. As can be seen, the critical key is the

JSONProxy's first parameter (yes, it's the first parameter - ignoring "self") which has to be changed tomatch the location where the JSONRPC service will answer function calls from. However - there's a

catch, which needs explaining, and the reason why the txJSONRPC Server example was modified will

become clear.

Getting Pyjamas AJAX clients and txJSONRPC Services "lined up"

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

16 of 61 08-03-2009 18:11

Page 17: Pyjamas the Book

Recall that AJAX is restricted, for security reasons, to allowing access only to the same server, as

described at the beginning of this chapter. The implications are that the Web Framework must be

configured to serve both the AJAX application's HTML and Javascript from the same root URL that the

JSONRPC server - and all other AJAX responses - come from.

This is the reason why this was done, in the txJSONRPC example:

root = static.File("/var/www/")root.putChild("doc", static.File("/usr/share/doc"))root.putChild("test", Example())site = server.Site(root)

In this instance, the compiled output would have to be moved into either /var/www/ or into

/usr/share/doc/, neither of which are really acceptable, so it is better to modify the server.tac

example to this:

root = static.File("home/lkcl/src/pyjamas/examples/jsonrpc/output/")root.putChild("test", Example())site = server.Site(root)

Remember to substitute the appropriate location where the jsonrpc compiled output can be accessed. Bymaking the static files be the root of the server.tac example, the compiled output of the

JSONRPCExample can be accessed by browsing to http://localhost:7080/. And, as can be

seen, there is also a /test/ URL which points to the Example txJSONRPC Service that now matches

up with the modifications made in the Pyjamas client source code.

Getting Pyjamas AJAX clients and Django JSONRPC Services "lined up"

In the Django case, it's probably best to not bother to modify the Django example app to load static HTML

pages, but to go straight to running Django as a mod_python Apache2 module. Install and configuremod_python, if it isn't already included as part of the standard Apache2 distribution, and add the following

lines to the configuration:

<Location "/test-service/"> SetHandler python-program PythonHandler django.core.handlers.modpython # change mysite to point to the correct Django app settings SetEnv DJANGO_SETTINGS_MODULE mysite.settings # change the path to point to the directory with mysite in it. PythonPath "['/home/lkcl/src/django_pyjamas/'] + sys.path" PythonDebug On</Location>

Edit the directory of the PythonPath as appropriate, to point to the directory where the Django applicationwas created. Restart (or better, just reload) Apache2, and this is all that should be needed, in

combination with prior edits to the Apache2 configuration (the /examples/ Directive), to get the

JSONRPCExample to work.

Do not add the path of the Django application itself to the PythonPath!If the Django application is called "mysite", do not add "/path/src/mysite",just add "/path/src". Also, remember to modify the DJANGO_SETTINGS_MODULEto point to "mysite" only if the Django application is called "mysite",and to modify it, otherwise.

When using mod_python, it's important to remember that any errors in the Django application will cause

the Apache2 thread that served the page to "stick", and Apache2 will need to be restarted, to get rid of it.

Also, when using mod_python, running the built-in Django Test server (python ./manage.py

runserver 8000) is no longer necessary.

Pyjamas AJAX clients talking to Web.py

Whilst web.py automatically supports access to static content in subdirectories, if slightly more strict

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

17 of 61 08-03-2009 18:11

Page 18: Pyjamas the Book

control over the content is required, the following class can be used. It's based on the Images class that

can be found at http://webpy.org/images, with the additional mime types of HTML, Javascript, Text and

CSS being added. Add this to the code.py example, created earlier:

urls = ( '/chat/', 'chatcls', '/chat', 'chatcls', '/(.*)', 'static' # static content )

class static: def GET(self, name):

if name == '': name = 'JSONRPCExample.html'

ext = name.split(".")[-1]

cType = { "js":"application/x-javascript", "txt":"text/plain", "css":"text/css", "html":"text/html", "png":"images/png", "jpg":"image/jpeg", "gif":"image/gif", "ico":"image/x-icon" }

static_location = '/home/lkcl/src/pyjamas/examples/jsonrpc/output' if name in os.listdir(static_location): web.header("Content-Type", cType[ext]) print open('%s/%s' % (static_location, name), "rb").read() else: web.notfound()

Note that the "static" class has been added to urls, right at the top, to deal with all URLs (except/chat/). Also, it will be necessary to modify the location from where the static content is loaded from.

Also, for convenience, if no name is given, the content from JSONRPCExample.html is served. This

allows users to type in http://localhost:8080 once the Web.py application is running, instead of

http://localhost:8080/JSONRPCExample.html.

Also, either modify the urls to match up with the existing locations that the Pyjamas JSONRPCService.py

has been modified to support (/test/ or /test-service/), or edit JSONRPCService.py and

change the URI that JSONProxy talks to:

class EchoServicePython(JSONProxy): def __init__(self): # this example works against the Web.py Example: JSONProxy.__init__(self, "/chat/", ["echo", "reverse", "uppercase", "lowercase"])

Remember to save and recompile the Pyjamas application. Also, once the modifications to code.pyabove have been saved, re-run the Web.py app, with the command python code.py, and open

http://localhost:8080 in a Web browser.

What's been covered

We've gone over how to use AJAX, in both static (straight loading of text files) and dynamic (using

JSONRPC) fashions. The dynamic examples show how to use four different kinds of Web frameworks,all of which are very similar to one another. We've shown how to use python-based JSONRPC clients,

which can be used as the basis of test frameworks for JSONRPC servers.

In each case, with each of the Web Frameworks, the following has been emphasised and covered:

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

18 of 61 08-03-2009 18:11

Page 19: Pyjamas the Book

The importance of matching up the URL in the Pyjamas application's JSONRPC Proxy with the

location where the chosen Framework serves its JSONRPC Service from.

The importance of pandering to the restrictions on AJAX, for security reasonsThe ways in which the Framework serves the static HTML and Javascript content from one

location and the JSONRPC Service responses from another, but they must both come from the

same Root URL (due to AJAX security restrictions).

It's important to get this bit right, and well-understood, as it's the fundamental basis of the interaction

between Pyjamas applications and the rest of the world - the Web Server.

Merging Javascript into Python Pyjamas applications

Pyjamas is compiler technology: it converts python source code into javascript source code. In a way,

pyjs.py - the compiler - is very much like gcc, the Gnu C Compiler. gcc turns perfectly good (or bad) c

code into a human-unreadable mess, using an intermediate step: assembler. The Pyjamas compiler

effectively turns python source code into "assembly-like" javascript. Another way to put this is that it is

pure coincidence (it's not, really!) that the "assembly output" of pyjamas happens to be reasonably-

human-readable javascript.

There is quite a lot of javascript functionality which needs to be accessible by Pyjamas applications, in

order for Pyjamas to be useful (especially in Web Browsers), and so the design of the Pyjamas compilerhas had to include a way to understand javascript libraries, and to be able to include javascript code

fragments into the output, unmodified. The reasons are simple: not only would it be too much to expect

developers to rewrite perfectly good javascript libraries in python, but also there are built-in functions - inboth the Javascript language and in Web browsers - that simply cannot be rewritten. A way to interact

with javascript is therefore essential.

Being able to insert javascript fragments is conceptually identical to the gcc method of doing "inline

assembler". In gcc, you do horrible things like this:

int a=10, b; asm ("movl %1, %%eax; movl %%eax, %0;" :"=r"(b) /* output */ :"r"(a) /* input */ :"%eax" /* clobbered register */ );

And, in Pyjamas, it's not that different:

def foo(b): # this is python-land (to be translated to javascript) a = 10; # this is still python-land (to be translated to javascript) JS(""" b = a; /* this is javascript-land (and will be outputted, verbatim) */ """)

So, crucially, this chapter will cover what it is that needs careful attention, when wrapping a javascript

library in Python code; how Python functions should be called from inside wrapped javascript (and the

problems associated with doing that); and the importance of double-checking the compiled output against

the original Python code, to make sure that everything's reasonable.

At this point, it must be said that this is not for the faint-hearted: assembly-style programming never is.

Not only is it necessary to know two programming languages; not only is it necessary to know how the

compiler interfaces the two together, but also, it's necessary to work at "second hand" with one of the

languages, when debugging the application! That having been said, it is quite straightforward.

Simple Rectangle Test Class

The purpose of this exercise is to show, using as small a useful javascript library as possible, how to

wrap Javascript with Pyjamas. Start by saving the following javascript code as a file called jsrecttest.js as

the javascript "library":

function rectobj() {

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

19 of 61 08-03-2009 18:11

Page 20: Pyjamas the Book

}

function rect_init(x, y) { this.x = x; this.y = y;}

function rect_area() { return this.x * this.y;}

function rect_add(rect) { this.x += rect.x; this.y += rect.y;}

rectobj.prototype.area = rect_area;rectobj.prototype.add = rect_add;rectobj.prototype.init = rect_init;

If javascript is unfamiliar, then right now take a minute to look up prototyping, and how javascript objects

work. An object instance can be created in javascript, like this:

var obj = new rectobj();

It's quite obtuse, but easy to follow: the function rectobj() is being called, but the qualifier "new" tells

javascript to actually make an object (using the function rectobj), and thus, any occurrences of the word

"this" can be used in the function - just like "self" in python and "this" in c++ - to refer to member

variables, functions and objects. So, the function rect_init() adds two variables to a rectobj;

rect_area can return those two variables, multiplied together.

Crucially, however, the last three lines are what makes javascript incredibly powerful. Notice the use of

"prototype" - that's telling the javascript engine that all future declarations of "new" rectobjs must have

those three functions added to them.

Exactly the same thing can be done in Python - it's just that it's not good sane practice to modify Python

classes on-the-fly, as anyone reading the Python source code is going to get a real headache,

wondering where on earth the extra functions are coming from. However, javascript is a pure prototyping

language; there are no "classes"; there is only prototype. Given that there isn't any choice in the matter

(javascript programmers being used to getting headaches), everybody's happy.

It's worth noting that the reason why Python and Javascript work so welltogether is because of the dynamic run-time similarity - this ability tocreate classes in Python, by modifying the object, and adding functionsat runtime, being so similar to javascript's prototype capabilities.By contrast, The PyPy team (PyPy is an E.U-funded compiler project)are having a very difficult time with the Java Bytecode back-end,because Java, as a language, is simply not equipped with suchdynamism as Python and Javascript. Consequently, Java Bytecode -and the JVM it runs on - does not react favourably to such maltreatment.

Slight Detour, illustrating python-javascript equivalence

Whilst it would be unkind to create an exact replica of what's going on with jsrecttest.js, an

"equivalent" program in Python is here, for illustrative purposes only:

class rectobj: def __init__(self, x, y): self.x = x self.y = y def area(self): return self.x * self.y def add(self, rect): self.x += rect.x self.y += rect.y

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

20 of 61 08-03-2009 18:11

Page 21: Pyjamas the Book

The reason why this isn't exactly the same is because, in jsrecttest.js the javascript "class" (not

that there is such a concept of classes in javascript) is being created dynamically. So it's more like this:

class rectobj: passdef rect_area(self): return self.x * self.ydef rect_add(self, rect): self.x += rect.x self.y += rect.ydef rect_init(self, x, y): self.x = x self.y = yrectobj.__init__ = rect_initrectobj.add = rect_addrectobj.area = rect_area

Python purists should either be screaming or quaking in their boots at the incorrectness of this approach.The particularly well-informed ones will likely scream loudest about the lack of decorators. All of which is

irrelevant, because only the most insane or the most confident of Python programmers would dare do

anything like this. But - it's here for illustrative purposes.

Consider compiling the above examples to javascript, using this command: python pyjs.py horribletest.pyCompare the resultant output with the original jsrecttest.js, to geta feel for how pyjs.py works and also to gain confidence in itscapabilities. If the output from pyjs.py looks close to what wouldbe written in "pure" javascript, it makes life a lot easier when itcomes to understanding and writing Python-Javascript hybrids.

Wrapping javascript, inline, with JS()

Moving swiftly on, create a file, TestRect.py, with the following contents:

import jsrecttest.js # YUK!!!

class Rect: def __init__(self, x, y): JS(""" this.rect = new rectobj(); this.rect.init(x, y); """)

def add(self, r): JS(""" this.rect.add(r.rect); """) def area(self): JS(""" return this.rect.area(); """)

def get_x(self): JS(""" return this.rect.x; """)

def get_y(self): JS(""" return this.rect.y; """)

Straight away, notice two critical things: firstly, importing jsrecttest.js with an extension ".js" is definitely

not standard python - but then again, the whole concept of compiling python into javascript throws

spanners in the traditional works, anyway. Secondly: in this strange-looking Python class, whilst everyfunction uses "self", the javascript snippets use "this".

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

21 of 61 08-03-2009 18:11

Page 22: Pyjamas the Book

"self" in python is just a convention. If it feels more comfortable,substitute all occurences of the word "self" with the word "this" -consistently, in a python class member function - and the feeling ofpanic and unease should subside, only to be replaced by a different formof unease at breaking a python convention: the disappearance of the word"self" from the python code...

The inclusion of jsrecttest.js as if it was a python module is definitely non-pythonic, but due to the way

that pyjamas code is generated, and to ensure that there are no surprises, there really isn't much choice.

Browsers have a habit of executing code when it is loaded - but only when the code is included from the

initial web page. If javascript is used, to modify the URL of the browser page, to load up a second HTML

page, things start to go horribly wrong. So, the only way to guarantee that no limitations are imposed is to

actually include the script wholesale and inline, in the Pyjamas compiled output!

Examining the class Rect, it can be seen that the initialisation appears - somehow - to magically go into

javascript-land and create a rectobj() as a member variable, and that the rectobj instance gets

initialised with the same x and y parameters that the Python Rect class gets initialised with! It's the same

story, throughout all of the functions, and at this point, it's a very very good idea to run the pyjs.pycompiler, to find out what's going on. Run the following command (assuming that you saved the files in a

subdirectory of examples, or are executing the command from the pyjamas/examples/jsobject directory):

python ../../pyjs/pyjs.py TestRect.py

The output from pyjs.py is printed on-screen, and, an abbreviated version is shown, here:

function __Rect() {}function Rect(x, y) { var instance = new __Rect(); if(instance.__init__) instance.__init__.apply(instance, arguments); return instance;}

function __Rect_initialize() { if(__Rect.__was_initialized__) return; __Rect.__was_initialized__ = true; pyjs_extend(__Rect, __pyjslib_Object); __Rect.prototype.__class__.__new__ = Rect; __Rect.prototype.__init__ = function(x, y) {

this.rect = new rectobj(); this.rect.init(x, y);

}; __Rect.prototype.get_x = function() {

return this.rect.x;

};

/* ... functions cut, for clarity and brevity, are area, get_y and add. as well as a boat-load of non-intuitive crud. ... */}__Rect_initialize();

To explain the morass of extra code: it's to create some semblance and imitation of pythonic classes, in

a language which doesn't have classes. Note in particular the use of the function pyjs_extend, which

emulates class inheritance; the technique is prevalent in javascript libraries, to "extend" the functionality

of an object by copying its methods into the child "class".

One thing that deserves particular attention is this line:

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

22 of 61 08-03-2009 18:11

Page 23: Pyjamas the Book

__Rect.prototype.__init__ = function(x, y) {

This line sets up a "class" initialiser function, called __init__, which has two parameters, x and y. Just like

in c++, the concept of "this" is implicit in javascript: you always unavoidably get one, and so it need not be

passed around as a parameter. Note also that the contents of the __init__ function are - literally

verbatim - what was placed inside the JS(""" """) inlining wrapper.

Regarding the remaining functions, and to illustrate in a clearer manner the relationship between Python

and its resultant javascript output when compiled, change the get_x() function, as follows:

def get_x(self): return self.rect.x

Re-run pyjs,py and look for the following lines, in the output:

__Rect.prototype.get_x = function() { return this.rect.x; };

That's... that's the same, isn't it? Yes and no: there's more white-space in the previous example, and the

reason for that is because, inside the original JS(""" return this.rect.x; """) block there was... a lot of

white-space, all of which was substituted, verbatim, into the compiled output (go back to the previous

code, and either remove all white-space, add in javascript-style comments, or remove the indentation,

and re-run pyjs.py, to help emphasise this point).

Note! Javascript separates statements with semi-colons!Python does actually support semi-colons to separate statements,but typical well-written python applications don't try to cramvast numbers of statements onto one line, so the semi-colons areunnecessary. semi-colons are usually used in python line-crammingcompetitions, such as writing a fully-functional wiki server in11 lines of code...

In other words, what's happened is that the auto-generated code happens to be identical to the code that

we inlined. pyjs.py happens to "understand" how to turn python "self" into javascript "this", and the syntax

of javascript happens to be similar to python, in this simple example. So, purely for the purposes of this

exercise, unnecessary work was done by putting "inline" the very javascript that Pyjamas would have

generated, if the compiler was trusted to do its job. Try the same thing with the area() function:

def area(self): return self.rect.area()

Re-run pyjs.py TestRect.py, and, in the output, you will see this:

__Rect.prototype.area = function() { return this.rect.area(); };

Gosh. again, that's identical to the javascript substituted inline. how funny.

Running the example in a Web Browser

Whilst it's left to the reader as an experimental exercise to adapt and run this code in, for example,

Mozilla's spidermonkey javascript interpreter, it's quicker to test this code from inside a web browser.

Add the following lines to TestRect.py:

from pyjamas.ui import RootPanel, TextBox, HTML, Button

class TestRect:

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

23 of 61 08-03-2009 18:11

Page 24: Pyjamas the Book

def onModuleLoad(self):

self.r = Rect(0.0, 0.0)

self.xbox = TextBox() self.ybox = TextBox() self.addbutton = Button("Click to add x and y to Rectangle") self.addbutton.addClickListener(self)

self.xbox.setText("2") self.ybox.setText("5")

RootPanel().add(HTML("X Value:")) RootPanel().add(self.xbox) RootPanel().add(HTML("Y Value:")) RootPanel().add(self.ybox) RootPanel().add(self.addbutton)

RootPanel().add(HTML("Current value: %d %d" % ( self.r.get_x(), self.r.get_y(

def onClick(self, sender):

x = int(self.xbox.getText()) y = int(self.ybox.getText())

r = Rect(x, y)

self.r.add(r)

RootPanel().add(HTML("New value: %d %d" % ( self.r.get_x(), self.r.get_y()))) RootPanel().add(HTML("New Area: %d" % self.r.area()))

The application isn't designed to be pretty, it's designed to be functional. Create a file,

public/TestRect.html, with the following contents:

<html> <head> <meta name="pygwt:module" content="TestRect"> <title>Test Rectangle Javascript Object</title> </head> <body bgcolor="white"> <script language="javascript" src="pygwt.js"></script> </body></html>

Compile the application, as follows:

python ../../builder/build.py TestRect.py

Browse to the examples/jsobject/output/ directory (or wherever the application is being built) and click onoutput/TestRect.html

If using Pyjamas 0.3, the sprintf routine is limited to one % modifierper specifier string. So, whilst "New Area: %d" % 20 is acceptable,"New value: %d %d" % (5, 4) is not. Adapt the application as follows: RootPanel().add(HTML("New X value: %d" % self.r.get_x())) RootPanel().add(HTML("New Y value: %d" % self.r.get_y()))and likewise for the display of "Current value". Alternatively,upgrade to a new version of Pyjamas.

As can be seen, the application sets up the Button to call an onClick function, which reads the

text-input boxes, creates a Rect() and adds it to the instance self.r. self.r, another instance of Rect(),accumulates values added; the new x and y values are printed out, as is the area of the rectangle.

Remember, also: look at the output - for example TestRect.Mozilla.cache.html - and look for both the

rectobj and the use of the Rect object, comparing it to the original javascript and the original Python code.

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

24 of 61 08-03-2009 18:11

Page 25: Pyjamas the Book

Recap

What's been illustrated in this simple example is as follows:

How to import javascript as if it was python (import jsfile.js)

How to use the inline-assembly-like function JS() to put fragments of javascript directly into the

compiler output, unmodified.

How to call javascript functions and access javascript objects as if they were Python - and howsimple it is.

The importance of examining the compiler's output, counting blessings that javascript is human-

readable and that pyjs.py generates human-readable output

The fundamental similarity between python and javascript - their ability to modify objects at run-time- which makes the languages a much better match (than java, for example).

So, whilst it's necessary to have an understanding of both Javascript and Python, as well as how the

Pyjamas compiler works, the key to successfully wrapping javascript is definitely to keep an eye on the

output from the compiler, at every step of the way. One thing not covered, so far, however, is how to call

Python functions from inside pure javascript. Correction: how to make it look like python functions are

being called, given that the output is entirely javascript, in the end...

Calling Python from Javascript

It's a lie! What's actually needed is to understand how the Pyjamas compiler translates python functions,

especially those in classes and modules, into javascript. Understanding the translation is important,

because when no longer relying on pyjs.py to do the job automatically, it's clearly necessary to write,

inside the JS() block, exactly the same code that would have been generated.

So it's not like python is actually being called, from javascript - it just looks that way.

If the thought of how not in Kansas this all is, take a little timefor a Zen three-heels-clicking moment until the dizziness passes.

Once calm and zen-like peace has been achieved, create a file called jsdicttest.js, as follows:

function dictobj() {}

function dict_init(d) { this.d = d;}

function dict_get_value(key) { return this.d.__getitem__(key)}

dictobj.prototype.get_value = dict_get_value;dictobj.prototype.init = dict_init;

Next, create a file called TestDict.py with the following contents:

import jsdicttest.js # YUK!!!

class WrapperDict: def __init__(self): d = {'hello': 'world', 'goodbye': 2} JS(""" this.dict = new dictobj(); this.dict.init(d); """)

def python_get_value(self, key): return self.dict.d[key]

def javascript_get_value(self, key):

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

25 of 61 08-03-2009 18:11

Page 26: Pyjamas the Book

JS(""" return this.dict.get_value(key); """)

Notice that the jsdicttest.js file is being imported, verbatim, in that all-important and completely

non-pythonic fashion; notice also that, in the class initialisation, the local variable, d, is being passed in tothe inline-assembler-like JS() function.

Run pyjs.py, as follows, to compile the code to javascript

python ../../pyjs/pyjs.py TestDict.py

Examine the output. and look for this particular section:

__WrapperDict.prototype.__init__ = function() { var d = new pyjslib_Dict([['hello', 'world'], ['goodbye', 2]]);

this.dict = new dictobj(); this.dict.init(d);

};

Comparing this carefully against the original javascript, notice how the dictionary has been declared, and

how the input data has been turned into a javascript array. pyjslib_Dict is the name of the function

that is created when the Dict class, in the pyjamas library called "pyjslib.py", is compiled to javascript.

When you run build.py, the location of the Pyjamas library is automaticallyadded to the module import path, for convenience of building Pyjamas Webapplications. Take a moment to look at builder/build.py's source code,looking for the word "library". build.py uses pyjs.py but does quite abit more work - specifically - in preparing a web application - whereaspyjs.py is definitely just a straight compiler.

Understanding how pyjs.py treats builtins such as Dict, List, Tuple and String is crucial, if wrapping

javascript, as is understanding how modules are imported. Take a look at library/pyjslib.py, looking for theclass Dict. In particular, it may be illustrative to compare the "builtin" pyjamas implementation of Dict to

UserDict.py which comes as standard as part of the Python 2 distribution. Note that, apart from

getObject(), which has been added for convenience, the function names in pyjslib.py's Dict class,

and those in UserDict.py, are identical.

The implementation of Dict, in pyjslib.py, however, can be seen to be using standard javascript arrays to

"emulate" the expected functionality and behaviour that Python programmers would expect a Dict object

to have. On the basis that it's unreasonable to expect python programmers to have to"get involved" with

javascript builtin objects, pyjslib.py takes care of the differences, making javascript builtin objects look likePython builtins. So, looking further at the output from compiling TestDict.py, the following code fragment

should come as no surprise:

__WrapperDict.prototype.python_get_value = function(key) { return this.dict.d.__getitem__(key); };

Accessing the dictionary's items, by key, in the python_get_value() function in the original python code,

TestDict.py, has been compiled into a call to __getitem__. This is exactly what happens in standard

Python, as can be clearly seen with a simple test, using the UserDict.py module that comes with the

standard python distribution, so it should come as no surprise that Pyjamas is emulating this behaviour,

for convenience.

Illustrating how to "call" Python functions from javascript

Take the line which was generated by the compiler, above, in declaring the variable d in the WrapperDict

constructor, and adapt it to make changes to jstestdict.js, as follows:

function dict_init(d) {

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

26 of 61 08-03-2009 18:11

Page 27: Pyjamas the Book

var u = new pyjslib_Dict([['goodbye', 'cruel world']]); this.d = d; d.update(u);}

What is expected to happen, here, is that the value referenced by the key 'goodbye' will overwritten, from

its expected value, before - the number '2' - with the string 'cruel world'. However, it was done inside the

javascript, by manually declaring a built-in Dict object, using the auto-generated pyjslib_Dict()

function.

Take special note of this "trick". If looking to achieve something ina Javascript snippet, first write the code that you want, in Python.Then, run the Python code through pyjs.py (or build.py, as appropriate),and literally cut-and-paste - and adapt - the resultant output.There's no point in making life difficult, when there is an automatedtool that accurately does the job, already.

To illustrate this, again, add another function to WrapperDict, which gets the length of the dictionary:

def python_dict_length(self): return len(self.dict)

Again, len() is a built-in, that is implemented in pyjslib.py, and so it should come as no surprise to find,

when compiling TestDict.py by running pyjs.py TestDict.py, that the output contains this:

__WrapperDict.prototype.python_dict_length = function() { return pyjslib_len(this.dict); };

So, again, it has been illustrated that if it is required to find the length of a pyjslib.py-implemented Python

Dict object from inside Javascript, pyjslib_len must be used. All that's happening here is that the

name of the module is separated from the function name by an underscore. This same trick applies to

absolutely any python modules that are imported into an application. It's just that, for recognised built-in

functions such as len, map, filter and getattr, pyjs.py automatically recognises those and prepends themodule name "pyjslib". Any other functions, including not only in the modules that come with Pyjamas but

also developer-written code, must be imported as would normally be done, in Python, and the javascript

generated will be of the form "importedmodulename_functionname" or

"importedmodulename_classname".

Warning! This trick - of separating the module name from the function name -is something that was chosen for the convenience of the Pyjamas compiler,in order to emulate in javascript, as best as possible, the class andvariable scope rules of python. At some point, a better mechanism may bedeployed, and, as a result, it's possible that all use of this trick maybe utterly broken - at least, definitely version-dependent.In fact, in the llpamies branch of Pyjamas, it already is broken!The module-function calling conventions in the llpamies branch havebeen completely changed, but the work was an experiment that has notyet been completed.

Running TestDict in a Browser

To run TestDict in a Web browser, add the following lines to TestDict.py:

from pyjamas.ui import RootPanel, TextBox, HTML, Button

class TestDict:

def onModuleLoad(self):

self.r = WrapperDict()

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

27 of 61 08-03-2009 18:11

Page 28: Pyjamas the Book

self.kbox = TextBox() self.addbutton = Button("Click to look up key value (hello or goodbye)") self.addbutton.addClickListener(self)

self.kbox.setText("hello") # default to make life easier

RootPanel().add(HTML("Key:")) RootPanel().add(self.kbox) RootPanel().add(self.addbutton)

def display_value(self):

key = self.kbox.getText()

RootPanel().add(HTML("Value using python:" )) RootPanel().add(HTML(self.r.python_get_value(key))) RootPanel().add(HTML("Value using javascript:" )) RootPanel().add(HTML(self.r.javascript_get_value(key)))

def onClick(self, sender):

self.display_value()

Next, create a file, public/TestDict.html, with the following contents:

<html> <head> <meta name="pygwt:module" content="TestDict"> <title>Test Dictionary Javascript Object</title> </head> <body bgcolor="white"> <script language="javascript" src="pygwt.js"></script> </body></html>

After saving the modifications to TestDict.py, and saving public/TestDict.html, compile the application to

javascript, using build.py:

python ../../builder/build.py TestDict.py

Browse to the examples/jsobject/output/ directory (or wherever the application is being built) and click on

output/TestDict.html. The application will, when the button is pressed, look up the key "hello" and should

output "world". If the value in the input box is changed to "goodbye", then, in the first example, the value

"2" is outputted - from both the python_get_value() and the javascript_get_value() functions. However, ifthe latter part of the exercise was followed, by calling update() from inside jstestdict.js, then the resultant

output will instead be "cruel world".

Again, just as with the TestRect.py example, remember to look at the compiled output,

TestDict.Mozilla.cache.html for example, looking for WrapperDict and its use, and looking for dictobj, andhow that's used.

If an error appears, "WrapperDict is undefined" in the Javascript console ofthe browser, check that the contents of TestDict.py haven't been overwritten:"import jstestdict.js", and the declaration of the WrapperDict class, stillneed to be included in TestDict.py!

What's been covered

This section, on pretending to call Python from inside Javascript, covered the following:

How it's a lie that python is being "called" from javascript, and how it's actually the other way round:

that the python is being translated so that the inline javascript, in JS(), can be made to match up

with the compiled python code.How to "trick"-compile code into inline javascript, by writing the code first in Python, compiling it to

javascript and then copying that into the JS() block, to save time and effort.

How a class or function in a Python module is turned into a javascript function by the compiler

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

28 of 61 08-03-2009 18:11

Page 29: Pyjamas the Book

(separating the two with an underscore is the current convention, as of Pyjamas 0.3).

How the builtin classes and functions automatically have the module name "pyjslib_" prepended, in

the compiled javascript.Where to look for pyjslib.py (in the libraries/ directory);

How pyjamas implements Python built-ins, by wrapping and mapping to javascript built-in classes

and functions;

The importance - once again - of carefully examining the compiled output from pyjs.py and build.py,to assist in the development of javascript wrappers.

Remembering that python doesn't typically use semi-colons (although it's supported) to separate

statements but that in javascript, the use of semi-colons is essential.

Conclusion

This quite comprehensive chapter emphasises that, whilst it's necessary to not only know both Python

and Javascript but also to know how the pyjs.py compiler turns python into javascript, it's not rocket

science to integrate javascript into Pyjamas applications. The same type of techniques and care that arerequired, when writing inline assembler in a c or c++ program for example, are also required, here.

Yet, compared to writing inline assembler in c or c++, the task of integrating javascript inline into Pyjamas

applications is definitely a lot easier. Not least in making the task easier is the fact that javascript itself is

a high-level language (albeit an obtuse but very well-understood one), and asm programming is definitelya black art.

User-Interface Applications

The ui module is the most comprehensive of the Pyjamas libraries and, in many ways, is the raison d'etre

for the entire Pyjamas project. The ui module's class structure and API is near-identical to the Google

Web Toolkit ui module, and is also incredibly similar to Python-Qt4 and Python-Gtk2. As such, pyjamas.uiis almost minimalist in what it provides, but is definitely comprehensive enough to build decent and

functional applications.

Covered in this reference chapter will be each of the main classes in pyjamas.ui, which, for convenience,

are broken down into two sections: Panels and Widgets. Each of the two sections' classes are listed in

alphabetical order. Each class description contains the following:

A short description of each classA source code example

Where one is needed, a CSS stylesheet is given

The key functions are described which make each class useful

The chapter covers existing widgets in the pyjamas.ui library. A separate chapter covers how to write

widgets from scratch.

Panels

A Panel is a name given to a container, which other widgets and panels can be added to. For example,

the SimplePanel can only have one widget added to it (which isn't as daft as it sounds: some widgets

have different interaction rules with other widgets that can be mitigated by placing the widget in a"wrapper" panel). Other more complex panels control the layout and placement of their child widgets in

interesting and wonderful ways.

The set of available Panel widgets is quite basic, yet functional. The widgets are most definitely not as

pretty or as comprehensive as, for example, those that are available at http://extjs.com, but certainly theui Panel widgets are the "building blocks" - the foundation on which such lovelier widgets could be built.

ui.AbsolutePanel

ui.AbsolutePanel is a panel that positions its children using absolute pixel positions. This allows the

panel's children to overlap.

Note that the AbsolutePanel does not automatically resize itself to fit its children. There is nostraightforward way of doing this unless all the children are explicitly sized; the easiest workaround is just

to call panel.setWidth(width) and panel.setHeight(height) explicitly after adding the children (or specifying

the width and height in a CSS stylesheet). Choose an appropriate width and height, based on the sizes

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

29 of 61 08-03-2009 18:11

Page 30: Pyjamas the Book

and locations of children that are added to the AbsolutePanel.

Source Code:

from pyjamas.ui import SimplePanel, AbsolutePanel, VerticalPanel, HTMLfrom pyjamas import DOM

class AbsolutePanelDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

panel = AbsolutePanel()

panel.add(self.makeBox("Child 1"), 20, 10) panel.add(self.makeBox("Child 2"), 30, 30)

panel.setWidth("100%") panel.setHeight("100px")

self.add(panel)

def makeBox(self, label): wrapper = VerticalPanel() wrapper.setBorderWidth(1) wrapper.add(HTML(label)) DOM.setIntAttribute(wrapper.getTable(), "cellPadding", 10) DOM.setAttribute(wrapper.getTable(), "bgColor", "#C3D9FF")

return wrapper

ui.DialogBox

The ui.DialogBox class implements a panel that behaves like a dialog box.

A dialog box has an optional caption, and a widget which is displayed as the main part of the dialog box.

The user can drag the dialog box around by clicking on the caption.

The DialogBox class makes use of stylesheet definitions; if these are not supplied, the dialog box will

look very strange. The following stylesheet definitions are used by the example shown below:

.gwt-DialogBox { border: 2px outset; background-color: white; }

.gwt-DialogBox .Caption { background-color: #C3D9FF; padding: 3px; margin: 2px; font-weight: bold; cursor: default; }

.gwt-DialogBox .Contents { padding: 10px; }

As the DialogBox class is derived from PopupPanel, the user should be able to click outside the dialogbox to close it. However, because of a problem with Firefox 3, this does not work. To get around this, the

example shown below implements a "Close" button the user can click on.

Source Code:

from pyjamas.ui import SimplePanel, DialogBox, VerticalPanel, HTML, Buttonfrom pyjamas import Window

class DialogBoxDemo(SimplePanel):

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

30 of 61 08-03-2009 18:11

Page 31: Pyjamas the Book

def __init__(self): SimplePanel.__init__(self)

self.add(Button("Show Dialog", getattr(self, "showDialog")))

def showDialog(self): contents = VerticalPanel() contents.setSpacing(4) contents.add(HTML('You can place anything in a dialog box. Even drivel.')) contents.add(Button("Close", getattr(self, "onClose"))) contents.setStyleName("Contents")

self._dialog = DialogBox() self._dialog.setHTML('<b>Welcome to the dialog box</b>') self._dialog.setWidget(contents)

left = (Window.getClientWidth() - 200) / 2 top = (Window.getClientHeight() - 100) / 2 self._dialog.setPopupPosition(left, top) self._dialog.show()

def onClose(self): self._dialog.hide()

ui.DockPanel

The ui.DockPanel class divides the panel into five pieces, arranged into North, South, East, West andcenter pieces. In general the outer pieces are smaller, with the centre holding the main part of the panel's

contents, as shown below.

The alignment and size can be set for each widget within the DockPanel, by calling

setCellHorizontalAlignment(widget, alignment), setCellVerticalAlignment(widget, alignment),

setCellHeight(widget, height) and setCellWidth(widget, width). The default horizontal and verticalalignment to use for new widgets can be set by calling setHorizontalAlignment() and

setVerticalAlignment() before the widget is added.

Source Code:

from pyjamas.ui import SimplePanel, DockPanel, Label, HasAlignment

class DockPanelDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

panel = DockPanel() panel.setBorderWidth(1)

north = Label("North") west = Label("West") center = Label("Center") east = Label("East") south = Label("South")

panel.setHorizontalAlignment(HasAlignment.ALIGN_CENTER) panel.setVerticalAlignment(HasAlignment.ALIGN_MIDDLE)

panel.add(north, DockPanel.NORTH) panel.add(west, DockPanel.WEST) panel.add(center, DockPanel.CENTER) panel.add(east, DockPanel.EAST) panel.add(south, DockPanel.SOUTH)

panel.setCellHeight(center, "200px") panel.setCellWidth(center, "400px")

self.add(panel)

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

31 of 61 08-03-2009 18:11

Page 32: Pyjamas the Book

ui.FlexTable

The ui.FlexTable class implements a table that can have different numbers of cells in each row, andsingle cells can span multiple rows and columns.

Each FlexTable has a FlexCellFormatter which can be used to format the cells in the table. The

FlexCellFormatter has methods to set the row or column spans for a cell, as well as change the cell

alignment, as shown below.

Note that if row or column spanning is used, the cells on the rest of that row or column will be moved

over. This can cause some surprising results. Imagine a table with a 3x2 layout, like this:

+---+---+---+| A | B | C |+---+---+---+| D | E | F |+---+---+---+

If Cell is set up 0,0 to span two columns, like this:

flexTable.getFlexCellFormatter().setColSpan(0, 0, 2)

then the table, when displayed, ends up looking like this:

+-------+---+---+| A | B | C |+---+---+---+---+| D | E | F |+---+---+---+

It might be expected that cell B would be above cell E, but to make this happen it would be necessary toplace cell E at (1, 2) rather than (1, 1), as Cell A at (0, 0) has specifically been set to span the first two

columns.

Each FlexTable also has a RowFormatter which can be used to change style names, attributes, and the

visibility of rows in the table.

Please note that, whilst FlexTable is useful for laying out complexpatterns, some browsers are not perfect in determining the layout.If the patterns are particularly jagged, Firefox 2.0, for example,can get horribly confused. There isn't anything that can be doneabout this, other than to raise a bugreport with Mozilla, and redesignthe application to work around the problem. Welcome to an unfortunateinstance where browser incompatibility "hell" still applies, althoughit's necessary to push Firefox pretty hard, with an exceptionallycomplex layout, to encounter this particular bug.

Source Code:

from pyjamas.ui import SimplePanel, FlexTable, HasAlignment, Button

class FlexTableDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

self._table = FlexTable() self._table.setBorderWidth(1) self._table.setWidth("100%")

cellFormatter = self._table.getFlexCellFormatter() rowFormatter = self._table.getRowFormatter()

self._table.setHTML(0, 0, "<b>Mammals</b>") self._table.setText(1, 0, "Cow") self._table.setText(1, 1, "Rat")

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

32 of 61 08-03-2009 18:11

Page 33: Pyjamas the Book

self._table.setText(1, 2, "Dog")

cellFormatter.setColSpan(0, 0, 3) cellFormatter.setHorizontalAlignment(0, 0, HasAlignment.ALIGN_CENTER)

self._table.setWidget(2, 0, Button("Hide", getattr(self, "hideRows"))) self._table.setText(2, 1, "1,1") self._table.setText(2, 2, "2,1") self._table.setText(3, 0, "1,2") self._table.setText(3, 1, "2,2")

cellFormatter.setRowSpan(2, 0, 2) cellFormatter.setVerticalAlignment(2, 0, HasAlignment.ALIGN_MIDDLE)

self._table.setWidget(4, 0, Button("Show", getattr(self, "showRows")))

cellFormatter.setColSpan(4, 0, 3)

rowFormatter.setVisible(4, False)

self.add(self._table)

def hideRows(self): rowFormatter = self._table.getRowFormatter() rowFormatter.setVisible(2, False) rowFormatter.setVisible(3, False) rowFormatter.setVisible(4, True)

def showRows(self): rowFormatter = self._table.getRowFormatter() rowFormatter.setVisible(2, True) rowFormatter.setVisible(3, True) rowFormatter.setVisible(4, False)

ui.FlowPanel

The ui.FlowPanel is a panel that allows its contents to "flow" from left to right, and then from top to bottom,

like words on a page.

Due to the way it works, only the width of a FlowPanel needs to be specified: it will automatically take up

as much height as is needed to fit the panel's contents.

Unfortunately, circumstances where FlowPanel is useful is actually quite limited, because of the wayother widgets are typically implemented. Many widgets are wrapped up in a element, which is a

block-level element that overrules the element used by the FlowPanel, which is an inline element. As a

result, if a ui.Label is added to a FlowPanel, for example, it will still appear on a line by itself rather than

flowing with the other elements.

So it may be desirable to avoid using FlowPanel unless it is certain that the items being added will flowcorrectly. Also, it's worth experimenting by adding float: left; to the CSS style of the item being

added. If that doesn't work, try adding the widget to a SimplePanel, and adding the float-left style to the

SimplePanel.

Source Code:

from pyjamas.ui import SimplePanel, FlowPanel, Button

class FlowPanelDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

flow = FlowPanel()

flow.add(Button("Item 1")) flow.add(Button("Item 2")) flow.add(Button("Item 3")) flow.add(Button("Item 4")) flow.add(Button("Item 5")) flow.add(Button("Item 6"))

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

33 of 61 08-03-2009 18:11

Page 34: Pyjamas the Book

flow.add(Button("Item 7")) flow.add(Button("Item 8")) flow.add(Button("Item 9")) flow.add(Button("Item 10")) flow.setWidth("400px") self.add(flow)

ui.FormPanel

The ui.FormPanel class implements a traditional HTML form.

Any TextBox, PasswordTextBox, TextArea, RadioButton, CheckBox, ListBox, FileUpload and Hidden

fields contained within the form panel will be sent to the server when the form is submitted.

The example below calls Google to perform a search using the query entered by the user into the text

field. The results are shown in a separate Frame. Alternatively, Form.addFormHandler(handler) can becalled, to manually process the results of posting the form. When this is done, handler.onSubmit(event)

will be called when the user is about to submit the form; call event.setCancelled(True) to cancel the event

within this method. Also, handler.onSubmitComplete(event) will be called when the results of submitting

the form are returned back to the browser. Call event.getResults() to retrieve the (plain-text) valuereturned by the server.

Note that if a ui.FileUpload widget is used in a form, the form encoding and method must be set, as

follows:

self.form.setEncoding(FormPanel.ENCODING_MULTIPART) self.form.setMethod(FormPanel.METHOD_POST)

Setting Multipart encoding and POST method will ensure that the form is submitted in a way that Web

servers expect files to be uploaded.

Source Code:

from pyjamas.ui import SimplePanel, FormPanel, VerticalPanel, HorizontalPanelfrom pyjamas.ui import TextBox, Label, Button

class FormPanelDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

self.form = FormPanel() self.form.setAction("http://google.com/search") self.form.setTarget("results")

vPanel = VerticalPanel()

hPanel = HorizontalPanel() hPanel.add(Label("Search for:"))

self.field = TextBox() self.field.setName("q") hPanel.add(self.field)

hPanel.add(Button("Submit", getattr(self, "onBtnClick")))

vPanel.add(hPanel)

results = NamedFrame("results") vPanel.add(results)

self.form.add(vPanel) self.add(self.form)

def onBtnClick(self): self.form.submit()

ui.Grid

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

34 of 61 08-03-2009 18:11

Page 35: Pyjamas the Book

The ui.Grid class implements a panel which lays its contents out in a grid-like fashion, very like an HTML

table.

The setHTML(row, col, html) method can be used to set the HTML-formatted text to be displayed at the

given row and column within the grid. Similarly, setText(row, col, text) can be called to display plain(unformatted) text at the given row and column, and setWidget(row, col, widget) can be called to add any

widget at the given row and column within the grid.

Source Code:

from pyjamas.ui import SimplePanel, Grid, Button

class GridDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

grid = Grid(5, 5) grid.setHTML(0, 0, '<b>Hello, World!</b>') grid.setBorderWidth(2) grid.setCellPadding(4) grid.setCellSpacing(1)

for row in range(1, 5): for col in range(1, 5): grid.setText(row, col, "%d*%d=%d" % (row, col row*col))

grid.setWidget(4, 4, Button("Peekaboo")

self.add(grid)

Please note that there is a bug in the sprintf code, in versions of Pyjamasup to 0.3, that will stop "%d*%d=%d" from working. Either use this: str(row) + "*" + str(col) + " = " + str(row*col)or upgrade Pyjamas to greater than version 0.3

ui.HorizontalPanel

The ui.HorizontalPanel class is a panel that lays out its contents from left to right.

It is often useful to call setSpacing(spacing) to add space between each of the panel's widgets.setHorizontalAlignment(alignment) and setVerticalAlignment(alignment) can be called, before adding

widgets, to control how those widgets are aligned within the available space. Alternatively,

setCellHorizontalAlignment(widget, alignment) and setCellVerticalAlignment(widget, alignment) can be

called, to change the alignment of a single widget after it has been added.

Note that if it is desired that a specific widget within the panel takesup different amounts of space, do not call widget.setWidth(width)or widget.setHeight(height), as these are ignored by thepanel. Instead, call panel.setCellWidth(widget, width) andpanel.setCellHeight(widget, height).

Source Code:

from pyjamas.ui import SimplePanel, HorizontalPanel, Label, HasAlignment

class HorizontalPanelDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

panel = HorizontalPanel() panel.setBorderWidth(1)

panel.setHorizontalAlignment(HasAlignment.ALIGN_CENTER) panel.setVerticalAlignment(HasAlignment.ALIGN_MIDDLE)

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

35 of 61 08-03-2009 18:11

Page 36: Pyjamas the Book

part1 = Label("Part 1") part2 = Label("Part 2") part3 = Label("Part 3") part4 = Label("Part 4")

panel.add(part1) panel.add(part2) panel.add(part3) panel.add(part4)

panel.setCellWidth(part1, "10%") panel.setCellWidth(part2, "70%") panel.setCellWidth(part3, "10%") panel.setCellWidth(part4, "10%")

panel.setCellVerticalAlignment(part3, HasAlignment.ALIGN_BOTTOM)

panel.setWidth("100%") panel.setHeight("200px")

self.add(panel)

ui.HtmlPanel

The ui.HTMLPanel class allows HTML to be included within an application, and other widgets embeddedinside the panel's contents, by wrapping them inside a tag.

Source Code:

from pyjamas.ui import SimplePanel, HTMLPanel, Button, Label

class HtmlPanelDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

id1 = HTMLPanel.createUniqueId() id2 = HTMLPanel.createUniqueId()

panel = HTMLPanel('<b>This is some HTML</b><br>' + 'First widget:<span id="' + id1 + '"></span><br>' + 'Second widget:<span id="' + id2 + '"></span><br>' + 'More <i>HTML</i>')

panel.add(Button("Hi there"), id1) panel.add(Label("This label intentionally left blank"), id2)

self.add(panel)

ui.PopupPanel

The ui.PopupPanel class implements a panel that pops up in the browser window to display somecontents. When the user clicks outside the popup, it disappears again.

The PopupPanel requires stylesheet definitions in order to work properly. The following stylesheet

definitions were used in the example below:

.showcase-popup { background-color: white; border: 1px solid #87B3FF; padding: 4px; }

Note that the popup panel is supposed to close when the user clicks outside of it. However, this doesn't

work under Firefox 3, so the code below adds a click handler so the user can click on the popup itself toclose it.

Source Code:

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

36 of 61 08-03-2009 18:11

Page 37: Pyjamas the Book

from pyjamas.ui import SimplePanel, VerticalPanel, HTML, Button, PopupPanel

class PopupPanelDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

vPanel = VerticalPanel() vPanel.setSpacing(4)

self._btn = Button("Click Me", getattr(self, "showPopup"))

vPanel.add(HTML("Click on the button below to display the popup.")) vPanel.add(self._btn)

self.add(vPanel)

def showPopup(self): contents = HTML("Hello, World!") contents.addClickListener(getattr(self, "onClick"))

self._popup = PopupPanel(autoHide=True) self._popup.add(contents) self._popup.setStyleName("showcase-popup")

left = self._btn.getAbsoluteLeft() + 10 top = self._btn.getAbsoluteTop() + 10 self._popup.setPopupPosition(left, top) self._popup.show()

def onClick(self): self._popup.hide()

ui.ScrollPanel

The ui.ScrollPanel class implements a panel that scrolls its contents.

If scroll bars are required to be always visible, call setAlwaysShowScrollBars(True). It's also possible tochange the current scrolling position programmatically, by calling setScrollPosition(vPos) and

setScrollHorizontalPosition(hPos) to change the horizontal and vertical scrolling position, respectively.

Source Code:

from pyjamas.ui import SimplePanel, ScrollPanel, HTML

class ScrollPanelDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

panel = ScrollPanel()

contents = HTML("<b>Tao Te Ching, Chapter One</b><p>" + "The Way that can be told of is not an unvarying " + "way;<p>The names that can be named are not " + "unvarying names.<p>It was from the Nameless that " + "Heaven and Earth sprang;<p>The named is but the " + "mother that rears the ten thousand creatures, " + "each after its kind.")

panel.add(contents) panel.setSize("300px", "100px") self.add(panel)

ui.StackPanel

The ui.StackPanel class displays a "stack" of sub-panels, where only one sub-panel is open at a time.

The StackPanel relies heavily on stylesheet definitions to make it look good: the default look of a

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

37 of 61 08-03-2009 18:11

Page 38: Pyjamas the Book

StackPanel without any styles defined is almost unusable. The following eye-burning stylesheet

definitions were used for the example given below:

.gwt-StackPanel { border: 5px solid #999999; background-color: #CCCCCC; border-collapse: collapse; }

.gwt-StackPanel .gwt-StackPanelItem { border: 2px solid #000099; background-color: #FFFFCC; cursor: pointer; font-weight: normal; }

.gwt-StackPanel .gwt-StackPanelItem-selected { border: 3px solid #FF0000; background-color: #FFFF66; cursor: default; font-weight: bold; }

The currently-open panel can be programatically changed, by calling the setStackVisible(index, visible)

method. To find out which panel is currently open, call getSelectedIndex(). To retrieve the widget at agiven index, call getWidget(index). Finally, the label for a stack item can be changed, by calling

setStackText(index, text).

Source Code:

from pyjamas.ui import SimplePanel, StackPanel, HTML

class StackPanelDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

stack = StackPanel()

stack.add(HTML('The quick<br>brown fox<br>jumps over the<br>lazy dog.'), "Stack 1") stack.add(HTML('The<br>early<br>bird<br>catches<br>the<br>worm.'), "Stack 2") stack.add(HTML('The smart money<br>is on the<br>black horse.'), "Stack 3")

stack.setWidth("100%") self.add(stack)

ui.TabPanel

The ui.TabPanel class implements a tabbed window, where clicking on a tab causes the associated

contents to be displayed.

The TabPanel relies heavily on cascading stylesheet definitions to operate correctly. The followingstylesheet definitions are used by the example shown below:

.gwt-TabPanel { }

.gwt-TabPanelBottom { border: 1px solid #87B3FF; }

.gwt-TabBar { background-color: #C3D9FF; }

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

38 of 61 08-03-2009 18:11

Page 39: Pyjamas the Book

.gwt-TabBar .gwt-TabBarFirst { height: 100%; padding-left: 3px; }

.gwt-TabBar .gwt-TabBarRest { padding-right: 3px; }

.gwt-TabBar .gwt-TabBarItem { border-top: 1px solid #C3D9FF; border-bottom: 1px solid #C3D9FF; padding: 2px; cursor: pointer; }

.gwt-TabBar .gwt-TabBarItem-selected { font-weight: bold; background-color: #E8EEF7; border-top: 1px solid #87B3FF; border-left: 1px solid #87B3FF; border-right: 1px solid #87B3FF; border-bottom: 1px solid #E8EEF7; padding: 2px; cursor: default; }

Source Code:

from pyjamas.ui import SimplePanel, TabPanel, HTML

class TabPanelDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

tabs = TabPanel() tabs.add(HTML("The quick brown fox jumps over the lazy dog."), "Tab 1") tabs.add(HTML("The early bird catches the worm."), "Tab 2") tabs.add(HTML("The smart money is on the black horse."), "Tab 3")

tabs.selectTab(0) tabs.setWidth("100%") tabs.setHeight("250px")

self.add(tabs)

ui.VerticalPanel

The ui.VerticalPanel class is a panel that lays out its contents from top to bottom.

It is often useful to call setSpacing(spacing) to add space between each of the panel's widgets.

setHorizontalAlignment(alignment) and setVerticalAlignment(alignment) can also be called before adding

widgets to control how those widgets are aligned within the available space. Alternatively,setCellHorizontalAlignment(widget, alignment) and setCellVerticalAlignment(widget, alignment) can be

called, to change the alignment of a single widget after it has been added.

Note that if it is desired that a specific widget within the panel takesup different amounts of space, do not call widget.setWidth(width)or widget.setHeight(height), as these are ignored by thepanel. Instead, call panel.setCellWidth(widget, width) andpanel.setCellHeight(widget, height).

Source Code:

from pyjamas.ui import SimplePanel, VerticalPanel, Label, HasAlignment

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

39 of 61 08-03-2009 18:11

Page 40: Pyjamas the Book

class VerticalPanelDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

panel = VerticalPanel() panel.setBorderWidth(1)

panel.setHorizontalAlignment(HasAlignment.ALIGN_CENTER) panel.setVerticalAlignment(HasAlignment.ALIGN_MIDDLE)

part1 = Label("Part 1") part2 = Label("Part 2") part3 = Label("Part 3") part4 = Label("Part 4")

panel.add(part1) panel.add(part2) panel.add(part3) panel.add(part4)

panel.setCellHeight(part1, "10%") panel.setCellHeight(part2, "70%") panel.setCellHeight(part3, "10%") panel.setCellHeight(part4, "10%")

panel.setCellHorizontalAlignment(part3, HasAlignment.ALIGN_RIGHT)

panel.setWidth("50%") panel.setHeight("300px")

self.add(panel)

Widgets

The definition of a Widget is "an element of a graphical user interface that displays information or allows

the user to interact with an application". It should therefore come as no surprise that pyjamas.ui contains

the basic widgets - buttons, input boxes etc. - that can be expected of a Widget set.

Also included are the kinds of widgets that are expected of an HTML-based widget set, such as those

that deal with Frames, Forms and Hyperlinks. These concepts, whilst unique to the HTML world, still needto be "widgetified", if Pyjamas applications are to get at them.

These HTML-based widgets, and the HTMLPanel, made it difficult to portPyjamas to the desktop-based widget sets, PyQt4 and PyGtk2. Consequently,Webkit, an HTML Web Engine, had to be used as the basis for the PyjamasDesktop port.

It's worth reiterating that, whilst the Pyjamas Widgets are in some ways minimalist yet comprehensive, it

is still possible, thanks to manipulation of the Browser DOM model, to write custom widgets very easily.pyjamas.ui is stuffed full of widgets that manipulate the DOM model (such as Tree and Menubar), so it's

not like there's a lack of examples to follow, or anything.

ui.Button

The ui.Button class is used to show a button. When the user clicks on the button, the given listenerfunction is called.

Note that the getattr() function can be used to specify which method will be called when the button is

clicked. This is the best way to write button click handlers if there is more than one button on the panel. If

there is only one button, btn = Button("...", self) can be used, instead. Under these

circumstances, a method called onClick() must be provided in the class, which will respond to the button

click.

Another useful method is Button.setEnabled(enabled), which enables or disables the button, depending

on the value of its parameter.

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

40 of 61 08-03-2009 18:11

Page 41: Pyjamas the Book

It's particularly useful to call setEnabled(False) when some data has beensent to a server, using AJAX. Normally in a web-based application, thebrowser stops responding whilst the page is reloaded after a form is submitted.However, in an AJAX application, pressing a button usually causes an AJAXsubmit - and there is nothing to stop the user from pressing the buttonmore than once. So it is a really good idea to disable the button, explicitly,until the AJAX response is received, and re-enable the button, if appropriatefor the application, after the response has been processed.

Source Code:

from pyjamas.ui import SimplePanel, Buttonfrom pyjamas import Window

class ButtonDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

btn = Button("Click Me", getattr(self, "onButtonClick")) self.add(btn)

self.btn2 = Button("Tickle me!", self) self.add(btn2)

def onButtonClick(self): Window.alert("Ouch!")

def onClick(self, sender, event): if sender == self.btn2: Window.alert("That tickles!") else: Window.alert("Who sent that???")

ui.CheckBox

The ui.CheckBox class is used to show a standard checkbox. When the user clicks on the checkbox, the

checkbox's state is toggled.

The setChecked(checked) method checks or unchecks the checkbox depending on the value of theparameter. To get the current value of the checkbox, call isChecked().

A checkbox can be enabled or disables using setEnabled(enabled). Also, addClickListener() can be

called to add a function that responds when the user clicks on the checkbox, as shown below. This

listener feature can be useful when building complicated input screens, where checking a checkbox

causes other input fields to be enabled or disabled.

Note: the onClick method will be called irrespective of whetherthe checkbox is checked or unchecked, which is very useful. If itis necessary to only take action when the checkbox is checked,call isChecked() to determine its value at the time.

Source Code:

from pyjamas.ui import SimplePanel, CheckBoxfrom pyjamas import Window

class CheckBoxDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

self.box = CheckBox("Print Results?") self.box.addClickListener(getattr(self, "onClick"))

self.add(self.box)

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

41 of 61 08-03-2009 18:11

Page 42: Pyjamas the Book

def onClick(self): Window.alert("checkbox status: " + self.box.isChecked())

ui.FileUpload

The ui.FileUpload class implements a file uploader widget.

The FileUpload widget must be inside a ui.FormPanel which is used to submit the HTML form to the

server. Note that it is necessary to set the form encoding and method as follows: {self.form.setEncoding(FormPanel.ENCODING_MULTIPART)

self.form.setMethod(FormPanel.METHOD_POST) } Setting Multipart encoding and POST method will

ensure that the form is submitted in a way that Web servers expect files to be uploaded.

The example below doesn't really work, as there is no suitable server at nonexistent.com. However, it

does show how a file upload widget could be used within a FormPanel.

Source Code:

from pyjamas.ui import SimplePanel, FormPanel, VerticalPanel, HorizontalPanelfrom pyjamas.ui import FileUpload, Label, Button

class FileUploadDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

self.form = FormPanel() self.form.setEncoding(FormPanel.ENCODING_MULTIPART) self.form.setMethod(FormPanel.METHOD_POST) self.form.setAction("http://nonexistent.com") # set this as appropriate self.form.setTarget("results")

vPanel = VerticalPanel()

hPanel = HorizontalPanel() hPanel.setSpacing(5) hPanel.add(Label("Upload file:"))

self.field = FileUpload() self.field.setName("file") hPanel.add(self.field)

hPanel.add(Button("Submit", getattr(self, "onBtnClick")))

vPanel.add(hPanel)

results = NamedFrame("results") vPanel.add(results)

self.form.add(vPanel) self.add(self.form)

def onBtnClick(self): self.form.submit()

ui.Frame

The ui.Frame class is used to embed a separate HTML page within an application.

If a URL is passed when the Frame is created, that URL will be used immediately. Alternatively, the

Frame.setUrl() method can be called to change the URL at any time. Frame.getUrl() can also be called

to retrieve the current URL for the frame.

Source Code:

from pyjamas.ui import SimplePanel, Frame

class FrameDemo(SimplePanel):

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

42 of 61 08-03-2009 18:11

Page 43: Pyjamas the Book

def __init__(self): SimplePanel.__init__(self)

frame = Frame("http://google.com") frame.setWidth("100%") frame.setHeight("200px") self.add(frame)

ui.Hidden

The ui.Hidden class represents a hidden form field.

ui.Hidden is really only useful when the hidden field is part of a ui.FormPanel.

Source Code:

from pyjamas.ui import SimplePanel, FormPanel, VerticalPanelfrom pyjamas.ui import Hidden, Button, NamedFrame

class HiddenDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

self.form = FormPanel() self.form.setAction("http://google.com/search") self.form.setTarget("results")

panel = VerticalPanel() panel.add(Hidden("q", "python pyjamas")) panel.add(Button("Search", getattr(self, "onBtnClick")))

results = NamedFrame("results") panel.add(results)

self.form.add(panel) self.add(self.form)

def onBtnClick(self): self.form.submit()

ui.Html

The ui.HTML class displays HTML-formatted text. To display unformatted text, use ui.Label.

ui.HTML's content can be set and retrieved, using setHTML and getHTML. If False is passed as the

second parameter when creating an HTML instance, word wrapping will be disabled, forcing all the text to

be on one line.

Source Code:

from pyjamas.ui import SimplePanel, HTML

class HtmlDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

html = HTML("Hello, <b><i>World!</i></b>") self.add(html)

ui.Hyperlink

The ui.Hyperlink class acts as an "internal" hyperlink to a particular state of the application. These states

are stored in the application's history, allowing for the use of the Back and Next buttons in the browser to

move between application states.

The ui.Hyperlink class only makes sense in an application which keeps track of state using the History

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

43 of 61 08-03-2009 18:11

Page 44: Pyjamas the Book

module. When the user clicks on a hyperlink, the application changes state by calling

History.newItem(newState). The application then uses a history listener function to respond to the

change in state in whatever way makes sense.

Critical to ensuring that the History module works correctly is theaddition of a hidden "history" frame to the HTML loader file of theapplication.

Here is the pyjamas "Kitchen Sink" html file, as an example:

<html> <head> <meta name="pygwt:module" content="KitchenSink"> <link rel='stylesheet' href='KitchenSink.css'> <title>KitchenSink</title> </head> <body bgcolor="white"> <script language="javascript" src="pygwt.js"></script>

<iframe id='__pygwt_historyFrame' style='width:0;height:0;border:0'></iframe> </body></html>

Source Code:

from pyjamas.ui import SimplePanel, VerticalPanel, HorizontalPanelfrom pyjamas.ui import Hyperlink, Labelfrom History import History

class HyperlinkDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

History().addHistoryListener(self)

vPanel = VerticalPanel()

self.stateDisplay = Label() vPanel.add(self.stateDisplay)

hPanel = HorizontalPanel() hPanel.setSpacing(5) hPanel.add(Hyperlink("State 1", False, "state number 1")) hPanel.add(Hyperlink("State 2", False, "state number 2"))

vPanel.add(hPanel) self.add(vPanel)

def onHistoryChanged(self, state): self.stateDisplay.setText(state)

ui.Image

The ui.Image class is used to display an image.

The Image class can display any image that is specified by a URL. The image can be stored somewhere

on the internet, or on any Web Server, anywhere. In the example below, the "public" directory within the

application's source folder is used, and the image is then accessed using a relative URL.

The example image file named "myImage.jpg" must be stored in "images" sub-directory, which must be in

the "public" directory of the application's main source directory.

As well as passing the image URL to the initialiser, setURL() can be called to change the image beingdisplayed, at any time. addClickListener() can also be used to add a listener function callback that will be

called when the user clicks on the image.

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

44 of 61 08-03-2009 18:11

Page 45: Pyjamas the Book

Source Code:

from pyjamas.ui import SimplePanel, Imagefrom pyjamas import Window

class ImageDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

# We display the "myImage.jpg" file, stored in the "public/images" # directory, where "public" is in the application's source directory.

img = Image("images/myImage.jpg") img.addClickListener(getattr(self, "onImageClicked")) self.add(img)

def onImageClicked(self): Window.alert("Stop that!")

ui.Label

The ui.Label class is used to display unformatted text. Unlike the ui.HTML class, it does not interpretHTML format codes. If False is passed as the second parameter when creating a label, word wrapping

will be disabled, forcing all the text to be on one line.

Source Code:

from pyjamas.ui import SimplePanel, Label

class LabelDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

label = Label("This is a label", wordWrap=False) self.add(label)

ui.ListBox

The ui.ListBox class allows the user to select one or more items from a list. There are two variations of

the ListBox: a normal list of items the user can click on, and a dropdown menu of items. Both variations

are shown in the example below.

Add items to a list by calling ListBox.addItem(). addItem can take the label to display, and also an optional

value to associate with that item in the list. ListBox.getSelectedIndex() returns the index of the currentlyselected item, or -1 if nothing is selected. ListBox.getItemText(n) returns the text for the given item in the

list, while ListBox.getValue(n) returns the value associated with the given list item. To detect when the

user selects something from a ListBox, set up a listener by calling addChangeListener(). Finally,

ListBox.clear() clears the current contents of the ListBox.

Source Code:

from pyjamas.ui import SimplePanel, HorizontalPanel, ListBoxfrom pyjamas import Window

class ListBoxDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

hPanel = HorizontalPanel() hPanel.setSpacing(10)

self.list1 = ListBox() self.list1.setVisibleItemCount(10) self.list1.addItem("Item 1")

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

45 of 61 08-03-2009 18:11

Page 46: Pyjamas the Book

self.list1.addItem("Item 2") self.list1.addItem("Item 3") self.list1.addChangeListener(getattr(self, "onList1ItemSelected"))

self.list2 = ListBox() self.list2.setVisibleItemCount(0) self.list2.addItem("Item A") self.list2.addItem("Item B") self.list2.addItem("Item C") self.list2.addChangeListener(getattr(self, "onList2ItemSelected"))

hPanel.add(self.list1) hPanel.add(self.list2) self.add(hPanel)

def onList1ItemSelected(self): item = self.list1.getItemText(self.list1.getSelectedIndex()) Window.alert("You selected " + item + " from list 1")

def onList2ItemSelected(self): item = self.list2.getItemText(self.list2.getSelectedIndex()) Window.alert("You selected " + item + " from list 2")

ui.Menubar

The ui.MenuBar and ui.MenuItem classes allow menu bars to be created in an application.

There are several important things to be aware of when adding menus to an application:

A stylesheet is absolutely essential, to define the look of the menu. The default style is terrible, and

makes the menu unusable. The following stylesheet entries were used for the example code below:

.gwt-MenuBar { background-color: #C3D9FF; border: 1px solid #87B3FF; cursor: default; }

.gwt-MenuBar .gwt-MenuItem { padding: 1px 4px 1px 4px; font-size: smaller; cursor: default; }

.gwt-MenuBar .gwt-MenuItem-selected { background-color: #E8EEF7; }

By default, each menu item can be associated with a class, whose execute method will be called

when that item is selected. Note that a helper class, MenuCmd, is defined below to allow more than

one menu item handler method to be defined within a single class.

Menu items are added directly, passing the item label and the associated command toMenuBar.addItem(). For adding sub-menus, the sub-menu must be wrapped up in a MenuItem, as

shown below.

The HTML codes in a menu item's label can be used, by calling MenuBar.addItem(label, True,cmd) instead of MenuBar.addItem(label, cmd). Similarly, HTML styling can be used in a menu's title

by calling MenuItem(label, True, submenu): see the second-to-last line of MenubarDemo.__init__,

below.

Source Code:

from pyjamas.ui import SimplePanel, MenuBar, MenuItemfrom pyjamas import Window

class MenubarDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

46 of 61 08-03-2009 18:11

Page 47: Pyjamas the Book

menu1 = MenuBar(vertical=True) menu1.addItem("Item 1", MenuCmd(self, "onMenu1Item1")) menu1.addItem("Item 2", MenuCmd(self, "onMenu1Item2"))

menu2 = MenuBar(vertical=True) menu2.addItem("Apples", MenuCmd(self, "onMenu2Apples")) menu2.addItem("Oranges", MenuCmd(self, "onMenu2Oranges"))

menubar = MenuBar(vertical=False) menubar.addItem(MenuItem("Menu 1", menu1)) menubar.addItem(MenuItem("<i>Menu 2</i>", True, menu2)) self.add(menubar)

def onMenu1Item1(self): Window.alert("Item 1 selected")

def onMenu1Item2(self): Window.alert("Item 2 selected")

def onMenu2Apples(self): Window.alert("Apples selected")

def onMenu2Oranges(self): Window.alert("Oranges selected")

class MenuCmd:

def __init__(self, object, handler): self._object = object self._handler = handler

def execute(self): handler = getattr(self._object, self._handler) handler()

ui.NamedFrame

The ui.NamedFrame class is a variation of the ui.Frame which lets a name be assigned to the frame.

Naming a frame makes it possible to refer to that frame by name in both Javascript code and as the

target for a hyperlink.

Source Code:

from pyjamas.ui import SimplePanel, VerticalPanel, NamedFrame, HTML

class NamedFrameDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

vPanel = VerticalPanel() vPanel.setSpacing(5)

frame = NamedFrame("myFrame") # note name of frame frame.setWidth("100%") frame.setHeight("200px")

vPanel.add(frame)

# note targets are set to "myFrame" vPanel.add(HTML('<a href="http://google.com" target="myFrame">Google</a>')) vPanel.add(HTML('<a href="http://yahoo.com" target="myFrame">Yahoo</a>'))

self.add(vPanel)

ui.PasswordTextBox

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

47 of 61 08-03-2009 18:11

Page 48: Pyjamas the Book

The ui.PasswordTextBox class implements a standard password input field.

Like its cousins the ui.TextBox and ui.TextArea classes, ui.PasswordTextBox defines many methods

which may prove useful.

The most important methods are going to be setText() and getText(), which set and retrieve the contents

of the input field, and setMaxLength() to specify how many characters the user can type into the field.

Note that for some reason, the setVisibleLength() method is not defined for a password field. Thismeans that it is necessary to specify the width of the field in pixels, as shown below.

Source Code:

from pyjamas.ui import SimplePanel, PasswordTextBox

class PasswordTextBoxDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

field = PasswordTextBox() field.setWidth("100px") self.add(field)

ui.RadioButton

The ui.RadioButton class is used to show a radio button. Each radio button is given a "group" value;

when the user clicks on a radio button, the other radio buttons in that group are deselected, and the

clicked on radio button is selected.

The setChecked(checked) method can be used to select or deselect a radio button, and isChecked()

can be called to see if a radio button is currently selected. Also a checkbox can be enabled or disabledusing setEnabled(enabled). Finally, addClickListener() can be used to set a function that will be called

when the user clicks on the radio button.

Source Code:

from pyjamas.ui import SimplePanel, HorizontalPanel, VerticalPanel, RadioButton

class RadioButtonDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

panel1 = VerticalPanel()

# First group, called "group1" panel1.add(RadioButton("group1", "Red")) panel1.add(RadioButton("group1", "Green")) panel1.add(RadioButton("group1", "Blue"))

panel2 = VerticalPanel()

# Second group, called "group2" panel2.add(RadioButton("group2", "Solid")) panel2.add(RadioButton("group2", "Liquid")) panel2.add(RadioButton("group2", "Gas"))

hPanel = HorizontalPanel() hPanel.add(panel1) hPanel.add(panel2)

self.add(hPanel)

ui.TextArea

The ui.TextArea class implements a standard multi-line input field.

The setCharacterWidth() method sets the width of the input field, in characters, while setVisibleLines()

sets the height of the field, in lines.

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

48 of 61 08-03-2009 18:11

Page 49: Pyjamas the Book

Use the getText() method to retrieve the field's current text, and setText() to set it. There are many other

useful methods defined by ui.TextArea and its parent classes; see the module documentation for more

details.

Source Code:

from pyjamas.ui import SimplePanel, TextArea

class TextAreaDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

field = TextArea() field.setCharacterWidth(20) field.setVisibleLines(4) self.add(field)

ui.TextBox

The ui.TextBox class implements a standard one-line input field.

There are many useful methods defined by ui.TextBox and its parent classes. For example, getText()

returns the current contents of the input field, and setText() lets the field's contents be set to a givenstring.

setVisibleLength() sets the width of the field, in characters. Similarly, setMaxLength() sets the maximum

number of characters the user can enter into the field.

Source Code:

from pyjamas.ui import SimplePanel, TextBox

class TextBoxDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

field = TextBox() field.setVisibleLength(20) field.setMaxLength(10)

self.add(field)

ui.Tree

The ui.Tree class adds a tree control to an application.

Call Tree.addTreeListener() to add a tree listener object to a tree. That listener object'sonTreeItemSelected() method will be called when the user clicks on that item in the tree control with which

the listener has been associated. Similarly, the listener object's onTreeItemStateChanged() method will

be called whenever the user opens or closes a branch of the tree. The listener object must have both

these methods defined, even if neither of them is used.

To open a branch of the tree, call the TreeItem.setState() method. If the state parameter is True, the

branch of the tree will be opened; if it is False, the branch of the tree will be closed.

Source Code:

from pyjamas.ui import SimplePanel, Tree, TreeItemfrom pyjamas import DOMfrom pyjamas import Window

class TreeDemo(SimplePanel):

def __init__(self): SimplePanel.__init__(self)

tree = Tree()

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

49 of 61 08-03-2009 18:11

Page 50: Pyjamas the Book

tree.addTreeListener(self)

s1 = self.createItem("Section 1") s1.addItem(self.createItem("Item 1.1", value=11)) s1.addItem(self.createItem("Item 1.2", value=12))

s2 = self.createItem("Section 2") s2.addItem(self.createItem("Item 2.1", value=21)) s2.addItem(self.createItem("Item 2.2", value=22))

s1.setState(True, fireEvents=False) s2.setState(True, fireEvents=False)

tree.addItem(s1) tree.addItem(s2) self.add(tree)

def createItem(self, label, value=None): item = TreeItem(label) DOM.setStyleAttribute(item.getElement(), "cursor", "pointer") if value != None: item.setUserObject(value) return item

def onTreeItemSelected(self, item): value = item.getUserObject() Window.alert("You clicked on " + value)

def onTreeItemStateChanged(self, item): pass # We ignore this.

An important part of a widget toolkit is being able to write custom widgets. In many widget sets,

developers are confronted immediately with a quite complex set of unusual-looking functions - paint,draw, refresh and other manipulations. The reason for this complexity is because the fundamental

underlying design of Desktop widget set frameworks is invariably too simplistic. Changes in width or

height of a single widget can have a knock-on effect that dramatically alters the layout of the entire

application. Rather than attempting to solve this really quite difficult problem, Desktop widgets simplyavoid it entirely, and impose on the developer to sort out the mess for themselves, and are given stern

warnings about how hard-coding widths and heights could mess things up.

The only technology that has tackled the issue of complex layouts, with any degree of success, is Web

Browsers. Web browsers simply have to tackle layouts properly, due to the overwhelming user-driven

diversity and the sheer numbers of people that use Web browsers. Consequently, Pyjamas and

Pyjamas-Desktop, both being based on web technology, benefit from the underlying layout issues having

been made a lot easier.

Both Pyjamas and Pyjamas-Desktop manipulate the DOM model - an HTML page - as if it was an XMLdocument. Better than that: unlike with manipulating an HTML page in traditional web browser

development, it's not necessary to get involved with Javascript - unless desired. Pyjamas provides a

module which makes the job of controlling the underlying DOM model that much easier, and this chapter

shows step-by-step how to go about creating a custom widget, through DOM model manipulation andDOM event interaction.

Missing from the HTML specification, but present in Adobe Flash, are widgets such as sliders and dials.

Many Desktop widget sets have control widgets, so it makes a lot of sense to create one. So this chapter

starts with a simple Vertical scroller which receives "mouse click" to change the position, goes on later to

add "drag" and then adds keyboard interaction, almost as an afterthought (just like most badly-designedapplications do. hoorary!)

Vertical Slider

Start off by importing the DOM model and, because the slider will receive mouse (and later keyboard)

events, it is based on FocusWidget. FocusWidget has the means to add keyboard and event listeners,

set a "tab order" index, and to set and clear focus. Create a file called Controls.py, starting off with thefollowing lines:

from pyjamas import DOM

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

50 of 61 08-03-2009 18:11

Page 51: Pyjamas the Book

from pyjamas.ui import FocusWidget

The class will be derived from FocusWidget. Width and height parameters will not be added to the

constructor, because Pyjamas Widgets are based on HTML principles: DOM models. So, a developer

can add a CSS "Class", with setStyleName(), and thus set the width and/or height set as desired in the

CSS stylesheet. Alternatively, the functions setWidth() and setHeight() can be used. It is however

necessary to pass in the slider's minimum, maximum and default values.

This is an important point to emphasise: widgets should not impose "look"onto users - that should, ideally, be defined through CSS. A Widget ClassAPI should be about "function" rather than "form". So, the constructorfor the widget has minimum, maximum and default values, not width, heightor colour.

Add the following lines to the newly-created Controls.py:

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: the widget is based on

a "div" tag, hence we call DOM.createDiv() and set that as the FocusWidget's element. Immediately,

therefore, it is clear 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 anapplication just that little bit easier to understand.

Next, it is necessary to make the slider "handle" and to make it possible for the handle to move freely. In

other words, it is necessary to be able to set the coordinates, exactly, of the slider "handle". There is a

Pyjamas ui widget which has exactly this functionality, already: AbsolutePanel. Take a look at

pyjamas/ui.py and search for "class AbsolutePanel". Examining what AbsolutePanel.__init__() does,

it can be seen that by setting its "div" container tag to be position "relative", child elements can be

positioned accurately within it. Also, a second hard-coded "div" will be added, to represent the actual

slider handle:

DOM.setStyleAttribute(element, "position", "relative") DOM.setStyleAttribute(element, "overflow", "hidden")

self.handle = DOM.createDiv() DOM.appendChild(element, self.handle)

Note that the second "handle" div is appended to the container "div" as a child element. As this is just ademonstration, we're going to hand-code the slider handle with some attributes, making it 10 pixels high,

with 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 whereusers could over-ride all these settings. the "handle" elementwould have a CSS style added to it, using setStyleName.

Note also that DOM.setAttribute() is not used to set the border, width and height. W3C HTML

specifications need to be consulted, here: observe that "border" is a CSS attribute reserved for DOM

components such as "table". So, if DOM.setAttribute("border", "1px") is called on a "div" tag, it silently

fails to do anything, in the browser. If the Javascript Console is examined, there might be a warning - but

it is likely swamped by all the other CSS errors...

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

51 of 61 08-03-2009 18:11

Page 52: Pyjamas the Book

If the same thing is tried under Pyjamas-Desktop, a much more usefulrun-time error will occur, as the attributes and the CSS style attributesof an element are directly accessed as static variables, in entirelydifferent data structures. So, if the variable being accessed don't exist,Python can do nothing but complain.

The upshot is: pay attention to the underlying DOM model, and remember to simultaneously develop an

application using both Pyjamas and Pyjamas-Desktop, to save a great deal of time. If a border on a "div"

tag is required, it must be set as a CSS Style attribute with DOM.setStyleAttribute, not as an

attribute (with DOM.setAttribute):

DOM.setStyleAttribute(self.handle, "border", "1px") DOM.setStyleAttribute(self.handle, "width", "100%") DOM.setStyleAttribute(self.handle, "height", "10px") DOM.setStyleAttribute(self.handle, "backgroundColor", "#808080")

These lines make the slider "handle" have a border of 1 pixel; set the handle width to always be the sameas its container; set its height to 10 pixels, and set its colour to a nice boring shade of grey.

Testing

With these basic beginnings, it's enough to test the application, to see if it's working. If all that was wanted

was a little grey box in a widget, the task would be complete. However, it's good practice to confirm what'salready working, before proceeding. In the same directory that the Controls.py module has been saved,

create an HTML Loader file, ControlDemo.html, with the following contents:

<html> <head> <meta name="pygwt:module" content="ControlDemo"> <title>Hello</title> </head> <body bgcolor="white"> <script language="javascript" src="pygwt.js"></script> </body></html>

Next, create a file, ControlDemo.py, again in the same directory, with the following contents:

""" testing our demo slider"""from pyjamas.ui import RootPanelfrom Controls import VerticalDemoSlider

class ControlDemo: def onModuleLoad(self): b = VerticalDemoSlider(0, 100) RootPanel().add(b) b.setWidth("20px") b.setHeight("100px")

Note that the Demo slider has been imported from Controls.py, which is why it was necessary to place

ControlDemo.py in the same directory. Also, note that the width and height are set, hard-coded, to 20 by

100 pixels. b.setStyleName("demoslider") could have been used, instead, but that would require the

creation of a CSS stylesheet along with referencing it in ControlDemo.html.

One thing that's great about Pyjamas: these eight lines are enough code to do exactly what is desired,with clear and consise purpose: create a slider, add it to the root panel, set its width to 20 pixels and the

height to 100. It really couldn't get any easier. Compile the example with the following command:

python ../../builder/build.py ControlDemo.py

Open output/ControlDemo.html in a web browser, and it should result in a delightful 20x10 grey box beingdisplayed, which is very exciting. Next on the list is to make it move, and for that, a "click listener" is

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

52 of 61 08-03-2009 18:11

Page 53: Pyjamas the Book

needed.

Making it move

To receive a click event, FocusWidget.addClickListener() is to be used. The widget itself will receive themouse click event. Open pyjamas/ui.py and search for the function in FocusWidget called

onBrowserEvent(). For convenience, the relevant portion is shown here:

def onBrowserEvent(self, event): type = DOM.eventGetType(event) if type == "click": for listener in self.clickListeners: if hasattr(listener, "onClick"): listener.onClick(self, event) else: listener(self, event)

Examining this function, it can clearly be seen that, in order to receive "click" events, a function called

onClick() must be added to the VerticalDemoSlider class. As we want to know where the mouse was

clicked, it is necessary to add two arguments to the onClick() function, in order to receive the mouse

event object as the second. Add these additional lines to the ControlDemo class:

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")

As can be seen, the mouse event y position is retrieved from the incoming event, with

DOM.eventGetClientY; the absolute location of the container is subtracted, and the resultant relative

value is passed to moveSlider(). In moveSlider, the "offset height" of the widget is the total height of

the widget, giving the travel range. With a little math, the new position of the "handle" can be calculated, in

pixels. Again, learning from how AbsolutePanel sets the coordinates of its child widgets, copying some

lines of code from the function AbsolutePanel.setWidgetPosition allows the location of the slider handle

to be set.

Save the changes to ControlDemo.py, re-build and refresh the browser. Click on the slider, and... nothingshould happen. A very important line was missed out! Go back to VerticalDemoSlider.__init__ and add

this, at the end of the function, and try again:

self.addClickListener(self)

The significance of this line can be seen from how FocusPanel's onBrowserEvent function works: unless

the instance of the slider class is listening to click events, the onClick function will not be called. Once

added, and the application is recompiled, then amazing things happen! the slider widget works! A

single-click moves the slider handle to where mouse is clicked. Notice how the slider centre moves towhere the mouse pointer actually points to: this is entirely a fluke, and is probably due to browser bugs in

the CSS style implementation.

Notice also that the value of the "slider" - the control - hasn't actually been made use of, but there's

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

53 of 61 08-03-2009 18:11

Page 54: Pyjamas the Book

enough maths to calculate the value that it should be set to. 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 self.setValue(new_value)

Then, 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,add the functions 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): self.valuechange_listeners.append(listener)

def removeControlValueListener(self, listener): self.valuechange_listeners.remove(listener)

Notice how, right at the beginning, in the constructor, an empty list was added:

self.valuechange_listeners was initialised, and that list can now be used, as shown above, by

storing listeners that will receive notification of when the value of the slider changes. Also, notice howwe're defining the interface, here - the interaction with other widgets - by expecting classes to have a

function called onControlValueChanged() that will take as parameters the sender, the old value and

the new value of the slider control. The reason for this is that a listener will find it useful to know not only

which widget changed, and also the new value, but also it may be useful to a listener to know by how

much a value changed.

Save the file, as the next step is to check that this code works. In the "test code", ControlDemo.py, addthese extra lines to ControlDemo.onModuleLoad() and also add the additional function

onControlValueChanged:

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

Note how the ControlDemo class is set as a listener for change notification: this is why an

onControlValueChanged function was added. In it, the label's text will be changed.

(Also, remember to add an import of Label to the top of thefile - the line "from pyjamas.ui import Label" will suffice).

Save the changes to ControlDemo.py, recompile the application with build.py, and refresh the browserwindow (or reopen output/ControlDemo.html). There should be a very boring Text Label 200 pixels

underneath a grey box, with the words "Not set yet" displayed. Clicking anywhere between the box and

the words should not only move the slider, but also changes the text to say "Value: 83", or something to

that effect. Amazing.

Congratulations, a slider has been created, in 70 lines of python code, and a demonstration of its use in20. It can be clicked on it. Try to resist the urge to press the up and down arrows and to click and hold the

mouse: it won't work.

What's been covered

In this section, the following issues have been covered:

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

54 of 61 08-03-2009 18:11

Page 55: Pyjamas the Book

How to create a widget that can react to user-input, by deriving from FocusWidget

How to use manipulation of the DOM model to change screen-content, by adding HTML Elements

to itHow to use the DOM module to change attributes and CSS Style attributes

The importance of checking the W3C HTML Specifications when it comes to distinguishing the

difference between HTML Element attributes and HTML Element CSS attributes

How a Pyjamas ui widget is effectively a "guardian" of HTML elements - parts of the DOM model.How a widget could be made to react to user-input and other events, by using the features

provided by an onBrowserEvent function of a base class (in this example, FocusWidget).

The importance of looking at the existing pyjamas.ui source code to work out what is going on, andthe importance of identifying, copying and adapting sections of existing code.

This latter advice is perhaps just general good advice for programming. In the Pyjamas case, given thatthe pyjamas.ui module is really quite small, it's a lot easier advice to follow than if confronted with tens of

thousands of lines of code.

Also, it's worth reiterating at this point that at no time, anywhere in the code, was there any mention of a

"repaint" function, a "redraw", "refresh" or other function that otherwise interferes with the simple, simplejob of doing exactly what is wanted: putting stuff on-screen. Although many people bitch like hell about

how "difficult" web browsers are, the bitching is invariably down to the requirement to get involved with

Javascript. Web browser technology actually does a stunningly good job, and is clearly a better basis for

a Desktop widget framework than most Desktop widget frameworks! And, given that the programming is

being done in Python, one of the key reasons for all the complaining goes away.

The reason why "repaint" isn't needed is because whenever a part of the DOMmodel is changed, the underlying browser technology gets an automaticnotification to take care of redrawing the screen. It does have to be saidthat in certain circumstances, certain browsers (in particular IE6 and IE7)don't actually get it right every time - and even Firefox can be made to screwup, if pushed particularly hard in the right places. However, with care andattention, and a little studying of HTML and CSS behaviour, the automaticredrawing will be perfect every time.

Improvements to the Slider: Event Handling and Capture

The simple slider receives a basic "click" to move the slider. Of much more interest is to be able to drag

and move the slider, as this is the kind of behaviour that is more intuitive to users. As there already is

demo slider to work from, that will be used as a base class. The primary focus of this section will

therefore to add mouse event handling - up, down, and move.

Add the following lines to Controls.py:

class VerticalDemoSlider2(VerticalDemoSlider):

def __init__(self, min_value, max_value, start_value=None):

VerticalDemoSlider.__init__(self, min_value, max_value, start_value) self.mouseListeners = [] self.addMouseListener(self) self.sinkEvents(Event.MOUSEEVENTS) self.dragging = False

def addMouseListener(self, listener): self.mouseListeners.append(listener)

def removeMouseListener(self, listener): self.mouseListeners.remove(listener)

Here, VerticalDemoSlider2 is derived from VerticalDemoSlider. The class adds itself as a "mouse

listener" - and indicates to the DOM model that notifications about all Mouse Events must be received.

Examine pyjamas/ui.py and look for "class FocusWidget".Note how FocusWidget sinks the events ONCLICK, FOCUSEVENTS and KEYEVENTS.This is why VerticalDemoSlider, which derives from FocusWidgets, was

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

55 of 61 08-03-2009 18:11

Page 56: Pyjamas the Book

capable of receiving mouse click events (through FocusWidget'sonBrowserEvent function).

By asking VerticalDemoSlider2 to receive MOUSEEVENTS, when the mouse moves into the widget, a

"mouse enter" event is received; also, all move, down and up events are also received for as long as the

mouse is over the element and, when the mouse moves out, a mouse "leave" event is received. All of

these events will result in onBrowserEvent being called, which is the next key function needed:

def onBrowserEvent(self, event): type = DOM.eventGetType(event) if type == "mousedown" or type == "mouseup" or \ type == "mousemove" or type == "mouseover" or type == "mouseout": MouseListener().fireMouseEvent(self.mouseListeners, self, event) else: VerticalDemoSlider.onBrowserEvent(self, event)

Here, as can be seen, it's very straightforward: mouse events are passed to a convenience function

(fireMouseEvent) which will call the appropriate handler functions on each of the listeners. These

lines of code were copied verbatim from other pyjamas/ui.py widgets that deal with mouse events:

Label for example. Also, note how, if the type of the event is not found to be a mouse event,

VerticalSliderDemo (the base class) is asked to handle the event. In this way, mouse click, focus andkeyboard events will still be dealt with. Without this explicit call through to the base class function (which is

actually in FocusWidget, as VerticalSliderDemo doesn't have its own onBrowserEvent function), it would

be extra and unnecessary work to handle these events.

Search for "class MouseListener" in pyjamas/ui.py. Examining fireMouseEvent, it can be seen that

there are five possible functions that could be called (on every one of the listeners, in turn):onMouseDown, onMouseUp, onMouseMove, onMouseLeave and onMouseEnter. Therefore, it is

necessary to add each of these four functions - even if all of them are not used.

onMouseEnter and onMouseLeave, are going to be left blank. However, if it was desirable to make the

slider visually "respond" to the mouse hovering over the slider, it is these two functions that would take

care of that - perhaps by adding and removing a CSS stlye, using addStyleName().

def onMouseEnter(self, sender): #self.addStyleName("sliderhighlighted") pass def onMouseLeave(self, sender): #self.removeStyleName("sliderhighlighted") pass

The core of the work is done in the remaining three functions. Firstly, onMouseMove, which takes care

of translating the mouse pointer's absolute y position into actual movement of the slider:

def onMouseMove(self, sender, x, y): if not self.dragging: return self.moveSlider(y)

Notice how slider movement is only allowed when "dragging" is set. If this wasn't done, then the slider

would immediately start to jump and move when the mouse cursor was placed over the widget, as mouse

"move" events occur immediately that the mouse enters the DOM model Element that the widget is

responsible for. Clearly this is undesirable: the expected behaviour is for the slider to move only when a

mouse button is held down:

def onMouseDown(self, sender, x, y): self.dragging = True DOM.setCapture(self.getElement()) self.moveSlider(y)

So, when a mouse down event occurs, the "dragging" flag is set to True. An indication is made, using

DOM.setCapture that all future events will be sent to this widget; the slider is also moved. The reason

for moving the slider is because it is a separate event from "mouse move", and people will get rather

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

56 of 61 08-03-2009 18:11

Page 57: Pyjamas the Book

annoyed if the the "mouse button" is clicked and the slider stays where it is. The reason for capturing

mouse events from now on is because users are quite likely to move the mouse out of the widget whilst

the button is still held down, and will expect the slider to still move. Only when the user lets go of the

mouse button will it be expected that the slider will stop moving about:

def onMouseUp(self, sender, x, y): self.dragging = False DOM.releaseCapture(self.getElement())

As can be seen, all that is necessary is to simply indicate, by setting dragging to False, that the slider

should no longer move about when "mouse move" events occur, and for event capturing to be cancelled.

This is all that is required to turn the "click-only" slider into a dragging-capable one. The majority of the

user-interface hard work was done in the previous section - all that's being done here is thatVerticalDemoSlider's moveSlider() function is called at the appropriate times.

Save the changes to Controls.py, then either copy the ControlDemo.py and associated HTML file, to

ControlDemo2, or simply change the existing ControlDemo.py to use VerticalDemoSlider2 instead of

VerticalDemoSlider. Recompile the code with this command:

python ../../builder/build.py ControlDemo.py

Open output/ControlDemo.html with a web browser, click-and-hold the mouse anywhere on the

slider and then move the mouse up or down, within its 200 pixel range. The value will change and be

displayed in the Label, below the slider.

Observe, occasionally, that mouse-drag results in selecting text!This Is Bad. It's to do with event handling, and there's a way tostop it.

Mouse events have a default behaviour, in browsers, and a "mouse down" and "drag" results in textselection. To terminate this behaviour, it is necessary to get at the event "early" - before it gets handed

on to the browser's underlying sub-system. Take a look at pyjamas/ui.py and search for "class

DialogBox" and look for the function onEventPreview. Copy those lines, and add them to

VerticalDemoSlider2:

def onEventPreview(self, event): # preventDefault on mousedown events, outside of the # control, to stop text-selection on dragging type = DOM.eventGetType(event) if type == 'mousedown': target = DOM.eventGetTarget(event) event_targets_control = target and \ DOM.isOrHasChild(self.getElement(), target) if event_targets_control: DOM.eventPreventDefault(event) return VerticalDemoSlider.onEventPreview(self, event)

Also, add this line to VerticalDemoSlider2's constructor:

DOM.addEventPreview(self)

It can be seen that, when a "mouse down" event occurs, the "default" behaviour - in this case, text

selection - is prevented, through the use of DOM.eventPreventDefault. However, previewing of

events consumes CPU resources, especially given how many mouse move events there can be. So,

sending every single browser event through a widget should only be done if it is absolutely necessary.

What's been covered

This section added a more user-friendly behaviour to the slider: dragging. Quite specific sections ofcode were cut-and-paste lifted from pyjamas/ui.py's DialogBox and PopupPanel to provide this

behaviour, and the following issues were covered:

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

57 of 61 08-03-2009 18:11

Page 58: Pyjamas the Book

Calling DOM.sinkEvents() to register event types, with the DOM subsystem, that the widget

should be notified of.

Adding an onBrowserEvent() function to receive and handle mouse events, and the

importance of passing on any other events to the base class.

How fireMouseEvent() provides some standard behaviour that makes mouse event handling

easier to write.How to implement dragging, by noting when the mouse is down, and only reacting to mouse move

events when the mouse is down.

The importance of calling DOM.setCapture() to ensure that all events go to the widget whilst the

mouse is down.

The importance of calling DOM.releaseCapture() on mouse-up, to give control back to the

browser!

How and why an onEventPreview() should be used.

In dealing with the issues, from the user requirements of providing mouse "drag" capability, there'ssomething near-fatalistic about the way the code emerged. The code almost wrote itself, according to the

requirements; the rules of the underlying DOM model; the existing Pyjamas API and the way in which

sections of code were borrowed near-verbatim from other widgets.

TODO

Keyboard Events and Keyboard Focus

module ui

NAME

ui

FILE

/home/lkcl/src/pyjamas-desktop/pyjamas-webkit/pyjamas/ui.py

DESCRIPTION

This module implements the Pyjamas user-interface widget set for Pyjamas-Desktop.

Classes

CellFormatter

FlexCellFormatter

DockPanelTmpRow

Event

EventObject

FormSubmitCompleteEvent

FormSubmitEvent

Focus

FocusListener

HasAlignment

HasHorizontalAlignment

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

58 of 61 08-03-2009 18:11

Page 59: Pyjamas the Book

HasVerticalAlignment

KeyboardListener

LayoutData

MouseListener

RowFormatter

UIObject

MenuItem

TreeItem

RootTreeItem

Widget

Composite

ClickDelegatePanel

TabBar

TabPanel

FileUpload

FocusWidget

ButtonBase

Button

CheckBox

RadioButton

ListBox

TextBoxBase

PasswordTextBox

TextArea

TextBox

Frame

NamedFrame

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

59 of 61 08-03-2009 18:11

Page 60: Pyjamas the Book

Hidden

Hyperlink

Image

Label

HTML

MenuBar

Panel

ComplexPanel

AbsolutePanel

RootPanelCls

CellPanel

DockPanel

HorizontalPanel

VerticalPanel

DeckPanel

FlowPanel

HTMLPanel

StackPanel

HTMLTable

FlexTable

Grid

SimplePanel

FocusPanel

FormPanel

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

60 of 61 08-03-2009 18:11

Page 61: Pyjamas the Book

PopupPanel

DialogBox

MenuBarPopupPanel

ScrollPanel

TreeContentPanel

Tree

Functions

HTMLPanel_createUniqueId()

RootPanel(element=None)

The function which is used to obtain a Root Panel, to whichall widgets must ultimately be added.

@param element: element can be None, in which case the default root panel is returned. element can be a string, in which case the widget in the underlying DOM model with the 'id' given by element is returned. element can also be a node in the underlying DOM model. A good way to think of this is in terms of an HTML document. if element is None, the HTML "body" is returned. if element is a string, getElementById('element') is returned.

Data

CheckBoxUnique = 0

FormPanel_formId = 0

HTMLPanel_sUid = 0

prefetchImages = {}rootPanels = {}

Pyjamas Book file:///C:/Documents%20and%20Settings/Administrator/My%20Docum...

61 of 61 08-03-2009 18:11