———package
XUL::Gui::Manual;
=head1 XUL::Gui Manual
Note: at times this document may fall behind what is written in XUL::Gui, in that case,
XUL::Gui is right.
gui programming has always been hard, be it a simple form, or a complex dynamic interface,
the learning curve has always been steep, the boilerplate painful, and the design patterns,
well, they have been quite tedious.
then came HTML, and with it a clean, clear, and concise nested programming style, that has,
for the most part, logical and intuitive functions and styling. it has taken some time for
web browsers to support user interfaces on a par with the native gui in most operating systems
but that time has come. firefox, available for all major operating systems, provides a rich
and extensible framework for developing cross platform gui applications. these applications are
written in XUL, Mozilla's gui development language, the same language that firefox itself is written
in. HTML is also fully supported, and can be freely intermixed with XUL. as powerful as XUL and
HTML are, they are fundamentally bound to javascript, which if you're anything like me, just isn't
a suitable replacement for perl.
XUL::Gui seeks not only to fully integrate all of the features of the XUL and HTML markup languages,
in both XML and functional forms, but to also proxy every property, attribute and method from
javascript to perl and back enabling transparent manipulation of the DOM in pure perl.
as functional as XUL and the DOM are, they aren't always the most convenient, otherwise the various
javascript frameworks would not exist. the XUL::Gui proxy aims to smooth some of the DOM's rough
edges by abstracting away the difference between properties and attributes, and adding plural versions
of many functions (you can also use any javascript framework with XUL::Gui by simply including it in a
<script> tag as you normally would: SCRIPT( src=>"myframework.js" ), but I hope in most cases that you
won't have to)
=head2 tag objects
the primary way of assembling your gui and submitting large updates
Label( value=>'Hello World' )
Button( label=>'Click' oncommand=>\&eventhandler )
the parenthesis are optional in simple contexts
$someparent->appendChild(Label value=>"$count");
but are of course needed for nested objects
display Window(
Hbox(
Label( value=>'Hello, World!' ),
Button( label=>'Click Me' )
)
);
or if you're golfing
display Window Hbox
Label( value=>'hello, world!' ),
Button label=>'Click Me';
every XUL and HTML tag is imported into your namespace with the following spellings:
Somexulname and SomeXulName
SOMEHTMLNAME and html_somehtmlname
the nesting of tags can be arbitrarily deep and complex and functions of course follow all the
same nesting rules as XML. however unlike XML, the attributes, properties and children
of a tag can be distributed in any order, but its probably best for readability to keep them at
the front of the @_ list. of course all arguments, children most usefully, are processed in order
Hbox(
Vbox( id=>'hbox1',
Label( value=>'vbox1' ),
Button( label=>'vbox2', oncommand=>\&eventhandler )
),
Label( value=>'hbox2' ),
Button( id=>'btn', label=>'hbox3', oncommand=>sub{
my ($self, $event) = @_;
print "$self->{ID} received event: ", $event->type, "\n";
# prints "btn received event: command"
})
)
in a tag, to set a property at creation time (if it makes sense), prepend a single underscore
Sometag( attributename=>'val', _property=>4 )
tag functions generate an XUL::Gui::Object hashref object that knows how to create
itself and then proxy interaction between perl and javascript for every attribute,
property and method that the corresponding XUL or HTML object has in javascript.
all of the names are mirrored into perl with the exact spelling and capitalization,
however all three are condensed into a single namespace, a perl $object->method; call
L<http://developer.mozilla.org/en/XUL> serves as the official documentation of tags and their
attributes, properties and methods
inside the hashref itself, all UPPERCASE keys are reserved, but feel free to use any
other keys as you want. a few useful reserved keys to know are:
ID the supplied or auto generated id
TAG the XUL or HTML tag name
A a hashref containing the creation time attribute and _property settings
C an array ref containing the creation time children
M a hashref for user defined methods
W the parent widget if it exists
all tags are loaded into the exported %ID hash with their specified id or an
auto generated one. all reserved ids match C</^xul_\d+/>
tag objects are accessed as follows:
js: ID.btn = document.createElement('button');
ID.btn.setAttribute('id', 'btn');
ID.btn.setAttribute('label', 'Click Me');
ID.btn.setAttribute('oncommand', handler);
ID.someparent.appendChild(ID.btn);
perl: Button( id=>'btn', label=>'Click Me', oncommand=>\&handler );
$ID{someparent}->appendChild($ID{btn});
or all in one line:
$ID{someparent}->appendChild
(Button id=>'btn', label=>'Click Me', oncommand=>\&handler);
js: ID.btn.getAttribute('attribute')
ID.btn.setAttribute('attribute', value);
perl: $ID{btn}->attribute
$ID{btn}->attribute = $value;
js: ID.btn.property = 5;
perl: $ID{btn}->property = 5;
in the event of a namespace collision, the attribute is returned,
to get the property, simply prepend a C<_> to the name. in most cases
setting the attribute works better.
perl: $ID{btn}->_forcedproperty = $value;
js: ID.btn._prop = 5; // a property that starts with _
perl: $ID{btn}->__prop = 5; # only the first _ is shifted off
attributes don't start with underscores so they are safe, in the rare event of an attribute
that is not a perl C<\w>, just use the normal C<(get|set)Attribute()> call
js: ID.btn.callMethod();
ID.btn.callMethod(arg1, arg2);
perl: $ID{btn}->callMethod;
$ID{btn}->callMethod($arg1, $arg2);
here is as good a time as any to explain the DWIM details of how one namespace in perl
maps to three in javascript (and abstracts away the tedious C<(set|get)Attribute()> calls)
$ID{btn}->callMethod; # void context is always a method call
$ID{btn}->callMethod('@_ > 0'); # any arguments is obvious
$ID{btn}->somename = 5 # in this case, the following selection order is used
unless $ID{btn}->somename; # attribute if hasAttribute(...)
# function if typeof is function
# property if has property
# undef or warn if :lvalue
$ID{btn}->_somename = 10; # forced property ID.btn.somename = 10;
print $ID{btn}->method(_); # forced method, called as ID.btn.someMethod();
# a bit of a kludge, but such a collision is rare
# as nearly all accessor functions are implemented
# as properties, which in turn usually don't collide
# with attributes. but just in case, the overrides
# are available
the returned value of all -> calls is either a scalar, or a reference to an
appropriate proxy object.
=cut
#if the javascript returns an array, access the
#object as a perl \@array.
=pod
the bidirectional translation between perl and javascript is:
JavaScript | Perl
--------------------|-------------------------
Array | ARRAY ref # not implemented yet
Object | Tag Object
undefined /| undef
null <--/ | undef
|
String, Number, | SCALAR
any other scalar |
the same attribute, property, and method call syntax from tags apply to returned values as well.
all C<< -> >> operations are atomic and execute immediately unless inside a pragmatic block.
this is fine for most events, but there are occasions when large changes need to be made to the gui
that would be too slow to send individually to the client.
if you need to add many elements to the gui, you could write it in javascript with the
gui('javascript here') call, but that would be tedious, and Larry tells us we should be lazy.
so use the preferred method of generating your objects with the tag subs, and utilize map to factor
out some of XML's repetition. since tag objects are not written to the client until they are used
in a method call, such as appendChild(), and then are written in one large message, they are very
fast. a side effect of this means that attempting to set attributes, properties, or to call
javascript methods before using the object in a method call will result in errors.
that is all well and good, but what about if you need to make many changes to existing objects
such as loading thousands of lines into a list, as with all Perl, TIMTOWTDI:
$ID{list}->removeItemAt(0) for 1..$ID{list}->getRowCount; # neat and clean, but slow
$ID{list}->appendItem($_, $_) for @items; # mirrors what the JS solution would look like
buffered { # a touch longer than the first
$ID{list}->removeItemAt(0) for 1..shift; # but easily as clean, and much faster
$ID{list}->appendItem($_, $_) for @items; # keep in mind that dependent values
} $ID{list}->getRowCount; # such as this line need to be passed
# in or escaped within a now block
$ID{list}->removeItems # for a few common tasks, XUL::Gui adds plural methods
->appendItems(@items); # which are easiest and fastest of all
=head2 Pragmatic Blocks
buffered { CODE } LIST;
buffer SCALAR, sub{ CODE }, LIST;
cached { CODE };
now { CODE };
buffered accepts a code block that defers proxying all commands to the gui until the block ends.
it also accepts a list in case you need to pass in any non-defered attributes, as in the last section.
cached accepts a code block that performs set calls normally, but only performs a particular get once,
and then afterward always returns the same value. javascript function calls behave normally
now is provided as a way to temporarily escape a buffered or cached block without causing a buffer
flush or a cache reset. it does nothing outside of a pragmatic block.
buffered returns the value of the combined javascript call, useful for testing for errors.
cached and now return the result of their last perl expression.
buffered and cached can be nested in either order. when inside both, get calls are cached,
and set calls are buffered
there is only one buffer and cache, so nesting multiple buffered or cached blocks has no effect.
neither will work inside of a now block.
note that all subroutines called from within a pragmatic block retain that pragma.
=cut
#Buffer is like Buffered but allows you to fine tune which commands are buffered and which aren't.
#the options for it's scalar value are 'set', 'get', or 'call' or any combination separated
#with spaces such as 'set call'
=head3 Event Handler Subs
sub {
my ($self, $event) = @_;
$self->someattribute = 'something';
print $event->type, "\n";
}
=head3 Widgets
XUL::Gui has a robust widget system designed to group tag patterns and other widgets. it offers functionality
similar to XBL, but entirely in perl, and with what at least I think is an easier syntax.
*MyWidget = widget { # a simple widget
Hbox(
Label( value=>'Labeled Button: ' ),
Button( label=>'OK' )
)
}
inside of each widget, the following variables are defined:
%A the attributes passed in to the widget
@C the children passed into the widget
%M a hash containing widget methods, which can be added to
$W the widget itself, in case you need to call any methods during construction
$W->mymethod is the same as $M{mymethod}($W)
*MyWidget = widget{ # a widget that accepts attributes and children
Hbox(
Label( value=>$A{label} ),
Button( label=>'OK', attribute 'oncommand' )
@C
)
}
MyWidget( label=>'Labeled Button: ', oncommand=>\&action, SomeChildObject() );
=head3 Methods and Event Handlers:
*BetterWidget = widget{
Hbox
Label( id=>'lbl', value=>$A{label} ),
Button( id=>'btn' label=>'OK', attribute 'oncommand' ),
Button( id=>'exit', label=>'Exit', oncommand=>sub{
my $self = shift;
$self->{lbl}->label = 'Goodbye';
$self->blur;
quit;
}),
@C
}
mymethod => sub{
my $self = shift;
say $self->{lbl}->value;
$self->{btn}->focus;
};
BetterWidget( id=>'better' label=>'Better: ', oncommand=>\&action);
.....
$ID{better}->mymethod; #prints the label's value and focuses the OK button
You may be wondering what happens when you create a second BetterWidget now that the internal
elements have id's. As we have seen, all id's get loaded into the %ID hash for later reference.
However, if widgets behaved the same way, you could never reuse a widget, and what would be
the point? Rather, inside of a widget, all id's are in their own private lexical space.
After instantiating the widget with an id of 'better':
$ID{lbl} does not exist, but
$ID{better}{lbl} does, and can be interacted with as normal.
Inside of a widget's method handlers, $_[0] contains no native methods of its own,
but contains hash keys of all of the id's defined within the widget
Inside of a widget's event handlers, $_[0] contains widget methods that affect the current object,
as well as containing hash keys of all of the id's defined within the widget, in addition to all
of its ordinary attributes, properties, and methods that go along with Tag objects
Since widgets define their own namespace and methods and behave externally the same way as normal
Tag objects, it is possible to create complex interaction without much repeated coding.
Widgets also can be nested within each other without limit. Each nested widget is again its
own lexical id space.
$ID{mainwidget}{subwidget}{lbl}->value = 'something';
=head3 Widgets As Classes
previously we have seen that widgets behave like Tag objects, but Widgets can also behave like
classes, using the extends function.
*SuperClass = widget{
Vbox(
Label( value=>'SuperClass' ),
Button( id=>'btn', width=>$A{width} )
)}
supermethod = sub{ ... };
*SubClass = widget{ extends &SuperClass }
submethod = sub{ ... };
any SubClass objects now have both the 'submethod' and 'supermethod' methods, and SubClass
creates the same gui elements as SuperClass. It does this because extends returns the results
from the &SuperClass call. This also means that you are free to rearrange, add, or dismiss objects
from the SuperClass as you see fit. Named ID's from the superclass are also in the subclass.
*ReverseClass = widget{
my @super = extends SubClass( width=>50, @_ ); #add a default value
reverse @super
};
*AnotherClass = widget{
extends &ReverseClass; #throw away super class's objects
Vbox(
Label( 'Only the button from SuperClass' ),
$ID{btn}, #grab the btn
)
};
when you call a widget, you need to always use parenthesis due to its runtime definition,
to get around this:
sub MyWidget;
*MyWidget = widget{ ... };
BEGIN{ *MyWidget = widget{ ... } }
MyWidget can now be called like any native tag object
=head3 inline methods
any key value pair in a tag's argument list with a coderef value and a key that doesn't match /^on/ is entered into that
tag's method table, as if it were a widget.
=head2 new javascript functions
while XUL and the DOM in general have a fairly decent interface, there is of course a bit lacking,
otherwise jQuery and the like would not exist. While you can of course use any of those javascript extensions
from perl simply by including them in a script tag as you would normally, i felt that several key methods
should be added by default, some of which are simply plural versions of existing functions, which i find
far more convenient, and save you the trouble of using Buffered for large updates. most of these functions
are implemented in javascript during the initial bootstrap process, but a few are implemented directly in
perl. either way they are all available from the start of your gui program
=over 4
=item C<< ->removeChildren( LIST ) >>
removes the children in LIST, or all children if none given
=item C<< ->removeItems( LIST ) >>
removes the items in LIST, or all items if none given
=item C<< ->appendChildren( LIST ) >>
appends the children in LIST
=item C<< ->prependChild( CHILD, [INDEX] ) >>
inserts CHILD at INDEX (defaults to 0) in the parent's child list
=item C<< ->appendItems( LIST ) >>
append a list of items
=item C<< ->replaceItems( LIST ) >>
removes all items, then appends LIST
=back
=cut
1;