Writing Grail Applets


On This Page:

See also:

Introduction

Grail applets are stylized Python modules that can be incorporated in HTML pages by using a new tag (explained below). An applet module defines a class that is instantiated by the browser when the applet is invoked. Applets interact with the user by creating one or more Tk widgets. For example, the following class creates a button displaying the image of a question mark (file Question.py):
from Tkinter import *

class Question:

    def __init__(self, master):
        self.button = Button(master,
                             bitmap='question',
                             command=self.action)
        self.button.pack()

    def action(self):
        # Toggle image
        if self.button['bitmap'] == 'question':
            self.button.config(bitmap='questhead')
        else:
            self.button.config(bitmap='question')
It should be stressed that there is nothing Grail-specific in this example module: in fact, it can be run as a stand-alone Python program (while still remaining usable as an applet module) by adding the following lines at the end:
if __name__ == '__main__': # Are we run as a main program?
    root = Tk()
    button = Question(root)
    root.mainloop()
This applet can be invoked by the following HTML page (file test.html):
<HEAD>
<TITLE>Grail Applet Test Page</TITLE>
</HEAD>

<BODY>
<H1>Test an Applet Here!</H1>
Click this button!
<OBJECT CLASSID="Question.py">
If you see this, your browser does not support Python applets.
</OBJECT>
</BODY>
Grail must be able to find the Question class in the module file Question.py. By default, the module name and the class name are assumed to be the same.

The <OBJECT> Tag

The <OBJECT> tag supports "alternative contents". Browsers that don't understand this tag will skip the tags, and display whatever there is between <OBJECT> and </OBJECT>. In the example, this is the string "If you see this, your browser does not support Python applets.". Browsers that do understand it will interpret it, and skip (nearly) everything they encounter until the matching end tag. Thus, it is possible to have an applet displayed by Grail but an image displayed by other browsers.

The <OBJECT> tag is currently being proposed by the W3C, in a W3C working draft entitled Inserting objects into HTML (edited by Dave Raggett). As far as we know, Grail is the first browser to implement this tag. Because of its tentative status, it is possible that details will change in a future release of Grail.

The <OBJECT> element has the following general format:

<OBJECT
    CLASSID="string"
    CODEBASE="string"
    MENU="string"
    WIDTH=number
    HEIGHT=number>

  <PARAM NAME=name VALUE="string">

  ...more <PARAM> tags...

  ...alternative contents...

</OBJECT>

The proposed <OBJECT> tag has additional attributes TYPE, NAME, DECLARE, DATA, ALIGN, VSPACE and HSPACE, which are currently ignored by Grail. The MENU attribute is a Grail specific extension.

Attribute and tag names are case-insensitive; however their values are case-sensitive. Values containing other characters than letters or digits should be enclosed in double quotes. (For the exact rules, consult any HTML manual.) All attributes except CLASS are optional. Attributes can be specified in any order. Each attribute name should be specified at most once.

The <OBJECT> tag describes the code object to be executed. The <PARAM> elements provides runtime parameters to the code object. The contents of the <OBJECT> el ement, with the exception of <PARAM> elements, form the "alternative contents" mentioned above. The alternative contents are only displayed when the attributes of the <OBJECT> tag indicate (without having to do any network traffic) that it is not a Python applet.

The CLASSID and CODEBASE attributes together determine the class and module for the applet. The following combinations are allowed:

Other combinations do not specify a Python applet; the alternative contents of the <OBJECT> element will be displayed.

The class is instantiated with a first argument giving its "master" widget. This is a Tkinter Frame instance specifically created for this applet; its parent is the viewer's text widget. If the WIDTH and/or HEIGHT attributes were present, the frame is given this initial width and/or height.

When the MENU attribute is present, instead of a frame inside the viewer's text widget, the widget's master is a menu in the browser's menu bar with the given name. The menu remains in existence as long as the document that invoked the applet remains visible in the viewer. The widget instance is expected to add entries to this menu that provide commands specific to the document. (In this case, the widget has no "real estate" of its own.)

The name/value pairs specified by the <PARAM> elements are passed as keyword arguments to the constructor of the applet class, with their names converted to lower case. For example, an applet whose class begins as follows:

class Animation:

    def __init__(self, master, image="grail", rate=10):
        ...
could be invoked by any of the following element groups:
<OBJECT CLASSID==Animation.py>
</OBJECT>

<OBJECT CLASSID==Animation.py>
  <PARAM NAME=IMAGE VALUE=python>
</OBJECT>

<OBJECT CLASSID==Animation.py>
  <PARAM NAME=RATE VALUE=4>
</OBJECT>

<OBJECT CLASSID==Animation.py>
  <PARAM NAME=IMAGE VALUE=spam>
  <PARAM NAME=RATE VALUE=30>
</OBJECT>

Value strings that look like numbers are automatically converted to Python numeric values of the appropriate type (int, long or float); all other values are passed as strings.

The <APPLET> and <APP> Tags

The <APPLET> and <APP> tags are provided for backwards compatibility mostly. They are no longer described in this manual.

Widget Types

Grail applets can use all widget types available in the Tkinter module. See Matt Conway's Tkinter Life Preserver document and examples for more about Tkinter. Those are still based on Tk 3.6, however Grail uses Tk 4.0 (or higher). The differences are numerous but most examples still work. The Tkinter Life Preserver is also available on-line in HTML.

Two additional, indispensable sources of information are the Tk manual pages and the Tkinter source. Ousterhout's Tcl/Tk book is of limited value, since it refers to the manual pages for most details.

Interfacing to Grail

Grail applets can access certain objects in the Grail application through special attributes of the master argument passed to every applet instance. The most important objects are the App, the Browser, the Viewer, and the Context. The App object represents the entire Grail application, and contains information shared bewteen all browsers. The Browser object represents a top-level browser window. The Viewer object represents a viewer window. Often, there will be one viewer per browser, but when frame sets or tables are used, there may be several subviewers within the same browser. The Context object, finally, represents an intermediate level. Viewer objects representing table cells share their context with the Viewer object containing the table; however Viewer objects representing a frame have their own context (since each frame displays a separate URL).
grail_app
The Application object. Methods:
.get_cache_keys()
Return a list of URLs that are currently in Grail's cache.
Instance variables:
.global_history
An object representing Grail's global history (the set of URLs you have visited). Methods:
.remember_url(url [, title])
Add a URL to the global history, optionally with a given title.
.lookup_url(url)
Return a tuple (title, timestamp) representing the information in the global history about the given URL. If the URL is not known, return (None, None).
.inhistory_p(url)
Return true or false depending whether the given URL is in the global history.
.urls()
Return a list of all URLs currently in the global history.
grail_context
The Context object for the applet. Methods:
.get_baseurl([url, ...])
Return the base URL for the current document. If one or more URL arguments are give, resolves the first one relative to the base URL, the second one relative to the first one, and so on, and return the final result. Empty URLs are ignored.
.load(url)
Load the given URL into the context's viewer, replacing its current contents. Careful: this will usually cause the current applet to be deleted!
.follow(url)
Like .load(url), but interprets the URL relative to the current document's base URL.
.message(string)
Display a message in the document's message area.
.get_async_image(url)
Return an "asynchronous image object". This is a subclass of a Tkinter PhotoImage which will eventually be loaded with the image whose URL is given. Image objects are shared and should be considered read-only.
grail_browser
The Browser object for the applet. A description of the Browser object may be found in The Browser Object Interface in Grail.

grail_viewer
The Viewer object containing the applet. A description of the Viewer object may be found in The Viewer Object Interface in Grail.

Java Compatibility Kludge

To create a page that works with Grail, with Java compatible browsers (like Netscape 2.0), as well as with other browsers, you can use nested alternative contents: at the outermost level, you have an <OBJECT> tag (for Grail), inside it an <APPLET> tag (for Netscape 2.0), and inside that an IMG tag (for other browsers), for instance:

<!--Rotating earth Python applet-->
<OBJECT CLASSID=earth.py>
    <!--Rotating earth Java applet-->
    <APPLET CODE=earth.class>
        <!--Static earth image-->
        <IMG SRC="earth.gif">
    </APPLET>
</OBJECT>

Local Protocol Handlers

Grail has the capability of having support for new protocols added in the form of a plug-in architecture. The specifics of how this is accomplished is reviewed in the Extending Grail section. This section is worth while reading since applet local protocol handlers are most easily adapted from working plugins.

An applet may wish to override the behavior of the default plug-in protocol handlers for protocol accesses via anchors on the same page (e.g. with the same Context) as the applet. Specifically, an applet may wish to provide interaction with the handler at various stages of resolution and retreval. An applet may wish to override the default error handling. In the example below, a local implementation of the hdlAPI (CNRI Handle System API), the local handler retrieves the handle data from a alternate source if the handle is not found. This type of interaction with the protocol handler is only possible with a locally installed handler.

Local protocol handlers are installed from an applet and are subject to the restrictions of the applets restricted execution environment. This presents some minor porting issues when moving a plug-in API to a local handler.

Once you have a working plug-in protocol handler, porting it to be installed by an applet is fairly straightforward. One method of reusing 'most' of a plug-in protocol handler without copying code, is to try and use the plug-in version of the handler as is, and where it fails (either because of restricted execution environment or because the functionality is lacking), override the effected methods by subclassing.

Here is some excerpts from an example local protocol handler for communication and retrevals from the CNRI handle system. This handler only deals with handles that point to a specific type of data, making the class much simpler than the parent from which it derives.

import hdlAPI
import nullAPI
import hdllib
import hdl_type_nlm         # custom type handlers are imported rather
                            # than installed in $home/.grail/protocols

HDL_API_NAME = 'hdlAPI' # Key used to install the handler

class MEDLINE_hdl_access(hdlAPI.hdl_access):
    
    def __init__(self, hdl, method, params):
         """The init is usually simpler than the generic handler"""
	nullAPI.null_access.__init__(self, hdl, method, params)
	self._hdl, self._attrs = hdlAPI.parse_handle(hdl)
	self._types = hdl_type_nlm.handle_types
	self._formatter = hdl_type_nlm.data_formatter

   def pollmeta(self):
        """This is where you would intercept the various error
           conditions occuring during resolution"""
	nullAPI.null_access.pollmeta(self)
	try:
	    replyflags, self._items = self._hashtable.get_data(
		self._hdl, self._types)
	except hdllib.Error, inst:
	    if inst.err == hdllib.HP_HANDLE_NOT_FOUND:
                # Do some local processing here to fix the error
                # Or, if all else fails...
                raise IOError, "handle not found"

Once the custom handler class is complete, you need to associate it with the page by using the set_local_api API available from grail_context on the applets root object. The first arguement is a string that is used as a key for finding the handler. The convention that grail uses is: lowercase scheme id + 'API'. So for http, the first argument would be 'httpAPI'. The second argument is the class for the local handler. This class is expected to contain interfaces for Grail's protocol API as defined in the plug-in manual. Here's an example local handler for the CNRI handle system protocol:

import MEDLINEhdlAPI
class Controller:
    def __init__(self, root):
        # Install the local protocol handler
	root.grail_context.set_local_api(MEDLINEhdlAPI.HDL_API_NAME,
			            MEDLINEhdlAPI.MEDLINE_hdl_access)

This is assumming the applet is envoked on class 'Controller'.

The applet will install the local handler for the context of the current page. As soon as a link is followed, all local handlers are deleted from the current context. This is to prevent 'secret' local handlers from being perminantly installed from an applet.