The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

IWL::Manual::Tutorial - IWL Introductory Tutorial

DESCRIPTION

This tutorial should provide all the knowledge to build your first application, using IWL

OVERVIEW

Installation

Installation should be pretty straightforward. IWL is available through CPAN: http://search.cpan.org/dist/IWL/ , and only relies on HTTP::Parser as a C module. The rest of the requirements are all in pure Perl. See IWL for the complete list of module requirements.

Once installed, in order for IWL to actually be useful, the client static content (such as JavaScript and CSS files) need to be installed on the server. To do that, the iwl-install script is provided, and will be installed in the server's PATH.

For complete command-line help of the iwl-install script, invoke it with:

  iwl-install --help

The only mandatory argument for the script is for specifying the document root of the application. Optional arguments provide a fine-grained control for placing the various static content, that is needed by IWL, or for setup of a static uri script handler (See IWL::Static)

Example invocation:

  iwl-install -d . -j js -skin skin -static /cgi-bin/static.pl

The above command will install the skin files in /skin, and JavaScript files in /js, all relative to the document root, which is specified as the current directory. Furthermore, the command will setup /cgi-bin/static.pl as the static content handler.

Starting the new application

While IWL was inspired by Gtk2, and tries to mimic it as closely as possible, there is one main difference, which is worth noting. Whereas Gtk2 is aimed at building desktop applications, IWL is geared towards building web applications. With Gtk2, a program's life starts with creating the main window at the beginning of the loop. With IWL, the main window is the page which holds all representable data. In order to create the page, one has to use the IWL::Page class.

Creating a simple Hello World page

Lets begin by creating a Hello World page. The following code does just that:

  #!/usr/bin/perl
  # Using the whole IWL (overkill)
  use IWL;

  # Our page, with an appropriate title, which the user agent will most likely display as the title of its window
  my $page = IWL::Page->new->setTitle("Hello World");

  # The label, with a strong typed "Hello World" in it
  my $label = IWL::Label->new->appendTextType("Hello World", "strong");

  # Adding the label to the body of the page
  $page->appendChild($label);

  # Sending the page as HTML
  $page->send(type => 'html');

As can be seen (in a browser), we just created our first page. It contains the text Hello World in bold letters, which we did by creating a label with a strong text string, and adding it as a child of the page.

The above example illustrates the main concept of IWL. In order to display something, it needs to be created in the form of an IWL::Widget, and added in a hierarchy. Once the hierarchy is fully built, its full contents are obtained. The methods for parent-child management, and obtaining/sending content is available through the base IWL::Object class.

Internally, the send method of IWL::Object uses the IWL::Response class to build a proper response. The response class is designed to build a response for a standard web server by default, but leaves the possibility of creating custom responses for atypical servers.

Creating forms and processing values

While form processing has mostly been replaced by AJAX, it is still useful. So let us create a simple form example.

  #!/usr/bin/perl
  use IWL;

  # Getting the form parameters
  my $rpc = IWL::RPC->new;
  my %params = $rpc->getParams;

  my $page = IWL::Page->new->setTitle('My form');

  # Widget attributes can be set as arguments for the constructor, and as methods
  my $form = IWL::Form->new(action => "form.pl")->setMethod("post");

  # Adding a user input and a checkbox
  my $entry = IWL::Entry->new(name => 'entry');
  my $check = IWL::Checkbox->new(name => 'check');

  # Addding the entry default text, which will be displayed if it holds no value.
  # The default text will be passed by the form on submit.
  $entry->setDefaultText("Type here");
  $entry->setValue($params{entry}) if $params{entry} && $params{entry} ne 'Type here';

  # Adding the checkbox label and value
  $check->setValue("checkbox")->setLabel($params{check} ? "Untick here" : "Tick here");
  $check->setChecked($params{check});

  # Creating a submit button
  my $submit = IWL::Button->new->setLabel("Submit me")->setSubmit;

  # Creating the vertical box container
  my $vbox = IWL::VBox->new;
  $vbox->packStart($entry);
  $vbox->packStart($check);
  $vbox->packEnd($submit);
  $form->appendChild($vbox);
  $page->appendChild($form);

  $page->send(type => 'html');

There are a few things in this example worth mentioning. First, all arguments passed to the constructor, are either options or attributes. If the passed key-value pairs are not options for the widget, they will be added as attributes. This is an alternative syntax to the getters and setters for the same attributes, which are available by the class.

The IWL::RPC object, as can be guessed from the example, is not a widget. It has multiple tasks, some of which will be discussed later. Its main task however, is to provide a method for obtaining the CGI parameters. Here, we use the parameters, passed by the form widgets, to change some aspects of the page.

Playing with signals and events

Modern web application require need to react to various user events, in order to be dynamic and easy to use. In IWL, there are two ways to achieve this.

Signals

Signals are events, which emitted and processed in the client-side portion of an application. They include various user and widget generated events, such as mouse clicks or state changes. They key distinction of signals is that they are processed entirely using JavaScript. Lets look at one example of a button, which is listening for a mouse click:

  $button->signalConnect(click => "some expression here; call_my_function(this)");

There are two methods in IWL::Widget, which manage signals for a given widget. They are signalConnect - for connecting an expression to a signal, and signalDisconnect - for disconnecting an expression, or all expressions from a signal, or all signals of a widget. The expressions are just JavaScript code, which is passed as a string, similarly to the way people write expressions in the corresponding attributes (such as 'onclick') in HTML.

Events

Events are similar to signals, in a way that they correspond to user or widget events. In an essence, every signal also has a corresponding event, but not all events have a signal. The difference, however, is that event for the most part are processed in Perl, and only one event handler can be registered per event per widget. Event registration is handled by IWL::RPC::Request, from which IWL::Widget inherits. Lets modify the above signal example:

  $button->registerEvent('IWL-Button-click', 'my_script.pl', {someParam => 'foo'})

registerEvent registers the script URL with the event name. When the button is clicked, besides emitting a click signal, the corresponding event will also be emitted. Using an XMLHTTPRequest object, the given script will be called with special parameters, which the IWL::RPC::handleEvent will understand. Here is an example of how to handle the above event in my_script.pl:

  IWL::RPC->new->handleEvent(
    'IWL-Button-click',
    sub { },
    ...
  );

The subref for the given event will receive all the parameters which are passed by the event in its course to the script, as well as various other options, which depend on the given signal. For most, but not all signals, the subref arguments will be: parameters hashref, the id string of the widget, which emitted the event, and the collected data, if such was requested. The collected data is a hash of parameters, collected from all control widgets, which are descendants of the widget. Control widgets include entries, spinners, sliders and so on. In an essence, if the event was registered with the collectData option, it makes the emitting widget behave similarly to a form.

The subroutine is expected to return two values. The first one is some data, which is dependant on both the type of handled event, and the specified options for the event upon registration. The second one is an extra hashref of options, which some events need for further process on the client-side.

Most events, based on signals, can handle multiple type of data values. For these events, the subroutine can return an IWL::Object, a hash or array reference, or a string. There are three ways this data is then handled. First, if the update option was specified when registering the event, an existing widget in the page will be updated with that data. Second, if the widget has some specific handler on the client-side, that handler process this data. Finally, if the user specifies a JavaScript expression in an onComplete option during registration, that expression will be evaluated with three variables in its context: json - a JavaScript object, which holds the returned data and extra options, params - the params, as seen in the handler subroutine, and the event options. Here is a complete example of both a registration and handling of an event:

  $button->setId('button');
  $button->registerEvent('IWL-Button-click', 'my_script.pl', {someParam => 'foo'}, {onComplete => 'completeResponse(json, params)'});

  # my_script.pl
  IWL::RPC->new->handleEvent(
    'IWL-Button-click',
    sub {
        my ($params, $id) = @_;
        return IWL::Label->new->setText("The button was pressed with: " . $params->{someParam})
            if ($id eq 'button');
    }
  );
 
  # some javascript file
  function completeResponse(json, params) {
      if (!json) return;
      IWL.createHtmlElement(json.data, document.body);
  }

From the code, if the button with id button is clicked, it will emit an event to the my_script.pl with parameters, containing someParam. In the script, the subroutine handler will catch that event, create a new label with some text, and return it. Finally, when the response arrives on the client-side, the function completeResponse will be called, and it will add that label to the document.

AUTHOR

  Viktor Kojouharov

WEBSITE

http://code.google.com/p/iwl

LICENCE AND COPYRIGHT

Copyright (c) 2006-2008 Viktor Kojouharov. All rights reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perldoc perlartistic.

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.