Developing your own Widgets

A programmer should be able to build web applications entirely from Widgets that are available, without ever having to think about the underlying technologies. That is what Reahl is about. However, it is possible for programmers to directly use all the lower-level web technologies in order to create novel Widgets that can be re-used from Python.

This is obviously an advanced topic, and a big one which falls outside the scope of this tutorial. Knowing a little about what is possible on that level is useful though: it gives you a better sense of how Reahl works, and how it is different from other Widget-sporting frameworks.

This section explains the gist of what is possible, without the gory details.

What is a Widget, really

If you search web designer and web development forums, you will find a lot of tricks that are presented in order to achieve a certain effect on a web page. These tricks invariably use a combination of web technologies: you may have to write a bit of HTML and a bit of CSS and a bit of JavaScript to make it work. You may also have to provide some server-side code, exposed via one or more URLs.

Imagine if you can take all the bits of code and URLs in all the various languages that make up one trick and package it in a way that not only makes it reusable, but that hides all that stuff and make the programmer’s world consist of Python only.

That is the dream fuelling Reahl Widgets.

To fulfill the dream, the developer of a low-level Widget needs to be able to use all those web development tricks, and wrap them in something with the face of a Python Widget class.

For a start, a Widget is built around its Python class. Server-side logic goes into the Python class but the Python class is also responsible for generating some HTML. Widgets already exist for most basic HTML elements. Thus, you can easily compose a Widget from other Widgets to achieve the required basic HTML representation wrapped in a Python class.

Shipping JavaScript and CSS code

JavaScript and CSS code that works in concert with the Python code of your Widget is written in separate files. Remember, such code must be sent to the client browser to be executed on that side.

There are two aspects to this problem:

packaging:

You need a way to include CSS/JavaScript files to be packaged with your Python egg.

inclusion on a web page:

You need a way to include each CSS/JS file on every HTML5Page of your app.

The reahl.web.libraries module includes the tools you need to fulfill these tasks.

In short, you just put your JavaScript or CSS files somewhere in the directory of one of your Python packages. Then you create a Library subclass which represents your project’s JavaScript and CSS code. (The frontend-code of your project.)

Lastly, you need to change your configuration to include your new Library. This is done in file file web.config.py:

from some.module import MyLibrary

web.frontend_libraries.add(MyLibrary())

Once this is done your CSS and JavaScript will be present on any HTML5Page.

Adding CSS

Adding CSS to your own Widget is really simple. You just include CSS (as explained above) which uses a CSS selector that will target the HTMLElement representing your Widget.

For example, in your Python class you can do this:

class MyWidget(Widget):
    def __init__(self, view):
        super(MyWidget, self).__init__(view)
        self.div = self.add_child(Div(view))
        self.div.append_class('mywidget')

Then add CSS by including a CSS file in your Library containing:

div.mywidget: { border: 1px solid black; }

JavaScript + Python: a pattern

JavaScript needs a bit more thought:

Here’s how we think of Widgets: The Python class represents the Widget as a whole. The Python class also is responsible for generating HTML and to handle any server-side logic.

However, there is a part of that Widget that is executed on the client browser as JavaScript. This JavaScript conceptually also is part of the Widget – it is its JavaScript half, so to speak.

Wouldn’t it be nice if you can create a JavaScript instance in the browser for each instance of your Widget that is present on a page? Well, it turns out you can.

There are three steps to this:

  1. write the code for a JavaScript widget (the code re-used by each instance);

  2. include that code on your pages; and

  3. instantiate a JavaScript instance for each rendered instance of your Widget on page load.

Since we’ve already covered distributing and including JavaScript code on your pages, let’s skip that middle step here. As for the other two:

Write code for a JavaScript widget

What works quite well with such code is to put the JavaScript half of your Widget into what JQuery UI calls a “JavaScript widget”. This is something that is very much like a class in JavaScript that is attached to an HTMLElement which represents your Widget in the browser DOM.

Here is an example of how we do it:

(function($) {
"use strict";

$.widget("mywidgetjs", {
    options: {
       message: 'a default message to show'
    },

    // This is like __init__ for your JavaScript half. It will be called to initialise each instance.
    _create: function() {
        // in the scope of such a "method" the following holds:
        //     the variable "this" is the javascript widget's instance... just like Python's self:
        this.anothermethod();

        //     this.options is the options the widget was constructed with:
        alert(this.options.message);

        //     and this.element is a Jquery Query on the element to which the JavaScript widget was attached:
        this.element.addClass("mywidget");
    },
    anothermethod: function() {
    }
});

$.extend($.mywidgetjs, {
    version: "1.8"
});

})(jQuery);

Instantiating JavaScript instances

Your Widget must be represented in HTML by some HTMLElement. Let’s assume it is a Div for argument’s sake.

If you give such a Div a css class, say ‘mywidget’, you can instantiate its JavaScript half (done in the previous section) using JavaScript like this:

$("div.mywidget").mywidgetjs("{message:'Hello there'}")

Reahl has a mechanism by which a Widget can register such a small JavaScript snippet for execution on page load. The Python class of your Widget only needs to implement a reahl.web.fw.Widget.get_js() method. Reahl collects all such JavaScript snippets for all Widgets on a page and makes sure they are executed at page load time.

Here is an example:

class MyWidget(Widget):
    def __init__(self, view):
        super(MyWidget, self).__init__(view)
        self.div = self.add_child(Div(view))
        self.div.append_class('mywidget')

    def get_js(self, context=None):
        my_snippets = ['''$("div.mywidget").mywidgetjs("{message:'Hello there'}")''']
        return super(MyWidget, self).get_js(context=context) + my_snippets

Different instances with different options

If you have several MyWidgets on a page, our example so far will create a JavaScript widget instance for each of them because it uses a JQuery selector that will find them all (“div.mywidget”).

This is very economical because Reahl will ensure that the JavaScript snippet is only included once on the page, not once per MyWidget instance.

However, if you need to send different options to different Widget instances, you may have to choose a different selector that will target each instance individually. For example:

class MyWidget(Widget):
    def __init__(self, view, unique_name, message):
        super(MyWidget, self).__init__(view)
        self.div = self.add_child(Div(view))
        self.div.set_id(unique_name)
        self.message = message

    def get_js(self, context=None):
        my_snippets = ['''$("div#%s").mywidgetjs("{message:'%s'}")''' \
                          % (self.unique_name, self.message)]
        return super(MyWidget, self).get_js(context=context) + my_snippets

Adding server-side URLs

Let’s assume you are working on a web application that allows users to store and browse Photos online. Perhaps you’d like to show smaller “thumbnail” versions of photos on some overview page of sorts. You would need a Thumbnail Widget for this.

In order to show a small picture, the HTML for a Thumbnail Widget would need to include an <img> element, and the src attribute of that element needs an URL to be available on the server from where it will fetch a shrinked version of the original image. Users of your Thumbnail Widget do not want to know about this though: it is web-implementation details that should be hidden.

When a Widget is instantiated, it can register a so-called SubResource with its current View. A SubResource represents an URL controlled by your Widget. What it does is up to you. Reahl already includes different kinds of SubResources to be able to deal with forms, validation, ajax and all manner of things. A RemoteMethod, for example exposes a method on a Python instance on the server – but it can be invoked by POSTing to its URL.

In the example above, you may use a RemoteMethod which queries the database for the thumbnail version of a given photo – and then returns the thumbnail as a .png file.

The bottom line here is that the Widget creates its own SubResource so that users of the Widget need to be aware of this extra server-side URL that is needed to make things work. The Widget also can use the URL of the SubResource it created in places like the src of an Img. All of these implementation details is thus hidden from the programmer who just needs to instantiate the Widget without concern for its extra hidden URLs.

SubResource URLs can also be parameterised, just like a View can be parameterised by passing parameters via its Url.