NAME

POE::XUL - Create remote XUL application in POE

SYNOPSIS

use POE;
use POE::Component::XUL;

POE::Component::XUL->spawn( { apps => {   
                                    Test => 'My::App',
                                    # ....
                            } } );
$poe_kernel->run();

##########
package My::App;
use POE::XUL::Node;

sub spawn
{
    my( $package, $event ) = @_;
    my $self = bless { SID=>$event->SID }, $package;
    POE::Session->create(
        object_states => [ $self => 
            [ qw( _start boot Click shutdown other_state ) ] ],
    );
}

#####
sub _start {
    my( $self, $kernel ) = @_[ OBJECT, KERNEL ];
    $kernel->alias_set( $self->{SID} );
}


#####
sub boot {
    my( $self, $kernel, $event ) = @_[ OBJECT, KERNEL, ARG0 ];
    $self->{D} = Description( "do the following" );
    $self->{W} = Window( HBox( $self->{D}, 
                               Button( label => "click me", 
                                       Click => 'Click' ) ) );
    $event->finish;
}

#####
sub Click {
    my( $self, $kernel, $event ) = @_[ OBJECT, KERNEL, ARG0 ];
    $event->done( 0 );
    $kernel->yield( 'other_state', $event );
}

sub other_state {
    my( $self, $kernel, $event ) = @_[ OBJECT, KERNEL, ARG0 ];
    $event->wrap( sub {
            $self->{D}->textNode( 'You did it!' );
            $self->{W}->firstChild->appendChild( $self->{B2} );
        } );
    $event->finished;
}

#####
sub shutdown {
    my( $self, $kernel, $SID ) = @_[ OBJECT, KERNEL, ARG0 ];
    $kernel->alias_remove( $self->{SID} );
}

DESCRIPTION

POE::XUL is a framework for creating remote XUL applications with POE. It includes a web server, a Javascript client library for Firefox and a widget toolkit in Perl.

POE::XUL uses mirror objects. That is, each XUL node exists as a Perl object in the server and as a DOM object in the client. A ChangeManager on the server and the javascript client library are responsible for keeping the objects in sync. Note that while all node attribute changes in the server are mirrored in the client, only the most important attributes (value, selected, ...) are mirrored from the client to the server.

POE::XUL currently uses a syncronous, event-based model for updates. This will be changed to an asyncronous, bidirectional model (comet) soon, I hope.

XUL is only supported by browsers from the mozilla project (Firefox and xulrunner). While this limits POE::XUL's use for general web application, POE::XUL would make for some very powerful intranet apps.

NOTE: POE::XUL should be considered alpha quality. While I have apps based on POE::XUL in production, the documentation is probably incomplete and this API will probably change.

POE::XUL is a fork of Ran Eilam's XUL::Node. POE::XUL permits the async use of POE events during event handling. It also removes the use of the excesively slow Aspect and the heavy XML wire protocol. POE::XUL::Node's API is closer to that of a DOM element. POE::XUL has rudimentary support for sub-windows. XUL::Node's (IMHO) dangerous autoloading of XUL::Node::Applications packages has been removed.

The application

POE::XUL applications generaly have one POE session per application instance. The POE session is created when a boot request is recieved from the client. The session then must handle a 'boot' event, where-in it creates a Window node and its children nodes. The session is kept active, handling the user events it has defined, until the users stops using it, that is a period of inactivity. The session is then sent a 'timeout' event followed by a 'shutdown' event.

Because every application stays in-memory for the entire duration of the application, you will probably want to set up a HTTP proxy front-end with process affinity.

It might also be possible to have multiple POE::XUL applications with-in one session. Tests needed.

XUL nodes

If you are not familiar with XUL, you should read http://www.xulplanet.com/tutorials/xultu/intro.html. You should also keep http://developer.mozilla.org/en/docs/XUL handy.

XUL nodes are created and manipulated with POE::XUL::Node. Each application must create a Window node and all its children.

Layers

There are many layers POE::XUL. Maybe too many.

First off, the browser or xulrunner loads start.xul?AppName, which loads the Javascript client library and any necessary CSS. The client library sends a boot event to the server using prototype.js. POE::Component::XUL handles HTTP requests in the server. For a boot request, it creates a POE::XUL::ChangeManager for the application which is used by the event to capture any changes to POE::XUL::Node. The controler then spawns the application and calls its boot state. All nodes created during the boot request will have been noticed by the change manager. These nodes are converted into JSON instructions by the ChangeManager, which are sent as the HTTP response. The JS client library decodes the JSON instructions, populating the XUL DOM tree with the new nodes.

The user then interacts with the XUL DOM, which will provoke DOM events. These events are turned into an AJAX request by the JS client library. POE::Component::XUL decodes these requets and hands them to the POE::XUL::Controler. The Controler creates and populates an POE::XUL::Event. The Event will get the ChangeManager to handle any event side-effects, such as setting value of the target node. The Event will then call any user-defined callbacks or postbacks. When the event is finished, the ChangeManager converts any changes to the POE::XUL::Nodes to JSON instructions, which are sent as the HTTP response. The JS client library decodes the JSON instructions, modifying the XUL DOM tree as necessary.

Understand? Myabe the following diagram will help:

                                    User
                                     |
Firefox or xulrunner              DOM Node
                                     |
                              +------+------+
                             /               \
JS client library          Event           Response
                            \/                /\
HTTP/AJAX                 Request            JSON
                            \/                /\
POE::Component::XUL       decode              ||
POE::XUL::Controler       create Event        ||
POE::XUL::Event           side effects       flush
POE::XUL::ChangeManger    record changes -> convert

XBL

You are encouraged to create your own XUL nodes with XBL. To do so, you will need a custom start.xul that loads the CSS that defines your XBL. To create the nodes with POE::XUL::Node

POE::XUL EVENTS

The life of an application is controled by 1 package method and 2 or more POE events.

spawn

sub spawn {
    my ( $package, $event ) = @_;
    my $SID = $event->SID;
    POE::Session->create( #... );
}

Not actually an event! This is a package method that will be called to create a new application instance. It must set the session's alias to the application's SID, available via $event->SID.

All furthur communication with the application instance happens by posting POE events to the SID.

boot

sub boot {
    my( $self, $event ) = @_[ OBJECT, ARG0 ];
    # create a POE::XUL Window and other nodes.
}

Once the application's session has been spawned, a boot event is sent. This event must create at least Window with POE::XUL::Node. It should also create all necessary child nodes.

timeout

sub timeout {
    my( $self, $SID ) = @_[ OBJECT, ARG0 ];
    # ....
}

Called after the application has been inactive (no events from the client) for longer then the timeout value. No action is required.

shutdown

sub timeout {
    my( $self, $kernel, $SID ) = @_[ OBJECT, KERNEL, ARG0 ];
    $kernel->alias_remove( $SID );
    # ....
}

Posted when it is time to delete an application instance. This is either when the instance has timed-out, or when the server is shutting down.

The session is expected to remove all references (aliases, files, extrefs, ...) so that the POE kernel may GC it.

SUB-WINDOWS

SUB-WINDOW SUPPORT IS STILL EXPERIMENTAL. As such, it could very well change in a future version.

Sub-window support is complicated because POE::XUL is synchronous, event-based. This means that changes to a node must be done during an event that is dispatched from the relevant window.

connect

sub connect {
    my( $self, $event ) = @_[ OBJECT, ARG0 ];
    # create a POE::XUL Window and child nodes.
}

Posted from a new sub-window when it has been created. This is similar to boot, in that you must create a POE::XUL::Node Window and child nodes.

disconnect

sub disconnect {
    my( $self, $event ) = @_[ OBJECT, ARG0 ];
    # Delete the POE::XUL Window and its child nodes.
    # But don't send those instructions to the main window
    pxInstruction( 'empty' );
}

Posted from the main window when a sub-window is closed. You should delete all nodes related to the sub-window. But, because the event

DOM EVENTS

After the boot event, further interaction happens via callback events that you defined on your nodes. A callback may be a coderef or a POE event.

Note that POE::XUL events to not bubble like DOM events do.

Click

The most important event. Happens when a user clicks on a button. The application will react accordingly. See "Click" in POE::XUL::Event for more details.

Change

A less important event, Change is called when the value of a TextBox has changed. The application does not have to update the source node's value; this is a side-effect handled by the ChangeManager. See "Change" in POE::XUL::Event for more details.

Select

See "Select" in POE::XUL::Event for more details.

Pick

Called when the users selects a colour in a Colorpicker, Datepicker or other nodes. See "Pick" in POE::XUL::Event for more details.

POE::XUL::Event and POE::XUL::ChangeManager

Only changes that are wrapped in an Event will be seen by the ChangeManager and be mirrored in the client. POE::XUL::Event will wrap the initial event and call it with "call" in POE::Kernel. If you wish to post further POE events, you must set the Event's done to 0, and wrap any node changes with "wrap" in POE::XUL::Event. You must call "finished" in POE::XUL::Event to complete the request.

"wrap" in POE::XUL::Event also provides error handling; if your code dies, the error message will be displayed in the browser.

TODO

POE::XUL is still a work in progress. Things that aren't done:

Keepalive

If a keepalive request was sent ever X seconds, the application timeout could be much shorter, as we would know sooner a browser window was closed.

Sub-windows

Better handling of sub-windows is needed. Events should be disassociated from windows. Nodes should be associated with windows. Changes to a node should be mirrored in the relevante window, regardless of where the event originated.

Comet

Move from a synchronous event-based model to a full, bi-directional, asynchronous model using Comet (http://cometd.com/). Comet would also act as a keepalive.

Better XUL coverage

There are no tests for <colorpicker>, <datepicker>, <toolbar<gt>, <listbox<gt>, <tab<gt> and more.

POE::XUL::Application

A base class that would handle most of the simple house-keeping.

AUTHOR

Philip Gwyn <gwyn-at-cpan.org>

CREDITS

Based on XUL::Node by Ran Eilam, POE::Component::XUL by David Davis, and of course, POE, by the illustrious Rocco Caputo.

COPYRIGHT AND LICENSE

Copyright 2007 by Philip Gwyn. All rights reserved;

Copyright 2005 by David Davis and Teknikill Software;

Copyright 2003-2004 Ran Eilam. All rights reserved.

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

SEE ALSO

perl(1), POE::XUL::Node, POE::XUL::Event, POE::XUL::Controler.