NAME
Clay::UI::Role::Events::Listener - role that lets every widget listen to events
SYNOPSIS
use v5.22;
use warnings;
use feature 'signatures';
no warnings 'experimental::signatures';
use Object::Pad;
use Clay::UI::Box;
use Clay::UI::Events::OnPress;
use Clay::UI::Enum::Result;
class My::Panel :strict(params) :does(Clay::UI::Box) {}
my $box = My::Panel->new(id => 'box');
# Both listeners always run; the return values only decide whether
# the event moves on to the parent.
$box->on('OnPress', sub ($event) { # stops bubbling
say 'first listener';
return;
})->on('OnPress', sub ($event) {
say 'second listener';
return Clay::UI::Enum::Result->CONTINUE;
});
say scalar @{ $box->handlers_for('OnPress') }, ' listeners'; # 2 listeners
$box->fire_event(Clay::UI::Events::OnPress->new); # first listener, second listener
DESCRIPTION
This role is the receiving half of the Clay::UI event system. Every widget has it: Clay::UI::Role::Core::Element and Clay::UI::Role::Core::TextNode compose it, so you never compose it yourself. Any widget can therefore listen to events fired at it and to events that bubble up to it from its descendants, whether or not it can fire events itself (see Clay::UI::Role::Events::Emitter).
Text widgets have the role too, but no event reaches them: a text widget cannot fire, nothing bubbles through it (it has no descendants), and Clay::UI fires events only at element widgets. Listen on the element widget around the text instead.
METHODS
on
$widget->on($event_name, sub ($event) { ... });
$part->on($event_name, sub ($owner, $event) { ... }, owner => $owner);
Registers a listener: a coderef called with the event object when an event whose name is $event_name is fired at the widget or bubbles to it. A widget may have several listeners for one name; they run in the order they were registered. There is no way to remove a listener.
All listeners of a widget for the event always run, whatever the earlier ones return. The return values only decide whether the event moves on to the parent (see Clay::UI::Enum::Result): return Clay::UI::Enum::Result->CONTINUE to let it bubble on. Anything else stops it from bubbling, including an empty return, undef and whatever the last statement happens to return; one stopping listener is enough.
The widget keeps its listeners, so a listener must not capture an object that holds the widget, or the two keep each other alive and are never freed. Capturing the widget itself is such a cycle, and so is capturing the object of a widget class that listens on one of its own parts (a child, an internal child) when that object holds the part. There are two ways around it:
A listener that needs the widget it is registered on reads it from the event (
$event->current_target) instead of naming the widget's variable inside the closure:$box->on('OnPress', sub ($event) { my $box = $event->current_target; # not the outer $box ... return; });A listener that needs another object, its owner, is registered with the "owner" option and gets the owner as its first argument:
method build_parts () { $_button->on('OnRelease', sub ($self, $event) { $self->submit; # the owner, not the method's $self return Clay::UI::Enum::Result->HANDLED; }, owner => $self); }
Returns the widget, so calls can be chained. Dies with Clay::UI::Role::Events::Listener: event name must be a non-empty string or Clay::UI::Role::Events::Listener: handler must be a coderef, and for bad options (see below).
owner
owner => $object
The only option of on; options follow the handler as name => value pairs. The owner must be an object (a blessed reference). The widget keeps it weakly: an owner-bound listener does not keep its owner alive. While the owner exists, the handler is called with ($owner, $event), and its return value counts as described above. Once the owner has been freed, the handler no longer runs: the listener answers CONTINUE, so it never stops the event from bubbling, and its entry is dropped from the widget (by the next on or "handlers_for" for that event name), so the listeners of owners that are gone do not pile up.
Name the handler's first parameter after what the owner is to the code ($self in a method that passes owner => $self): the parameter then hides the method's own $self, so the closure cannot capture it by mistake.
Dies with Clay::UI::Role::Events::Listener: owner must be an object, got ..., Clay::UI::Role::Events::Listener: unknown option(s): ... (the only option is owner) and Clay::UI::Role::Events::Listener: options must be name => value pairs, got an odd number of values.
handlers_for
my $listeners = $widget->handlers_for($event_name);
Returns a new arrayref of the listeners registered for $event_name, in registration order (empty when there are none). Each is a coderef to be called with the event. For a plain listener it is the coderef given to "on"; for an owner-bound listener it is a coderef that calls the handler with the owner and the event, or returns CONTINUE once the owner is gone. Listeners whose owner is gone are left out (and dropped from the widget). Changing the arrayref does not change the widget. "fire_event" in Clay::UI::Role::Events::Emitter calls it for every widget the event visits.
Listeners are called handlers in this method's name and in the error message of "on" (handler must be a coderef); both words mean the coderefs registered with on.
SEE ALSO
Clay::UI::Role::Events::Emitter, Clay::UI::Events::Event, "EVENTS" in Clay::UI.