Developing your own Widgets¶
A programmer should be able to build web applications entirely from
Widget
s 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 Widget
s 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 Widget
s.
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. Widget
s already exist for
most basic HTML elements. Thus, you can easily compose a Widget
from
other Widget
s 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 Widget
s: 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
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 Widget
s 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 SubResource
s 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.