2009-06-28
Pyjamas, in the most basic of terms, is a port of the Google Web Toolkit (which uses Java), to Python. What does this really mean? according to the Pyjamas website: pyjamas is a stand-alone python to javascript compiler, an AJAX framework / library and a Widget set API.
With Pyjamas, a developer can write a Python application that contains buttons, labels, and various other common widgets; and then compile the application to a web ready pile of javascipt code. Since the javascript code handles all of the cross-browser crap, the developer can focus on writing a decent app and not have to worry about all of the various browser idiosyncrasies.
As an example, I have created a very basic Pyjamas app that will do the following:
The python code is
Looks like a typical Python app to me!
The compiled web application is viewable at www.jezra.net/code_examples/pyjamas_devil/index.htm
As with most things, there were a few gotcha's that I ran into and I had a devil of a time finding the solutions. The first, and in my mind the most important gotcha is that when making an HTTPRequest().asyncPost(), one needs to add "1" as the last argument if the returned data is XML. If the "1" is omitted, the returned data is treated as plain text and any attempt to process the data as XML will fail.
The second biggest gotcha also deals with XML. Since there is no discernable documentation for processing data from returned XML, one might need to write custom functions, based on the few available Pyjamas examples that deal with HTTPRequests, for retrieving data from a chunk of XML. Although this isn't really that major of an issue, but it would be more efficient to use native XML processing and not have to reinvent the wheel.
All things considered, I certainly plan to use Pyjamas in the future whenever I need to write an asyncronous web-app.
With Pyjamas, a developer can write a Python application that contains buttons, labels, and various other common widgets; and then compile the application to a web ready pile of javascipt code. Since the javascript code handles all of the cross-browser crap, the developer can focus on writing a decent app and not have to worry about all of the various browser idiosyncrasies.
As an example, I have created a very basic Pyjamas app that will do the following:
- asyncronously read data from an XML file containing part of the Devil's Dictionary by Ambrose Bierce.
- populate a listbox with the dictionary words
- display the word's definition when a word is selected
The python code is
from pyjamas.ui.RootPanel import RootPanel from pyjamas.ui.Panel import Panel from pyjamas.ui.VerticalPanel import VerticalPanel from pyjamas.ui.HorizontalPanel import HorizontalPanel from pyjamas.ui.HTML import HTML from pyjamas.HTTPRequest import HTTPRequest from pyjamas.ui.Label import Label from pyjamas.ui.ListBox import ListBox #what do we need to process returned XML? #make a XMLHTTPRequest handler to process the dictionary XML class processDictionaryXMLHandler: def __init__(self,caller): #who called this class? (AKA parent) self.caller = caller def onCompletion(self, xml): self.caller.set_message("processing data...") '''process the text''' elements = xml.getElementsByTagName("element") for i in range(elements.length ): element = elements.item(i) word = self.getChildNodeValue(element,"word") desc = self.getChildNodeValue(element,"desc") self.caller.add_dictionary_item(word,desc) self.caller.set_message("Adding "+word) self.caller.update_list() self.caller.set_message("") def onError(self, text, code): self.caller.set_message("error %s %s" % (code,text)) def onTimeout(self, text): self.caller.set_message("timeout %s" % text) #get the value of a nodes child def getChildNodeValue(self,node,childname): childNode=node.getElementsByTagName(childname).item(0) if childNode.firstChild.nodeValue != null: value = childNode.firstChild.nodeValue else: value="" return value #get the value of a node def getNodeValue(self,node): value = node.firstChild.nodeValue return value #define devils dictionary application class class ddApplication: def __init__(self): '''make some global variables''' #we need a dict type to hold key/value pairs for words self.words_dict = {} #create a vertical panel to hold most of our infoself.vPan = verticalPanel() self.vPan = VerticalPanel() #make a label for "devils dictionary title = Label("The Devil's Dictionary (L through Z)") #give credit to the author by = Label("by Ambrose Bierce") #give titles to the labels so we can style them with a CSS title.setStyleName("dd_title") by.setStyleName("dd_by") #add the labels to the vertical panel self.vPan.add(title) self.vPan.add(by) #create a listbox to display the words self.wordsListBox = ListBox() #make the wordslistbox a dropdownlist self.wordsListBox.setVisibleItemCount(20) #hide the wordslistbox self.wordsListBox.setVisible(False) #what should happen when the wordslistbox changes? self.wordsListBox.addChangeListener(getattr(self,"word_list_changed") ) #add the wordsListBox to the vertical panel #self.vPan.add(self.wordsListBox) #make an HTML text thingy to hold the definition of the word self.definitionHTML = HTML("") #hide the definition self.definitionHTML.setVisible(False) #add the definition to the vert panel #self.vPan.add(self.definitionHTML) hPan = HorizontalPanel() #add some spacing for the hPan elements hPan.setSpacing(10) hPan.add(self.wordsListBox) vPan2 = VerticalPanel() self.wordLabel = Label("") vPan2.add(self.wordLabel) vPan2.add(self.definitionHTML) hPan.add(vPan2) self.vPan.add(hPan) #make a label to pass messages to the user in case of a problem self.messageLabel = Label() #set the style for the messageLabel self.messageLabel.setID("info_label") #add the messageLabel to the vert panel self.vPan.add(self.messageLabel) #add the vertical panel to a specific div of the template RootPanel("content").add(self.vPan) def word_list_changed(self): #get the current index of the wordslistbox selected_index = self.wordsListBox.getSelectedIndex() word = self.wordsListBox.getValue(selected_index) self.display_definition(word) def set_message(self,message): self.messageLabel.setText(message) def display_definition(self,word): self.wordLabel.setText(word) definition = self.words_dict[word].strip() self.definitionHTML.setHTML("<pre>"+definition+"</pre>") self.definitionHTML.setVisible(True) def add_dictionary_item(self,word,description): self.words_dict[word]=description def update_list(self): for key in self.words_dict.keys(): self.wordsListBox.addItem(key) #display the wordListBox self.wordsListBox.setVisible(True) def run(self): self.get_dictionary_xml() def get_dictionary_xml(self): self.set_message("retrieving XML data...") HTTPRequest().asyncGet(None,None,"dictionary.xml",processDictionaryXMLHandler(self),1); if __name__ == '__main__': app = ddApplication() app.run()
Looks like a typical Python app to me!
The compiled web application is viewable at www.jezra.net/code_examples/pyjamas_devil/index.htm
As with most things, there were a few gotcha's that I ran into and I had a devil of a time finding the solutions. The first, and in my mind the most important gotcha is that when making an HTTPRequest().asyncPost(), one needs to add "1" as the last argument if the returned data is XML. If the "1" is omitted, the returned data is treated as plain text and any attempt to process the data as XML will fail.
The second biggest gotcha also deals with XML. Since there is no discernable documentation for processing data from returned XML, one might need to write custom functions, based on the few available Pyjamas examples that deal with HTTPRequests, for retrieving data from a chunk of XML. Although this isn't really that major of an issue, but it would be more efficient to use native XML processing and not have to reinvent the wheel.
All things considered, I certainly plan to use Pyjamas in the future whenever I need to write an asyncronous web-app.
Comments
I'm not sure what you mean by "retrieving data from a chunk of XML", but this sounds like some sort of parsing to me and in my opinion this doesn't belong on the client side, javascript is a quirky weird langauge for doing "real programming stuff" as I have found at times in the past, you should use the server side python for any parsing like this and have it send the data to the client side in as controlled a manner as possible, just trying to make javascript (its still converted to javascript at the end from your python code) parse random XML, etc sounds like a recipe for disaster to me
Rick, I was indeed referring to XML processing, which I've never really like in any language. However, by transferring data as XML, the data is being formatted and transferred in a very controlled manner and this allows me to programmatically retrieve the data that I need.