Drawing of rye

Perl Rui Developer Guide

Contents

  1. Introduction
  2. The Hello World
  3. Projects
  4. Applications
  5. Widgets
  6. Styles
  7. Sizing and Positioning
  8. Images
  9. Events
  10. Windows
  11. NoteBooks
  12. Models
  13. MVC
  14. Renderers
  15. Glossary
  16. References

Introduction

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.

The Hello World

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.

Projects

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:

  1. Run the createProject.pl script, giving it the name of the new project. E.g. createProject.pl myproject
  2. Add Perl classes to the project Perl library ($Rui::PROJECT_HOME/perllib/): models, views, or other classes
  3. Add applications to the project and add a mapping for each appilcation at $Rui::PROJECT_HOME/conf/applications.cfg
  4. Run the project server ($Rui::PROJECT_HOME/bin/servers/server-http.pl) from within the project directory
  5. Point your browser at the launcher page. For the application named MyApp in the application map: http://localhost:8888/perl-rui.html?MyApp|perl

There is more information in the Rui POD.

Applications

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:

  1. Create a subclass of Rui::Application::Base
  2. Create a mapping from the application name to the application class and place it at $Rui::PROJECT_HOME/conf/applications.cfg. See Rui::Application::Map for details
  3. Restart the project server ($Rui::PROJECT_HOME/bin/servers/server-http.pl)
  4. Test your application in a browser. We named the Hello World application "HelloWorld", so it will be available at the URL 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

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:

  1. You create it from the parent
  2. You call various methods on the widget and set event handlers
  3. You destroy the widget by either destroying one of its containers, or sending the widget itself the destroy message: $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.

Styles

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">

Sizing and Positioning

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.

Images

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:

  1. Place the image somewhere under $Rui::PROJECT_HOME/web/images in your project directory. It can be a GIF, JPG, or PNG, and can be transparent or animated
  2. Create a new image object, and provide it with width, height, and a path to your image file. So if you placed your image in $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]);
    
  3. All widgets allow for background images, label like objects (label, button, tab) allow for a single image shown next to the text, and list like objects (list, tree, table) allow for item images. How you set your image depends on the host widget. For background images: $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)

Events

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.

Windows

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');

NoteBooks

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.

Models

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.

MVC

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.

Renderers

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)]);

Glossary

References

  1. PODs
  2. Rui homepage