Rui (remote user interface) is a set of presentation servers, and compatible presentation clients. Perl Rui is a UI framework for Perl. It allows you to use the Rui client from Perl, and create rich UIs that deploy to web browsers. This document is an introductory guide for Perl developers who want to use Perl Rui. There is a glossary section. Please send questions and suggestions to us.
The links to the example applications in this tutorial assume you have a server installed localy, on port 8888.
Let us look at Hello World in Rui:
package MyProject::Application::HelloWorld; use base 'Rui::Application::Base'; sub start { shift->Label(value => "Hello World") } 1;
Rui applications are Perl packages. The 1st line gives the package a
name. In this case the application class would be found in
$Rui::PROJECT_HOME/perllib/Rui/Application/eg/HelloWorld.pm
.
Rui applications inherit from Rui::Application::Base. The 2nd line establishes this fact.
Rui applications must implement a single template method,
start()
, shown on the 3rd line. In this method we create a
single label inside the application main window (which we get from
@_
using shift
), and set its value to 'Hello
World'.
Click here to run the application.
a Perl Rui project is collection of applications. Everything needed to run the applications of a project lives in one place: the project directory. A project directory can include:
The name of a project directory is the name of the project, and it must
be found under the Perl Rui projects directory, under the Rui home
directory. So project MyProject
should be placed at:
Rui/ : Rui home Perl/ : $Rui::PROJECTS_HOME, Perl Rui projects dir MyProject/ : $Rui::PROJECT_HOME, project dir
A script at
$Rui::PROJECTS_HOME/base/bin/devel/createProject.pl
creates
a new project, empty but working. To create a new project:
createProject.pl
script, giving it the name
of the new project. E.g. createProject.pl myproject
$Rui::PROJECT_HOME/perllib/
): models, views, or other
classes
$Rui::PROJECT_HOME/conf/applications.cfg
$Rui::PROJECT_HOME/bin/servers/server-http.pl
) from
within the project directory
MyApp
in the application map:
http://localhost:8888/perl-rui.html?MyApp|perl
There is more information in the Rui POD.
An application is a window, but there is only one per session. It models the main window. The server instantiates the application object. To create a new application you need to:
$Rui::PROJECT_HOME/conf/applications.cfg
. See Rui::Application::Map
for details
$Rui::PROJECT_HOME/bin/servers/server-http.pl
)
http://server:port/perl-rui.html?HelloWorld|perl
To end the application, call destroy()
on the application
object. The application will also exit when the user closes its main
window. Ending an application closes all of its windows. Click here to
run the application exit demo.
Widgets are created on their parents: an application, a window, or a panel. Any composite will do. You create widgets by calling a method named after their type. Example:
$label = $parent->Button(value => 'click here');
You create a widget with named parameters, so that you can configure everything about it from the constructor, except its children. Example:
$button = $parent->Button( value => 'a button', style => {padding => '2px 3px 1px 2px', color => '#FF0000'}, layoutHint => {minHeight => 100, minWidth => 50}, disabled => 1, tooltip => 'tooltip of a button', events => {Click => sub { shift->source->value('clicked!') }}, );
Or you can use methods for the same result as the example above:
$button = $parent->Button-> value ('a button')-> style (padding => '2px 3px 1px 2px', color => '#FF0000')-> layoutHint (minHeight => 100, minWidth => 50)-> disabled (1)-> tooltip ('tooltip of a button')-> addListener (Click => sub { shift->source->value('clicked!') });
The life of a widget should look something like:
$button->destroy
You can disable widgets and set tooltips on them, using
disabled()
and tooltip()
. You can set focus on
a widget with focus()
.
You can get the application where the widget is running using
getApplication()
. This is useful when you need to send the
application a destroy()
message so that it will exit.
Widgets are styled using the CSS standard:
$button->style(backgroundColor => '#FF00FF', color => '#00FF00');To get a style (assuming it has been set):
$color = $button->style('color');
See Rui::Widget::Base for a list
of valid styles. Note there are some keys you cannot use:
width
, left
, etc. These are keys managed by
the layout manager.
The preferred method to change styles is using CSS stylesheets, not the
style()
method. This way you can define styles once and
reuse them between widgets and applications, and styles can be changed
without changing the source code. You can add a CSS class to any widget
using the cssClass()
method:
$label->cssClass('MyStyleClass');
You define the CSS class so:
.MyStyleClass { margin: 0px 5px 0px 0px; padding: 3px 3px 3px 3px; border: 2px inset threedhighlight; }
You define the CSS class in a .css
file, that you place at
$Rui::PROJECT_HOME/web/style
. You also need to reference
the stylesheet from the launcher page. Here is an example line that
would be added to the head
section of the launcher page:
<link href="style/IE6/MyStylesheet.css" rel="stylesheet" type="text/css">
You do not set size or location explicitly for widgets, unless you are
using the rarely used null
layout manager. Instead you set
a layout manager on the parent composite, configure it, and configure
any layout hints on the child widget. The layout manager will layout the
widget according to its configuration and the hints of the children.
Layout will occur after any relevant change to the composite or to the
children, and when the user resizes the window. The layout algorithm
operates automatically, so there is no need to explicitly start layout.
You set a layout manager on composites using the
layoutManager()
method (or in the constructor hash), with
the name
key. To set a box
layout on a panel:
$panel->layoutManager(name => 'box');
The available layout managers are: row
(default),
box
, null
, grid
, and
fill
. Each defines a different layout algorithm that is
applied to the children of the composite. Each different layout manager
defines properties that you can set on the composite to modify how it
does layout. You set the properties using the
layoutManager()
method (or in the constructor hash).
Example: grid
layout allows you to set the number of
columns:
$parent->Panel(layoutManager => {name => 'grid', numCols => 3});
While the layoutManager()
method sets the layout properties
of the composite, the layoutHint()
method sets hints on a
child. Again the valid keys are defined per layout manager. Example: a
box
layout with a vertical orientation allows you to set
a fixed height for each child:
$panel = $parent->Panel(layoutManager => {name => 'box', orient => 'vertical'}); $panel->Label(value => 'foo', layoutHint => {height => 50});
Margin, padding, and border are set using the style mechanism. Their values will taken into account by the layout manager when sizing and positioning the widget.
The panel POD describes all the layout managers, their properties, and their hints.
Labels, buttons, notebook tabs, and other widgets, allow you to set images on them. All widgets let you set a background image. To set an image on a widget:
$Rui::PROJECT_HOME/web/images
in your project
directory. It can be a GIF, JPG, or PNG, and can be transparent or
animated
$Rui::PROJECT_HOME/web/images/funny/smiley.png
, and it
is 16X16 pixels in size:
$image = Rui::Image->new(file => "funny/smiley.jpg", size => [16, 16]);
$widget->backgroundImage($image)
.
For label images: $button->image($image)
. Or you could
set them in the constructor hash. Item images are configured using
renderers, as explained below
To remove an image, set its value as undef on the host widget:
$label->image(undef)
Widgets are listenable objects.
This means you can add and remove listeners. The listeners will
be called with a single parameter when an event is fired by the
listenable: the event. Widgets define a
name, and an event class, for each event they fire. Example: Rui::Widget::Button defines one event.
Its name is Click
, and the event class is
Rui::Event
. Some widgets are views, and do not fire events.
Intead, their models fire events. textfield is an example. The MVC section shows how to listen to such events.
Here we create a button with a listener that will switch the value of a label whenever the button is clicked:
$label = $self->Label(value => 'foo'); $self->Button( value => 'click to switch label', events => {Click => sub { $label->value($label->value eq 'foo'? 'bar': 'foo') }}, );
Click here to run the above example.
The top level application window is created for you by the server, and
is modelled by the application object. Windows have the same interface as panels. Unlike widgets, they are
constructed from their classes, not from their parent. There are 3 keys
you can use when creating windows: width
,
height
, and isResizable
. You destroy windows
(and all their child widgets) using destroy()
.
Here we create a 640X480 window, set it as resizable, set a layout manager for the top level panel, set the window title to "foo", and add a label to it:
Rui::Window->new( width => 640, height => 480, isResizable => 1, title => 'foo', layoutManager => {name => 'grid'}, )->Label(value => 'foo');
The notebook is a container that shows only one of its pages at a time, and allows the user to select a different page by clicking a tab. Each tab has an associated page. Here we create a notebook with 3 tabs and pages, and put a label in each page:
$noteBook = $window->NoteBook; foreach $i (1..3) { # create a new tab $noteBook->Tab(value => "tab $i"); # create a new page for the tab and put a label in it $noteBook->Page->Label(value => "A Label in tab $i"); }
A notebook has a single event: Selection_Change
. It is a
value event. The value is the
selected index. Here is an example of listening to the event and
updating a label with the selected index:
$notebook->addListener(Selection_Change => sub { $label->value(shift->value) });
Click here to run the notebook example.
Before we look at how Perl Rui implements MVC, we need to understand models. Models are non-visual listenables. A model can hold domain data, but more often it is used as an adapter on some domain model.
The model used most is the value
model. It stores (or adapts) one value, and provides one method
(value()
) to get/set the value and one event
(Change
) to observe. Other models use value models to hold
and adapt their data. See the paper Understanding and Using
ValueModels for more information on how Perl Rui uses value models.
A list model holds (or adapts) a list
of values. You can get fine-grained events (e.g.
RangeRemoved
) when the list changes. A selection in list model
manages a list model, and a selection from the list.
When you create an MVC application, after you create your domain model classes, you create the user interface model. This is a network of Perl Rui model objects, also called the value-net, that models the interactivity of the application UI. On this network you place your views. Thus you do not change views explicitly when events are fired. Instead you configure the models of the views to model the relationships between different parts of the UI. The PhoneBook sample demonstrates how a simple value-net is created between the views and the domain model.
Perl Rui implement the MVC pattern. Complex widgets (e.g. list) are called views. The only difference between them and simple widgets (e.g. label), is that they access their data from a model. While widgets are controlled through their API, views are controlled through their models. The view listens to model events, and modifies its display accordingly. It listens to events from the user, and modifies its model accordingly. Perl Rui does not use controllers (the view itself translate user events into model commands), but you may add them if required.
Each different view expects a different model class: text fields require a value model,
while lists require a selection in
list as their model. You can use model()
to get/set the
model of the view, or you can set it in the constructor hash of a view.
If created without a model, the view will create its own default model.
Views hold their models in a value model called the model holder.
You can configure the model holder as an adapter on some adaptee. The
view will automatically update itself when the adaptee changes, so their
is no need to listen to model events and synchronize views.
Here we create 2 text areas with a shared value model:
$model = Rui::Model::Value->new; $self->TextArea(model => $model); $self->TextArea(model => $model);
Everytime the model changes (when user changes the text and focuses out of the text area) all views observing the model are updated. Thus the text areas will always show the same value. If you need to listen to the change in the text area, listen to the model not the view. However this is rarely needed. More often the value-net takes care of propagating changes through the UI. Click here to run the text area example.
The list and tree view use renderers to render their models. The
renderer is called when the view needs to render an item from the model.
The view uses the information returned by the renderer to render the
item. A renderer is an anonymous subroutine or an object. It is is
called with 1 parameter: the item. If the renderer is an object, the
method render()
is called.
The renderer must return a hash ref. It will be used to construct a label. A list with the following renderer will show the name of the color of the shoe, and an icon if it is a sports shoe:
sub render { my ($self, $shoe) = @_; return { value => $shoe->color->name, image => $shoe->isSports? $images{sportsShoe}: undef, }; }
If you do not specify a rendering, the default rendering will be used.
It tries to run a method value()
on the item. If that
fails, it tries to find a value
key in the item. If that
fails, it treats the item as a scalar. Using the default rendering, all
of the following three lists models will show the same value:
# assuming $foo->value eq 'foo', $bar->value eq 'bar' $list = Rui::Model::List->new(data => [$foo, $bar]); $list = Rui::Model::List->new(data => [{value => 'foo'}, {value => 'bar'}]); $list = Rui::Model::List->new(data => [qw(foo bar)]);
$Rui::PROJECTS_HOME
.
base
. This is where you will find classes and
other resources common to all projects.
base
project, or some user
project. You can get the path using $Rui::PROJECT_HOME
.
$Rui::PROJECTS_HOME/../web/perl-rui.html
, but you can
create other launch pages inside your project dir. You may want to
do this to add stylesheets, authentication, etc.