File Coverage

lib/HTML/Object/EventTarget.pm
Criterion Covered Total %
statement 185 238 77.7
branch 66 142 46.4
condition 49 122 40.1
subroutine 23 26 88.4
pod 9 9 100.0
total 332 537 61.8


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## HTML Object - ~/lib/HTML/Object/EventTarget.pm
3             ## Version v0.2.2
4             ## Copyright(c) 2022 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2021/12/11
7             ## Modified 2022/11/11
8             ## All rights reserved
9             ##
10             ##
11             ## This program is free software; you can redistribute it and/or modify it
12             ## under the same terms as Perl itself.
13             ##----------------------------------------------------------------------------
14             package HTML::Object::EventTarget;
15             BEGIN
16             {
17 29     29   15337 use strict;
  29         107  
  29         1083  
18 29     29   225 use warnings;
  29         127  
  29         1088  
19             # Changed inheritance from Module::Generic to HTML::Object::Element, because I need modules like
20             # HTML::Object::DOM::TextTrack that inherits from EventTarget to also have the parent method provided
21             # by the core module HTML::Object::Element
22 29     29   211 use parent qw( HTML::Object::Element );
  29         97  
  29         464  
23 29     29   2692 use vars qw( $PACK_SUB_RE $SIGNALS $VERSION );
  29         123  
  29         2903  
24 29     29   12358 use HTML::Object::EventListener;
  29         143  
  29         668  
25 29     29   17015 use Scalar::Util ();
  29         101  
  29         862  
26 29     29   219 use Want;
  29         91  
  29         6944  
27 29     29   503 our $PACK_SUB_RE = qr/^(((?<pack>[a-zA-Z\_]\w*(?:\:\:\w+)*)\:\:)?(?<sub>\w+))$/;
28             # Hash reference of signal to array of object to remove their listeners
29 29         156 our $SIGNALS = {};
30 29         674 our $VERSION = 'v0.2.2';
31             };
32              
33 29     29   240 use strict;
  29         119  
  29         794  
34 29     29   224 use warnings;
  29         153  
  29         95336  
35              
36             sub init
37             {
38 97     97 1 313 my $self = shift( @_ );
39 97 50       519 $self->{_init_strict_use_sub} = 1 unless( CORE::exists( $self->{_init_strict_use_sub} ) );
40 97 50       590 $self->{_exception_class} = 'HTML::Object::Exception' unless( CORE::exists( $self->{_exception_class} ) );
41 97 50       832 $self->SUPER::init( @_ ) || return( $self->pass_error );
42 97         467 $self->{event_listeners} = {};
43 97         703 return( $self );
44             }
45              
46             sub addEventListener
47             {
48 5     5 1 41827 my $self = shift( @_ );
49 5   50     26 my $type = shift( @_ ) || return( $self->error( "No event listener type was provided." ) );
50 5   50     18 my $callback = shift( @_ ) || return( $self->error( "No event listener callback was provided." ) );
51 5 50       52 return( $self->error( "Event listener type \"$type\" contains illegal characters. It should contain only alphanumeric and _ characters." ) ) if( $type !~ /^\w+$/ );
52 5         18 $type = lc( $type );
53 5 50 33     26 if( !ref( $callback ) && $callback =~ /$PACK_SUB_RE/ )
54             {
55 0         0 my( $pack, $sub ) = @+{qw( pack sub )};
56 0   0     0 $pack ||= caller;
57 0 0       0 if( my $ref = $pack->can( $sub ) )
58             {
59 0         0 $callback = $ref;
60             }
61             else
62             {
63 0         0 return( $self->error( "Unknown subroutine \"$callback\" in package \"$pack\"." ) );
64             }
65             }
66 5 50       19 return( $self->error( "Event listener callback is not a code reference." ) ) if( ref( $callback ) ne 'CODE' );
67 5         27 my $opts = $self->_get_args_as_hash( @_ );
68 5         604 my @ok_params = qw( capture once passive signal debug );
69 5         12 my $params = {};
70 5         32 @$params{ @ok_params } = CORE::delete( @$opts{ @ok_params } );
71 5         12 my $post_processing;
72 5 50 33     23 $post_processing = CORE::delete( $opts->{post_processing} ) if( CORE::exists( $opts->{post_processing} ) && ref( $opts->{post_processing} ) eq 'CODE' );
73 5 50       23 if( scalar( keys( %$opts ) ) )
74             {
75 0 0       0 warnings::warn( "Unrecognised options: '" . join( "', '", sort( keys( %$opts ) ) ) . "'\n" ) if( warnings::enabled( 'HTML::Object' ) );
76             }
77 5   100     19 $params->{capture} //= 0;
78 5   50     31 $params->{once} //= 0;
79 5   50     26 $params->{passive} //= 0;
80 5         32 my $key = join( ';', $type, Scalar::Util::refaddr( $callback ), $params->{capture} );
81 5 100       23 $self->{event_listeners} = {} if( !CORE::exists( $self->{event_listeners} ) );
82 5         13 my $repo = $self->{event_listeners};
83 5   50     44 my $debug = CORE::delete( $params->{debug} ) || 0;
84 5   50     50 my $eh = HTML::Object::EventListener->new(
85             type => $type,
86             code => $callback,
87             options => $params,
88             element => $self,
89             debug => $debug,
90             ) || return( $self->pass_error( HTML::Object::EventListener->error ) );
91 5 50 33     69 if( $params->{signal} && $params->{signal} =~ /^\w+$/ )
92             {
93 0         0 $SIG{ $params->{signal} } = \&_signal_remove_listeners;
94 0 0       0 $SIGNALS->{ $params->{signal} } = [] if( !CORE::exists( $SIGNALS->{ $params->{signal} } ) );
95 0         0 push( @{$SIGNALS->{ $params->{signal} }}, $eh );
  0         0  
96             }
97 5 100       41 $repo->{ $type } = {} if( !CORE::exists( $repo->{ $type } ) );
98 5 100       24 $repo->{ $type }->{sequence} = $self->new_array if( !CORE::exists( $repo->{ $type }->{sequence} ) );
99 5         72 $repo->{ $type }->{ $key } = $eh;
100 5 50       34 if( $repo->{ $type }->{sequence}->has( $key ) )
101             {
102 0         0 $repo->{ $type }->{sequence}->remove( $key );
103             }
104 5         198652 $repo->{ $type }->{sequence}->push( $key );
105             # Call any post-processing callback if necessary.
106             # Those are used so that event monitoring can be enabled upon setting event handlers and not before, on some data like array or scalar
107             # See HTML::Object::LDOM::List for example
108 5 50       696 if( defined( $post_processing ) )
109             {
110 0         0 $post_processing->( $eh );
111             }
112 5         59 return( $eh );
113             }
114              
115             sub dispatchEvent
116             {
117 2     2 1 7 my $self = shift( @_ );
118 2   50     9 my $event = shift( @_ ) || return( $self->error( "No event object was provided." ) );
119 2 50       11 return( $self->error( "Event object provided ($event) is not an HTML::Object::Event" ) ) if( !$self->_is_a( $event => 'HTML::Object::Event' ) );
120 2         102 $event->target( $self );
121            
122 2   50     78 my $type = $event->type || return( $self->error( "The event has no type associated with it!" ) );
123 2         1714 $type = lc( $type );
124 2         11 my $can_cancel = $event->cancelable;
125 2 50 33     1772 return( $self ) if( $can_cancel && $event->cancelled );
126             # from current element to top one
127 2         81014 my $path = $event->composedPath;
128 2         21 $event->eventPhase( $event->CAPTURING_PHASE );
129             # Go from top to our element, i.e. reverse
130             $path->reverse->foreach(sub
131             {
132 6     6   211 my $node = shift( @_ );
133 6         24 $event->currentTarget( $node );
134             $node->handleEvent( $event ) || do
135 6 50       5353 {
136             };
137 6 50 33     31 if( $can_cancel && $event->cancelled )
138             {
139 0         0 return;
140             }
141             # Make sure to return true to keep looping
142 6         4568 return(1);
143 2         1836 });
144 2 50 33     101 return( $self ) if( $can_cancel && $event->cancelled >= $event->CANCEL_IMMEDIATE_PROPAGATION );
145 2         2009 $event->eventPhase( $event->AT_TARGET );
146 2         1779 $event->currentTarget( $self );
147 2         1791 $self->handleEvent( $event );
148 2 50 33     12 return( $self ) if( $can_cancel && $event->cancelled >= $event->CANCEL_PROPAGATION );
149             # This event does not bubble, so we do nothing more
150 2 50       1937 return( $self ) if( !$event->bubbles );
151 2         2040 $event->eventPhase( $event->BUBBLING_PHASE );
152             # Now, go from our element to the top one
153             $path->for(sub
154             {
155 6     6   142 my( $i, $node ) = @_;
156             # Skip the first one which is us.
157 6 100       22 return(1) if( $i == 0 );
158 4         22 $event->currentTarget( $node );
159             $node->handleEvent( $event ) || do
160 4 50       3539 {
161             };
162 4 50 33     21 if( $can_cancel && $event->cancelled )
163             {
164 0         0 return;
165             }
166             # Make sure to return true to keep looping
167 4         3082 return(1);
168 2         1839 });
169 2         106 return( $self );
170             }
171              
172 0     0 1 0 sub event_listeners { return( shift->_set_get_hash( 'event_listeners', @_ ) ); }
173              
174             sub getEventListeners
175             {
176 138     138 1 418 my $self = shift( @_ );
177 138         315 my $type = shift( @_ );
178 138 50 33     965 return( $self->error( "No event type propoded to get its event listeners." ) ) if( !defined( $type ) || !CORE::length( $type ) );
179 138 100       675 $self->{event_listeners} = {} if( !CORE::exists( $self->{event_listeners} ) );
180 138         357 my $repo = $self->{event_listeners};
181 138 100       998 return if( !scalar( keys( %$repo ) ) );
182 3 50       15 $repo->{ $type } = {} if( !CORE::exists( $repo->{ $type } ) );
183 3 50       12 $repo->{ $type }->{sequence} = $self->new_array if( !CORE::exists( $repo->{ $type }->{sequence} ) );
184 3         22 my $results = $self->new_array;
185             $repo->{ $type }->{sequence}->foreach(sub
186             {
187 3     3   67 my $key = shift( @_ );
188 3 50 33     38 $results->push( $repo->{ $type }->{ $key } ) if( CORE::exists( $repo->{ $type }->{ $key } ) && $self->_is_a( $repo->{ $type }->{ $key } => 'HTML::Object::EventListener' ) );
189 3         117 });
190 3         788 return( $results );
191             }
192              
193             sub handleEvent
194             {
195 12     12 1 22 my $self = shift( @_ );
196 12   50     34 my $evt = shift( @_ ) || return( $self->error({
197             message => "No event was provided to handle.",
198             class => 'HTML::Object::SyntaxError',
199             }) );
200 12 50       38 return( $self->error({
201             message => "Event object provided is not an event of class HTML::Object::Event or its descendants.",
202             class => 'HTML::Object::TypeError',
203             }) ) if( !$self->_is_a( $evt => 'HTML::Object::Event' ) );
204 12 50       433 return( $self ) if( $evt->cancelled );
205 12         9253 my $can_cancel = $evt->cancelable;
206            
207 12         10114 my $repo = $self->{event_listeners};
208 12   50     46 my $type = $evt->type || return( $self->error({
209             message => "No event type was provided.",
210             class => 'HTML::Object::SyntaxError',
211             }) );
212 12 100       10185 return( $self ) if( !CORE::exists( $repo->{ $type } ) );
213             return( $self->error({
214             message => "Repository of event listener of type '$type' is not an hash reference!",
215             class => 'HTML::Object::TypeError',
216 8 50       42 }) ) if( ref( $repo->{ $type } ) ne 'HASH' );
217            
218             return( $self->error({
219             message => "Could not find the 'sequence' property in the repository of event listeners.",
220             class => 'HTML::Object::SyntaxError',
221 8 50       25 }) ) if( !CORE::exists( $repo->{ $type }->{sequence} ) );
222             return( $self->error({
223             message => "Sequence property of event listeners for type '$type' is not a Module::Generic::Array object",
224             class => 'HTML::Object::TypeError',
225 8 50       31 }) ) if( !$self->_is_a( $repo->{ $type }->{sequence} => 'Module::Generic::Array' ) );
226              
227 8         282 $evt->currentTarget( $self );
228 8         7034 my $eventPhase = $evt->eventPhase;
229 8         6506 foreach my $key ( @{$repo->{ $type }->{sequence}} )
  8         43  
230             {
231 8 50 33     47 return( $self->error({
232             message => "Empty key found in sequence repository.",
233             class => 'HTML::Object::SyntaxError',
234             }) ) if( !defined( $key ) || !CORE::length( $key ) );
235 8 50       22 if( !CORE::exists( $repo->{ $type }->{ $key } ) )
236             {
237 0         0 return( $self->error({
238             message => "Found an event listener of type '$type' with key '$key', but could not find its associated entry in the event listener repository.",
239             class => 'HTML::Object::SyntaxError',
240             }) );
241             }
242 8         18 my $listener = $repo->{ $type }->{ $key };
243 8 50       26 if( !$self->_is_a( $listener => 'HTML::Object::EventListener' ) )
244             {
245 0         0 return( $self->error({
246             message => "The event listener of type '$type' found is not an HTML::Object::EventListener object.",
247             class => 'HTML::Object::TypeError',
248             }) );
249             }
250            
251             # check we are in the right phase
252 8 100 66     398 if( ( $eventPhase eq $evt->AT_TARGET && $evt->target ne $self ) ||
      100        
      66        
      100        
      100        
253             ( $eventPhase eq $evt->CAPTURING_PHASE && !$listener->capture ) ||
254             ( $eventPhase eq $evt->BUBBLING_PHASE && $listener->capture ) )
255             {
256 4         2171 next;
257             }
258            
259 4         1223 my $code = $listener->code;
260 4 50       3597 if( ref( $code ) ne 'CODE' )
261             {
262             # return( $self->error({
263             # message => "The handler for the event listener of type '$type' is not a code reference.",
264             # class => 'HTML::Object::TypeError',
265             # }) );
266 0 0       0 warnings::warn( "Warning only: the handler for the event listener of type '$type' is not a code reference.\n" ) if( warnings::enabled( 'HTML::Object' ) );
267 0         0 next;
268             }
269 4         7 local $_ = $self;
270             # Note: Should we catch a die, or let it die, if that were the case?
271 4         22 $code->( $evt );
272 4 50 33     1966 last if( $can_cancel && $evt->cancelled == $evt->CANCEL_IMMEDIATE_PROPAGATION );
273             }
274 8         4212 return( $self );
275             }
276              
277             sub hasEventListener
278             {
279 2     2 1 40488 my $self = shift( @_ );
280 2         5 my $type = shift( @_ );
281 2 50       8 $self->{event_listeners} = {} if( !CORE::exists( $self->{event_listeners} ) );
282 2         4 my $repo = $self->{event_listeners};
283 2 50       8 return( $self->new_number(0) ) if( !scalar( keys( %$repo ) ) );
284 2 50 33     7 if( defined( $type ) && CORE::length( $type ) )
285             {
286 0 0       0 $repo->{ $type } = {} if( !CORE::exists( $repo->{ $type } ) );
287 0         0 my $n = scalar( keys( %{$repo->{ $type }} ) );
  0         0  
288 0 0       0 $n-- if( CORE::exists( $repo->{ $type }->{sequence} ) );
289 0         0 return( $self->new_number( $n ) );
290             }
291 2         4 my $n = 0;
292 2         6 foreach my $t ( keys( %$repo ) )
293             {
294 2         3 $n += scalar( keys( %{$repo->{ $t }} ) );
  2         5  
295 2 50       13 $n-- if( CORE::exists( $repo->{ $t }->{sequence} ) );
296             }
297 2         15 return( $self->new_number( $n ) );
298             }
299              
300             sub on : lvalue
301             {
302 139     139 1 401 my $self = shift( @_ );
303 139         348 my $event = shift( @_ );
304            
305             return( $self->_set_get_callback({
306             get => sub
307             {
308 138     138   102969 my $self = shift( @_ );
309 138 50 33     1403 return( $self->error( "No event provided to set event handler." ) ) if( !defined( $event ) || !CORE::length( $event ) );
310 138   100     942 my $listeners = $self->getEventListeners( $event ) || return;
311 3         17 return( $listeners->first );
312             },
313             set => sub
314             {
315 1     1   815 my $self = shift( @_ );
316 1         3 my $arg = shift( @_ );
317 1 50 33     11 return( $self->error( "No event provided to set event handler." ) ) if( !defined( $event ) || !CORE::length( $event ) );
318             # Argument provided is a code reference
319 1 50       9 if( ref( $arg ) ne 'CODE' )
320             {
321 0         0 return( $self->error( "Value provided is not a code reference." ) );
322             }
323 1   50     15 my $eh = $self->addEventListener( $event => $arg ) || return( $self->pass_error );
324 1         3 return( $eh );
325             }
326 139         1845 }, @_ ) );
327             }
328              
329             sub removeEventListener
330             {
331 2     2 1 49 my $self = shift( @_ );
332 2   50     9 my $type = shift( @_ ) || return( $self->error( "No event listener type was provided." ) );
333 2         23 my( $callback, $eh );
334 2 50       8 if( $self->_is_a( $_[0] => 'HTML::Object::EventListener' ) )
335             {
336 0         0 $eh = shift( @_ );
337 0         0 $callback = $eh->code;
338             }
339             else
340             {
341 2   50     36 $callback = shift( @_ ) || return( $self->error( "No event listener callback was provided." ) );
342             }
343 2 50       6 return( $self->error( "Event listener type \"$type\" contains illegal characters. It should contain only alphanumeric and _ characters." ) ) if( $type !~ /^\w+$/ );
344 2 50 33     25 if( !ref( $callback ) &&
345             $callback =~ /$PACK_SUB_RE/ )
346             {
347 0         0 my( $pack, $sub ) = @+{qw( pack sub )};
348 0   0     0 $pack ||= caller;
349 0 0       0 if( my $ref = $pack->can( $sub ) )
350             {
351 0         0 $callback = $ref;
352             }
353             else
354             {
355 0         0 return( $self->error( "Unknown subroutine \"$callback\" in package \"$pack\"." ) );
356             }
357             }
358 2 50 33     13 return( $self->error( "Event listener callback is not a code reference. You can provide either an event listener object (HTML::Object::EventListener, or a reference to a subroutine, or a subroutine name such as MyPackage::my_sub." ) ) if( defined( $callback ) && ref( $callback ) ne 'CODE' );
359 2         12 my $opts = $self->_get_args_as_hash( @_ );
360 2 50 33     289 if( defined( $eh ) && !CORE::exists( $opts->{capture} ) )
361             {
362 0         0 $opts->{capture} = $eh->options->{capture};
363             }
364 2         8 my @ok_params = qw( capture );
365 2         4 my $params = {};
366 2         10 @$params{ @ok_params } = CORE::delete( @$opts{ @ok_params } );
367 2 50       9 if( scalar( keys( %$opts ) ) )
368             {
369 0 0       0 warnings::warn( "Unrecognised options: '" . join( "', '", sort( keys( %$opts ) ) ) . "'\n" ) if( warnings::enabled( 'HTML::Object' ) );
370             }
371 2   50     46 $params->{capture} //= 0;
372 2         15 my $key = join( ';', $type, Scalar::Util::refaddr( $callback ), $params->{capture} );
373 2 50       17 $self->{event_listeners} = {} if( !CORE::exists( $self->{event_listeners} ) );
374 2         7 my $repo = $self->{event_listeners};
375 2 50       5 $repo->{ $type } = {} if( !CORE::exists( $repo->{ $type } ) );
376 2         13 $eh = CORE::delete( $repo->{ $type }->{ $key } );
377 2 50       18 $repo->{ $type }->{sequence} = $self->new_array if( !CORE::exists( $repo->{ $type }->{sequence} ) );
378 2         15 $repo->{ $type }->{sequence}->remove( $key );
379 2         1337 return( $eh );
380             }
381              
382             sub _signal_remove_listeners
383             {
384             my $sig = shift( @_ ) ||
385             do
386 0   0 0     {
387             warnings::warn( "Warning only: no signal was provided in HTML::Object::EventTarget::_signal_remove_listeners\n" ) if( warnings::enabled( 'HTML::Object' ) );
388             return;
389             };
390 0           my $all = $SIGNALS->{ $sig };
391 0 0         if( ref( $all ) eq 'ARRAY' )
392             {
393 0           $_->remove for( @$all );
394             }
395             }
396              
397             sub _trigger_event_for
398             {
399 0     0     my $self = shift( @_ );
400 0           my $type = shift( @_ );
401 0           my $class;
402 0 0         if( index( $_[0], '::' ) != -1 )
403             {
404 0           $class = shift( @_ );
405             }
406 0           my $opts = $self->_get_args_as_hash( @_ );
407 0   0       $opts->{class} //= '';
408 0   0       $class //= $opts->{class} || 'HTML::Object::Event';
      0        
409 0 0         $self->_load_class( $class ) || return( $self->pass_error );
410 0   0       my $event = $class->new( $type, @_ ) || return( $self->error( $class->error ) );
411 0 0 0       if( CORE::exists( $opts->{callback} ) && ref( $opts->{callback} ) eq 'CODE' )
412             {
413 0           local $_ = $event;
414 0           $opts->{callback}->( $event );
415             }
416 0 0         $self->dispatchEvent( $event ) || return( $self->pass_error );
417 0           return( $event );
418             }
419              
420             1;
421             # NOTE: POD
422             __END__
423              
424             =encoding utf-8
425              
426             =head1 NAME
427              
428             HTML::Object::EventTarget - HTML Object Event Target Class
429              
430             =head1 SYNOPSIS
431              
432             use HTML::Object::EventTarget;
433             my $eh = HTML::Object::EventTarget->new(
434            
435             ) || die( HTML::Object::EventTarget->error, "\n" );
436              
437             $e->addEventListener( change => sub
438             {
439             my $event = shift( @_ ); # also available as $_
440             # do something with that event (HTML::Object::Event)
441             }, {capture => 0});
442              
443             $e->dispatchEvent( $event );
444              
445             my $event_handlers = $e->getEventListeners( 'click' );
446             say "Found ", $Event_handlers->length, " event handlers";
447              
448             $e->handleEvent( $event );
449              
450             $e->on( click => sub
451             {
452             my $event = shift( @_ );
453             # do something
454             });
455             # or
456             sub onclick : lvalue { return( shift->on( 'click', @_ ) ); }
457             $e->onclick = sub
458             {
459             my $event = shift( @_ );
460             # do something
461             };
462              
463             $e->removeEventListener( $event_listener_object );
464              
465             =head1 VERSION
466              
467             v0.2.2
468              
469             =head1 DESCRIPTION
470              
471             This modules represents an event target and handler. This is implemented by L<HTML::Object::Document> and L<HTML::Object::Element> and its descendants.
472              
473             Of course, being perl, there is only limited support for events.
474              
475             =head1 CONSTRUCTOR
476              
477             =head2 new
478              
479             Creates a new L<HTML::Object::EventTarget> object instance and returns it.
480              
481             =head1 METHODS
482              
483             =head2 addEventListener
484              
485             Provided with a C<type>, a C<callback> and an optional hash or hash reference of C<options> and this will register an event handler (i.e. a callback subroutine) of a specific event type on the L<EventTarget|HTML::Object::Element>.
486              
487             When an event is "fired", the C<callback> is called, and the L<event object|HTML::Object::Event> is passed as it sole argument. Also, C<$_> is set to the current L<element object|HTML::Object::Element> on which the event got triggered.
488              
489             It returns the newly created L<HTML::Object::EventListener> upon success or L<perlfunc/undef> upon error and sets an L<error|Module::Generic/error>
490              
491             Possible options are:
492              
493             =over 4
494              
495             =item I<capture>
496              
497             A boolean value indicating that events of this type will be dispatched to the registered listener before being dispatched to any L<EventTarget|HTML::Object::Element> beneath it in the L<DOM|HTML::Object::Document> tree.
498              
499             Setting the capture flag controls whether an event listener is called in the Capture phase or Bubble phase.
500              
501             For example:
502              
503             <div id="div1">
504             <div id="div2"></div>
505             </div>
506              
507             $div1->addEventListener('click', \&doSomething1, {capture => 1 });
508             $div2->addEventListener('click', \&doSomething2, {capture => 0 });
509              
510             Triggering a C<click> event on C<div2> yields the following[1]:
511              
512             $div2->trigger('click');
513              
514             =over 4
515              
516             =item 1. The C<click> event starts in the capturing phase. The event looks if any ancestor element of element2 has a onclick event handler for the capturing phase.
517              
518             =item 2. The event finds one on C<div1>. C<doSomething1()> is executed.
519              
520             =item 3. The event travels down to the target itself, no more event handlers for the capturing phase are found. The event moves to its bubbling phase and executes C<doSomething2()>, which is registered to C<div2> for the bubbling phase.
521              
522             =item 4. The event travels upwards again and checks if any ancestor element of the target has an event handler for the bubbling phase. This is not the case, so nothing happens.
523              
524             =back
525              
526             The reverse would be:
527              
528             $div1->addEventListener('click', \&doSomething1, {capture => 0 });
529             $div2->addEventListener('click', \&doSomething2, {capture => 0 });
530              
531             =over 4
532              
533             =item 1. The click event starts in the capturing phase. The event looks if any ancestor element of C<div2> has a onclick event handler for the capturing phase and doesn’t find any.
534              
535             =item 2. The event travels down to the target itself. The event moves to its bubbling phase and executes C<doSomething2()>, which is registered to C<div2> for the bubbling phase.
536              
537             =item 3. The event travels upwards again and checks if any ancestor element of the target has an event handler for the bubbling phase.
538              
539             =item 4. The event finds one on C<div1>. Now C<doSomething1()> is executed.
540              
541             =back
542              
543             Setting an event listener like this:
544              
545             $div1->onclick = \&doSomething1;
546              
547             would register it in the bubbling phase, i.e. equivalent to:
548              
549             $div1->addEventListener( click => \&doSomething1, { capture => 0 } );
550              
551             [1] Koch, Peter-Paul "Event order" (Quirkcsmode) L<https://www.quirksmode.org/js/events_order.html>
552              
553             See L<for more information|https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#event_bubbling_and_capture> and L<this|https://www.quirksmode.org/js/events_order.html>
554              
555             =item I<once>
556              
557             A boolean value indicating that the listener should be invoked at most once after being added. If true, the listener would be automatically removed when invoked.
558              
559             =item I<passive>
560              
561             This, under perl, does nothing and thus always defaults to false.
562              
563             Under JavaScript, this would be a boolean value that, if true, indicates that the function specified by listener will never call C<preventDefault()>. If a passive listener does call C<preventDefault()>, the user agent will do nothing other than generate a console warning. See Improving scrolling performance with passive listeners to learn more.
564              
565             =item I<post_processing>
566              
567             This is a non-standard addition and is a code reference (i.e. a subroutine reference or an anonymous subroutine) passed that will be called once the event handler has been set. It is passed the newly created L<event listener object|HTML::Object::EventListener>.
568              
569             This is used by L<HTML::Object::DOM::List> for example, to enable event listening on array or scalar only when an event listener is registered. So, upon adding an event listener, this post-processing callback is called and this callback takes the appropriate step to start listening to a specific array or scalar.
570              
571             =item I<signal>
572              
573             A signal, such as C<ALRM>, C<INT>, or C<TERM>. The listener will be removed when the given C<signal> is called.
574              
575             =back
576              
577             Example:
578              
579             <table id="outside">
580             <tr><td id="t1">one</td></tr>
581             <tr><td id="t2">two</td></tr>
582             </table>
583              
584             # Subroutine to change the content of $t2
585             sub modifyText
586             {
587             my $t2 = $doc->getElementById("t2");
588             if( $t2->firstChild->nodeValue eq "three" )
589             {
590             $t2->firstChild->nodeValue = "two";
591             }
592             else
593             {
594             $t2->firstChild->nodeValue = "three";
595             }
596             }
597              
598             # Add event listener to table
599             my $el = $doc->getElementById("outside");
600             $el->addEventListener("click", \&modifyText, { capture => 0 } );
601              
602             Then, do:
603              
604             $el->trigger( 'click' );
605              
606             You can also pass subroutine name that is in your package, or a fully qualified subroutine name. For example:
607              
608             Assuming you are calling from the package My::Module, the following will search for a subroutine C<My::Module::my_callback>
609              
610             $el->addEventListener("click", 'my_callback', { capture => 0 } );
611              
612             Or
613              
614             $el->addEventListener("click", 'My:Module::my_callback', { capture => 0 } );
615              
616             If it does not exists, it will return C<undef> and set an L<error|HTML::Object::Exception>.
617              
618             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener>
619              
620             =head2 dispatchEvent
621              
622             Provided with an L<event|HTML::Object::Event>, and this dispatches the event to this L<EventTarget|HTNL::Object::Element>.
623              
624             It will first call L</composedPath> to get the branch from this current element to the top one, and will start from the top in the C<capture> phase, checking every element from top up to the current one and calls L</handleEvent>, which, in turn, will check if there are any listeners registered in this C<capture> phase, and if there are calls each listener's L<HTML::Object::EventListener/handleEvent>.
625              
626             Then once the C<capture> phase is done, it executes the event listeners on the current L<element|HTML::Object::Element>, if any.
627              
628             Then finally, if the event L<HTML::Object::Event/bubbles> property is true, it calls L</handleEvent> on each of the element starting from the current element's parent to the top one. L</handleEvent>, in turn, will check if there are any event listeners registereed for the element in question for the C<bubbling> phase and call their L<event handler|HTML::Object::EventListener/handleEvent>.
629              
630             If the event property L<HTML::Object::Event/cancelable> is set to true and a handler cancelled it at any point, then this whole process is interrupted.
631              
632             The event L<current element|HTML::Object::Event/currentTarget> is set each time, so you can check that to find out which one has cancelled it.
633              
634             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent>
635              
636             L<See also|https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#bubbling_and_capturing_explained>
637              
638             =head2 event_listeners
639              
640             Sets or gets an hash reference of all the event listeners registered.
641              
642             =head2 getEventListeners
643              
644             Provided with an event type, such as C<click>, and this returns all the L<event listener objects|HTML::Object::EventListener> registere for that event type in their order of registration. It returns an L<array object|Module::Generic::Array>
645              
646             my $listeners = $e->getEventListeners( 'click' );
647             say "Found ", $listeners->length, " event listeners.";
648              
649             =head2 handleEvent
650              
651             Provided with an L<event|HTML::Object::Event> and this will process it via all the registered event listeners in the order they were registered.
652              
653             It returns the current element object upon success, and upon error, it returns C<undef> and sets an L<error|Module::Generic/error>
654              
655             =head2 hasEventListener
656              
657             Provided with an optional event type and this returns the number of event listeners registered for the given type, if provided, or the total number of event listeners registered for all types.
658              
659             =head2 on
660              
661             This is a convenient method to set event listeners. It is to be called by a class, such as:
662              
663             sub onclick : lvalue { return( shift->on( 'click' @_ ) ); }
664              
665             Then, you can set a C<click> event listener for this element:
666              
667             $e->onclick = sub
668             {
669             my $event = shift( @_ ); # Also available with $_
670             # Do some work
671             };
672             # or
673             $e->onclick(sub
674             {
675             my $event = shift( @_ ); # Also available with $_
676             # Do some work
677             });
678              
679             =head2 removeEventListener
680              
681             Provided with a event C<type>, a C<callback> code reference or a subroutine name (possibly including its package like C<MyPackage::my_sub>), or an L<event listener object|HTML::Object::EventListener>, and an hash or hash reference of options and this removes an event listener from the L<EventTarget|HTML::Object::Element>.
682              
683             It returns the L<HTML::Object::EventListener> thus removed upon success or L<perlfunc/undef> upon error and sets an L<error|Module::Generic/error>
684              
685             Possible options, to identify the event handler to remove, are:
686              
687             =over 4
688              
689             =item I<capture>
690              
691             A boolean value indicating that events of this type will be dispatched to the registered listener before being dispatched to any L<EventTarget|HTML::Object::Element> beneath it in the L<DOM|HTML::Object::Document> tree.
692              
693             =back
694              
695             For example:
696              
697             my $eh = $e->addEventListener( click => sub{ # do something }, { capture => 1, once => 1 });
698             $eh->remove;
699             # or
700             $e->removeEventListener( click => $same_code_ref, { capture => 1 });
701              
702             However, if the options provided differ from the ones initially set, it will not uniquely find the event handler. Only the C<capture> option is used to uniquely find the handler. For example:
703              
704             This will fail to remove the handler, because the C<capture> parameter does not have the same value.
705              
706             $e->removeEventListener( click => $same_code, { capture => 0 });
707              
708             This will fail to remove the handler, because the C<callback> value is not the same as the original.
709              
710             $e->removeEventListener( click => $some_other_code_ref, { capture => 1 });
711              
712             See L<for more information|https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener>
713              
714             =head1 AUTHOR
715              
716             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
717              
718             =head1 SEE ALSO
719              
720             L<https://developer.mozilla.org/en-US/docs/Web/API/EventTarget>
721              
722             L<https://developer.mozilla.org/en-US/docs/Web/Events/Event_handlers>
723              
724             L<https://developer.mozilla.org/en-US/docs/Web/API/Event>
725              
726             L<https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events>
727              
728             L<https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers>
729              
730             L<https://domevents.dev/> a very useful interactive playground app that enables learning about the behavior of the DOM Event system through exploration.
731              
732             L<https://www.quirksmode.org/js/events_order.html> discussion of capturing and bubbling — an excellently detailed piece by Peter-Paul Koch.
733              
734             L<https://www.quirksmode.org/js/events_access.html> discussion of the event object — another excellently detailed piece by Peter-Paul Koch.
735              
736             =head1 COPYRIGHT & LICENSE
737              
738             Copyright(c) 2021 DEGUEST Pte. Ltd.
739              
740             All rights reserved
741             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
742              
743             =cut