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 Widget
s so that others can reap the
benefit of re-use of these Widget
s.
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 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 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.
Adding JavaScript¶
To add JavaScript to the mix, you use something akin to a class, but
written in JavaScript in which you write all code that needs to be
executed in the browser for your Widget
. This ‘JavaScript class’ is
instantiated on the browser for the HTML of each instance of your
Python Widget
present on a page.
If a Widget
needs a ‘JavaScript class’ to be instantiated on the
browser end, it achieves this by implementing a special method. This
method (.get_js()) returns a small snippet of JavaScript code which will
instantiate the JavaScript for that Widget
on a page when executed. When
rendering an HTML5Page
, the framework collects all these snippets
of JavaScript from all Widget
s on that page and include them on the
page in a <script>
element. This takes care of instantiating the
JavaScript side of Widget
s.
For this to work, the code implementing each ‘JavaScript class’ also
needs to be included somewhere on the page – this is the code that is
called in the ‘instantiating’ snippets. Code for such ‘JavaScript
classes’ is written in a separate .js files. Each such .js file is
listed in the .reahlproject file of the component containing
it. When a web application is started, Reahl builds one .js file
that contains all .js files of all the components used by your
application and makes the big file available via an URL that is
included in any HTML5Page
via a <script type="text/javascript">
element. (This file is thus the bulk of JavaScript code, and it will fetched
once by browsers and then cached for other pages.)
That’s really all there is to it... Some more details to make it more concrete:
Reahl uses the so-called Widget Factory from the JQuery UI project to simplify the development of the equivalent of a class, but written in JavaScript. If you don’t know the Widget Factory, here’s how it works: the Widget Factory is a function called widget(). You call widget() with the name of the JavaScript class to be created and a JavaScript object that contains a function for each method you want on the ‘JavaScript class’. The special function _create() is the constructor of the ‘JavaScript class’.
What the Widget Factory does is to add a function to the JQuery object which will create an instance of your ‘JavaScript class’ for each HTML element selected by a normal JQuery selection.
For an example, have a look at the implementation of LabelOverInput
. A
LabelOverInput
looks like an Input
with its label printed where you’d
expect a user to type some input. When a user clicks there to supply
some input, the label disappears. The HTML for LabelOverInput
is
basically a <span> that contains a <label> as well as another
<span> that wraps the actual <input> at stake.
To make this plan work, the LabelOverInput
needs some CSS for
positioning the <label> on top of the <input>. It also needs some
JavaScript to make the label appear and disappear at the right moments.
Here is the ‘JavaScript class’ for LabelOverInput
:
/* Copyright 2013, 2014 Reahl Software Services (Pty) Ltd. All rights reserved. */
/*
This file is part of Reahl.
Reahl is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation; version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* implements plan: http://www.alistapart.com/articles/makingcompactformsmoreaccessible */
(function($) {
"use strict";
$.widget("reahl.labeloverinput", {
_create: function() {
var o = this.options;
this.element.addClass("reahl-labeloverinput");
this.element.children().filter("label").each( function() {
var label = $(this);
// var input = label.siblings('*[name=' + label.attr("for") +']');
var span = label.siblings('span:first');
var input = span.find('*[name=' + label.attr("for") +']');
label.bind("click", function() {
input.focus();
});
input
.bind("focus", function(){
if ( $(this).val() == "" ) {
label.attr('hidden', 'true');
}
})
.bind("blur", function(){
if ( $(this).val() == "" ) {
label.removeAttr('hidden');
}
});
if ( input.val() == "" ) {
label.removeAttr('hidden');
}
else {
label.attr('hidden', 'true');
}
});
}
});
$.extend($.reahl.labeloverinput, {
version: "1.8",
});
})(jQuery);
In the code sample only one method is needed: _create(). This method
is the constructor of our ‘JavaScript class’. The variable this is
bound to an instance of our JavaScript class inside the
functions. Similarly, this.element refers to the HTML element to
which this instance of the JavaScript class is bound (the outer
<span>, which represents a complete LabelOverInput
in HTML).
On the Python side, the Widget
needs the .get_js() method that
should return a small line of JavaScript needed to instantiate the
‘JavaScript class’, browser side. Using Jquery and the JQuery UI
widget function, this is something small, like:
def get_js(self, context=None):
return ['$(".myclass").labeloverinput();']
The Python implementation of LabelledInlineInput
does not illustrate
how the HTML is composed, since it inherits all of that from
LabelledInlineInput
. It does illustrate attaching of JavaScript well
though, since adding an HTML class attribute, JavaScript and CSS is
all it really does:
class LabelOverInput(LabelledInlineInput):
"""A :class:`LabelledInlineWidget` that shows the Label superimposed over the Input itself.
The label is only visible while the Input is empty.
.. admonition:: Styling
Rendered like a :class:`LabelledInlineWidget` with reahl-labeloverinput appended to the class
of the containing <div> element.
:param html_input: (See :class:`InputLabel`)
:param css_id: (See :class:`HTMLElement`)
"""
@property
def attributes(self):
attributes = super(LabelOverInput, self).attributes
attributes.add_to('class', ['reahl-labeloverinput'])
return attributes
def make_label(self, for_input):
class AutoHideLabel(Label):
@property
def attributes(self):
attributes = super(AutoHideLabel, self).attributes
if self.for_input.value != '':
attributes.set_to('hidden', 'true')
return attributes
return AutoHideLabel(self.view, for_input=for_input)
def get_js(self, context=None):
js = ['$(%s).labeloverinput();' % self.contextualise_selector('".reahl-labeloverinput"', context)]
return super(LabelOverInput, self).get_js(context=context) + js
Adding CSS¶
Sometimes it is necessary to make some CSS also part of the
implementation of a Widget
. While the end-user may want to add
site-specific CSS to deal with the fonts, colors and sizes of an item,
sometimes the client-side functionality depends on a little bit of CSS.
The LabelOverInput
above is again an example of that. It needs some
CSS to help hide its label. CSS is attached in a similar way to how
JavaScript is attached. It is written in a file for that Widget
, and
registered in a .reahlproject from where the framework can pick up
all the bits of CSS for a web application and serve it up as one file.
To attach the CSS to the HTML, the CSS would also have to reference
the class attribute of a piece of HTML. The CSS for LabelOverInput
is
as follows:
/* Copyright 2013, 2014 Reahl Software Services (Pty) Ltd. All rights reserved. */
/*
This file is part of Reahl.
Reahl is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation; version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*------------------------------------------------------------------- LabelOverInput */
.reahl-labeloverinput {
position: relative;
}
.reahl-labeloverinput > label[hidden] {
position: absolute;
left: -99999999px;
}
.reahl-labeloverinput > label {
cursor: text;
position: absolute;
bottom: 0;
left: 0;
z-index:1;
}
.reahl-labeloverinput label.error {
position: absolute;
top: 1.5em;
left: 0;
z-index: 2;
}
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
low-level stuff – it must just happen.
When a Widget
is instantiated, it can register so-called SubResource
s
with the framework. A SubResource
can be a static file which is
reachable via an URL, or a method that can be called server-side by
visiting (or posting to) an URL, for example. The URLs of the
SubResource
s of a particular instance of a Widget
will all be made
available underneath the URL of all the View
s on which the Widget
is
used: If a Widget
appears on a View
with URL ‘/a/b’, the URL for a
SubResource
of a Widget
will be something like
‘/a/b/__my_small_pic’. (Each SubResource
has to have a unique name
from which its URL is derived.)
SubResource
URLs can also be parameterised, just like a View
can be
parameterised. This has the effect that the SubResource
is only
created once its URL is actually accessed.