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
HTML5Pageof 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:
write the code for a JavaScript widget (the code re-used by each instance);
include that code on your pages; and
instantiate a JavaScript instance for each rendered instance of your
Widgeton 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.
