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
|
|
15437
|
use strict; |
|
29
|
|
|
|
|
108
|
|
|
29
|
|
|
|
|
994
|
|
18
|
29
|
|
|
29
|
|
246
|
use warnings; |
|
29
|
|
|
|
|
114
|
|
|
29
|
|
|
|
|
1008
|
|
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
|
|
203
|
use parent qw( HTML::Object::Element ); |
|
29
|
|
|
|
|
110
|
|
|
29
|
|
|
|
|
449
|
|
23
|
29
|
|
|
29
|
|
2967
|
use vars qw( $PACK_SUB_RE $SIGNALS $VERSION ); |
|
29
|
|
|
|
|
108
|
|
|
29
|
|
|
|
|
2777
|
|
24
|
29
|
|
|
29
|
|
12783
|
use HTML::Object::EventListener; |
|
29
|
|
|
|
|
85
|
|
|
29
|
|
|
|
|
526
|
|
25
|
29
|
|
|
29
|
|
15652
|
use Scalar::Util (); |
|
29
|
|
|
|
|
85
|
|
|
29
|
|
|
|
|
800
|
|
26
|
29
|
|
|
29
|
|
212
|
use Want; |
|
29
|
|
|
|
|
97
|
|
|
29
|
|
|
|
|
6688
|
|
27
|
29
|
|
|
29
|
|
438
|
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
|
|
|
|
|
102
|
our $SIGNALS = {}; |
30
|
29
|
|
|
|
|
741
|
our $VERSION = 'v0.2.2'; |
31
|
|
|
|
|
|
|
}; |
32
|
|
|
|
|
|
|
|
33
|
29
|
|
|
29
|
|
230
|
use strict; |
|
29
|
|
|
|
|
100
|
|
|
29
|
|
|
|
|
662
|
|
34
|
29
|
|
|
29
|
|
173
|
use warnings; |
|
29
|
|
|
|
|
91
|
|
|
29
|
|
|
|
|
94370
|
|
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
sub init |
37
|
|
|
|
|
|
|
{ |
38
|
97
|
|
|
97
|
1
|
304
|
my $self = shift( @_ ); |
39
|
97
|
50
|
|
|
|
457
|
$self->{_init_strict_use_sub} = 1 unless( CORE::exists( $self->{_init_strict_use_sub} ) ); |
40
|
97
|
50
|
|
|
|
525
|
$self->{_exception_class} = 'HTML::Object::Exception' unless( CORE::exists( $self->{_exception_class} ) ); |
41
|
97
|
50
|
|
|
|
706
|
$self->SUPER::init( @_ ) || return( $self->pass_error ); |
42
|
97
|
|
|
|
|
448
|
$self->{event_listeners} = {}; |
43
|
97
|
|
|
|
|
697
|
return( $self ); |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
sub addEventListener |
47
|
|
|
|
|
|
|
{ |
48
|
5
|
|
|
5
|
1
|
40911
|
my $self = shift( @_ ); |
49
|
5
|
|
50
|
|
|
27
|
my $type = shift( @_ ) || return( $self->error( "No event listener type was provided." ) ); |
50
|
5
|
|
50
|
|
|
22
|
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
|
|
|
|
24
|
return( $self->error( "Event listener callback is not a code reference." ) ) if( ref( $callback ) ne 'CODE' ); |
67
|
5
|
|
|
|
|
32
|
my $opts = $self->_get_args_as_hash( @_ ); |
68
|
5
|
|
|
|
|
803
|
my @ok_params = qw( capture once passive signal debug ); |
69
|
5
|
|
|
|
|
12
|
my $params = {}; |
70
|
5
|
|
|
|
|
35
|
@$params{ @ok_params } = CORE::delete( @$opts{ @ok_params } ); |
71
|
5
|
|
|
|
|
12
|
my $post_processing; |
72
|
5
|
50
|
33
|
|
|
40
|
$post_processing = CORE::delete( $opts->{post_processing} ) if( CORE::exists( $opts->{post_processing} ) && ref( $opts->{post_processing} ) eq 'CODE' ); |
73
|
5
|
50
|
|
|
|
60
|
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
|
|
|
23
|
$params->{capture} //= 0; |
78
|
5
|
|
50
|
|
|
35
|
$params->{once} //= 0; |
79
|
5
|
|
50
|
|
|
29
|
$params->{passive} //= 0; |
80
|
5
|
|
|
|
|
36
|
my $key = join( ';', $type, Scalar::Util::refaddr( $callback ), $params->{capture} ); |
81
|
5
|
100
|
|
|
|
28
|
$self->{event_listeners} = {} if( !CORE::exists( $self->{event_listeners} ) ); |
82
|
5
|
|
|
|
|
15
|
my $repo = $self->{event_listeners}; |
83
|
5
|
|
50
|
|
|
31
|
my $debug = CORE::delete( $params->{debug} ) || 0; |
84
|
5
|
|
50
|
|
|
76
|
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
|
|
|
68
|
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
|
|
|
|
34
|
$repo->{ $type } = {} if( !CORE::exists( $repo->{ $type } ) ); |
98
|
5
|
100
|
|
|
|
29
|
$repo->{ $type }->{sequence} = $self->new_array if( !CORE::exists( $repo->{ $type }->{sequence} ) ); |
99
|
5
|
|
|
|
|
71
|
$repo->{ $type }->{ $key } = $eh; |
100
|
5
|
50
|
|
|
|
28
|
if( $repo->{ $type }->{sequence}->has( $key ) ) |
101
|
|
|
|
|
|
|
{ |
102
|
0
|
|
|
|
|
0
|
$repo->{ $type }->{sequence}->remove( $key ); |
103
|
|
|
|
|
|
|
} |
104
|
5
|
|
|
|
|
187992
|
$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
|
|
|
|
731
|
if( defined( $post_processing ) ) |
109
|
|
|
|
|
|
|
{ |
110
|
0
|
|
|
|
|
0
|
$post_processing->( $eh ); |
111
|
|
|
|
|
|
|
} |
112
|
5
|
|
|
|
|
47
|
return( $eh ); |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
sub dispatchEvent |
116
|
|
|
|
|
|
|
{ |
117
|
2
|
|
|
2
|
1
|
6
|
my $self = shift( @_ ); |
118
|
2
|
|
50
|
|
|
8
|
my $event = shift( @_ ) || return( $self->error( "No event object was provided." ) ); |
119
|
2
|
50
|
|
|
|
13
|
return( $self->error( "Event object provided ($event) is not an HTML::Object::Event" ) ) if( !$self->_is_a( $event => 'HTML::Object::Event' ) ); |
120
|
2
|
|
|
|
|
115
|
$event->target( $self ); |
121
|
|
|
|
|
|
|
|
122
|
2
|
|
50
|
|
|
85
|
my $type = $event->type || return( $self->error( "The event has no type associated with it!" ) ); |
123
|
2
|
|
|
|
|
1563
|
$type = lc( $type ); |
124
|
2
|
|
|
|
|
10
|
my $can_cancel = $event->cancelable; |
125
|
2
|
50
|
33
|
|
|
1728
|
return( $self ) if( $can_cancel && $event->cancelled ); |
126
|
|
|
|
|
|
|
# from current element to top one |
127
|
2
|
|
|
|
|
76370
|
my $path = $event->composedPath; |
128
|
2
|
|
|
|
|
20
|
$event->eventPhase( $event->CAPTURING_PHASE ); |
129
|
|
|
|
|
|
|
# Go from top to our element, i.e. reverse |
130
|
|
|
|
|
|
|
$path->reverse->foreach(sub |
131
|
|
|
|
|
|
|
{ |
132
|
6
|
|
|
6
|
|
196
|
my $node = shift( @_ ); |
133
|
6
|
|
|
|
|
22
|
$event->currentTarget( $node ); |
134
|
|
|
|
|
|
|
$node->handleEvent( $event ) || do |
135
|
6
|
50
|
|
|
|
4961
|
{ |
136
|
|
|
|
|
|
|
}; |
137
|
6
|
50
|
33
|
|
|
24
|
if( $can_cancel && $event->cancelled ) |
138
|
|
|
|
|
|
|
{ |
139
|
0
|
|
|
|
|
0
|
return; |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
# Make sure to return true to keep looping |
142
|
6
|
|
|
|
|
4316
|
return(1); |
143
|
2
|
|
|
|
|
1708
|
}); |
144
|
2
|
50
|
33
|
|
|
82
|
return( $self ) if( $can_cancel && $event->cancelled >= $event->CANCEL_IMMEDIATE_PROPAGATION ); |
145
|
2
|
|
|
|
|
1954
|
$event->eventPhase( $event->AT_TARGET ); |
146
|
2
|
|
|
|
|
1660
|
$event->currentTarget( $self ); |
147
|
2
|
|
|
|
|
1625
|
$self->handleEvent( $event ); |
148
|
2
|
50
|
33
|
|
|
10
|
return( $self ) if( $can_cancel && $event->cancelled >= $event->CANCEL_PROPAGATION ); |
149
|
|
|
|
|
|
|
# This event does not bubble, so we do nothing more |
150
|
2
|
50
|
|
|
|
1897
|
return( $self ) if( !$event->bubbles ); |
151
|
2
|
|
|
|
|
1839
|
$event->eventPhase( $event->BUBBLING_PHASE ); |
152
|
|
|
|
|
|
|
# Now, go from our element to the top one |
153
|
|
|
|
|
|
|
$path->for(sub |
154
|
|
|
|
|
|
|
{ |
155
|
6
|
|
|
6
|
|
167
|
my( $i, $node ) = @_; |
156
|
|
|
|
|
|
|
# Skip the first one which is us. |
157
|
6
|
100
|
|
|
|
21
|
return(1) if( $i == 0 ); |
158
|
4
|
|
|
|
|
15
|
$event->currentTarget( $node ); |
159
|
|
|
|
|
|
|
$node->handleEvent( $event ) || do |
160
|
4
|
50
|
|
|
|
3373
|
{ |
161
|
|
|
|
|
|
|
}; |
162
|
4
|
50
|
33
|
|
|
14
|
if( $can_cancel && $event->cancelled ) |
163
|
|
|
|
|
|
|
{ |
164
|
0
|
|
|
|
|
0
|
return; |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
# Make sure to return true to keep looping |
167
|
4
|
|
|
|
|
2735
|
return(1); |
168
|
2
|
|
|
|
|
1658
|
}); |
169
|
2
|
|
|
|
|
84
|
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
|
353
|
my $self = shift( @_ ); |
177
|
138
|
|
|
|
|
354
|
my $type = shift( @_ ); |
178
|
138
|
50
|
33
|
|
|
848
|
return( $self->error( "No event type propoded to get its event listeners." ) ) if( !defined( $type ) || !CORE::length( $type ) ); |
179
|
138
|
100
|
|
|
|
625
|
$self->{event_listeners} = {} if( !CORE::exists( $self->{event_listeners} ) ); |
180
|
138
|
|
|
|
|
350
|
my $repo = $self->{event_listeners}; |
181
|
138
|
100
|
|
|
|
812
|
return if( !scalar( keys( %$repo ) ) ); |
182
|
3
|
50
|
|
|
|
11
|
$repo->{ $type } = {} if( !CORE::exists( $repo->{ $type } ) ); |
183
|
3
|
50
|
|
|
|
16
|
$repo->{ $type }->{sequence} = $self->new_array if( !CORE::exists( $repo->{ $type }->{sequence} ) ); |
184
|
3
|
|
|
|
|
19
|
my $results = $self->new_array; |
185
|
|
|
|
|
|
|
$repo->{ $type }->{sequence}->foreach(sub |
186
|
|
|
|
|
|
|
{ |
187
|
3
|
|
|
3
|
|
44
|
my $key = shift( @_ ); |
188
|
3
|
50
|
33
|
|
|
25
|
$results->push( $repo->{ $type }->{ $key } ) if( CORE::exists( $repo->{ $type }->{ $key } ) && $self->_is_a( $repo->{ $type }->{ $key } => 'HTML::Object::EventListener' ) ); |
189
|
3
|
|
|
|
|
108
|
}); |
190
|
3
|
|
|
|
|
800
|
return( $results ); |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
sub handleEvent |
194
|
|
|
|
|
|
|
{ |
195
|
12
|
|
|
12
|
1
|
27
|
my $self = shift( @_ ); |
196
|
12
|
|
50
|
|
|
37
|
my $evt = shift( @_ ) || return( $self->error({ |
197
|
|
|
|
|
|
|
message => "No event was provided to handle.", |
198
|
|
|
|
|
|
|
class => 'HTML::Object::SyntaxError', |
199
|
|
|
|
|
|
|
}) ); |
200
|
12
|
50
|
|
|
|
44
|
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
|
|
|
|
479
|
return( $self ) if( $evt->cancelled ); |
205
|
12
|
|
|
|
|
8306
|
my $can_cancel = $evt->cancelable; |
206
|
|
|
|
|
|
|
|
207
|
12
|
|
|
|
|
10075
|
my $repo = $self->{event_listeners}; |
208
|
12
|
|
50
|
|
|
58
|
my $type = $evt->type || return( $self->error({ |
209
|
|
|
|
|
|
|
message => "No event type was provided.", |
210
|
|
|
|
|
|
|
class => 'HTML::Object::SyntaxError', |
211
|
|
|
|
|
|
|
}) ); |
212
|
12
|
100
|
|
|
|
9178
|
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
|
|
|
|
36
|
}) ) 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
|
|
|
|
27
|
}) ) 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
|
|
|
|
28
|
}) ) if( !$self->_is_a( $repo->{ $type }->{sequence} => 'Module::Generic::Array' ) ); |
226
|
|
|
|
|
|
|
|
227
|
8
|
|
|
|
|
295
|
$evt->currentTarget( $self ); |
228
|
8
|
|
|
|
|
6551
|
my $eventPhase = $evt->eventPhase; |
229
|
8
|
|
|
|
|
6038
|
foreach my $key ( @{$repo->{ $type }->{sequence}} ) |
|
8
|
|
|
|
|
44
|
|
230
|
|
|
|
|
|
|
{ |
231
|
8
|
50
|
33
|
|
|
50
|
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
|
|
|
|
30
|
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
|
|
|
|
|
21
|
my $listener = $repo->{ $type }->{ $key }; |
243
|
8
|
50
|
|
|
|
44
|
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
|
|
|
376
|
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
|
|
|
|
|
1939
|
next; |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
|
259
|
4
|
|
|
|
|
1123
|
my $code = $listener->code; |
260
|
4
|
50
|
|
|
|
3795
|
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
|
|
|
|
|
9
|
local $_ = $self; |
270
|
|
|
|
|
|
|
# Note: Should we catch a die, or let it die, if that were the case? |
271
|
4
|
|
|
|
|
20
|
$code->( $evt ); |
272
|
4
|
50
|
33
|
|
|
2072
|
last if( $can_cancel && $evt->cancelled == $evt->CANCEL_IMMEDIATE_PROPAGATION ); |
273
|
|
|
|
|
|
|
} |
274
|
8
|
|
|
|
|
4126
|
return( $self ); |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
sub hasEventListener |
278
|
|
|
|
|
|
|
{ |
279
|
2
|
|
|
2
|
1
|
38916
|
my $self = shift( @_ ); |
280
|
2
|
|
|
|
|
5
|
my $type = shift( @_ ); |
281
|
2
|
50
|
|
|
|
10
|
$self->{event_listeners} = {} if( !CORE::exists( $self->{event_listeners} ) ); |
282
|
2
|
|
|
|
|
7
|
my $repo = $self->{event_listeners}; |
283
|
2
|
50
|
|
|
|
11
|
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
|
|
|
|
|
5
|
my $n = 0; |
292
|
2
|
|
|
|
|
6
|
foreach my $t ( keys( %$repo ) ) |
293
|
|
|
|
|
|
|
{ |
294
|
2
|
|
|
|
|
4
|
$n += scalar( keys( %{$repo->{ $t }} ) ); |
|
2
|
|
|
|
|
8
|
|
295
|
2
|
50
|
|
|
|
10
|
$n-- if( CORE::exists( $repo->{ $t }->{sequence} ) ); |
296
|
|
|
|
|
|
|
} |
297
|
2
|
|
|
|
|
26
|
return( $self->new_number( $n ) ); |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
sub on : lvalue |
301
|
|
|
|
|
|
|
{ |
302
|
139
|
|
|
139
|
1
|
410
|
my $self = shift( @_ ); |
303
|
139
|
|
|
|
|
356
|
my $event = shift( @_ ); |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
return( $self->_set_get_callback({ |
306
|
|
|
|
|
|
|
get => sub |
307
|
|
|
|
|
|
|
{ |
308
|
138
|
|
|
138
|
|
96655
|
my $self = shift( @_ ); |
309
|
138
|
50
|
33
|
|
|
1352
|
return( $self->error( "No event provided to set event handler." ) ) if( !defined( $event ) || !CORE::length( $event ) ); |
310
|
138
|
|
100
|
|
|
863
|
my $listeners = $self->getEventListeners( $event ) || return; |
311
|
3
|
|
|
|
|
23
|
return( $listeners->first ); |
312
|
|
|
|
|
|
|
}, |
313
|
|
|
|
|
|
|
set => sub |
314
|
|
|
|
|
|
|
{ |
315
|
1
|
|
|
1
|
|
1034
|
my $self = shift( @_ ); |
316
|
1
|
|
|
|
|
11
|
my $arg = shift( @_ ); |
317
|
1
|
50
|
33
|
|
|
10
|
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
|
|
|
|
4
|
if( ref( $arg ) ne 'CODE' ) |
320
|
|
|
|
|
|
|
{ |
321
|
0
|
|
|
|
|
0
|
return( $self->error( "Value provided is not a code reference." ) ); |
322
|
|
|
|
|
|
|
} |
323
|
1
|
|
50
|
|
|
13
|
my $eh = $self->addEventListener( $event => $arg ) || return( $self->pass_error ); |
324
|
1
|
|
|
|
|
3
|
return( $eh ); |
325
|
|
|
|
|
|
|
} |
326
|
139
|
|
|
|
|
1801
|
}, @_ ) ); |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
sub removeEventListener |
330
|
|
|
|
|
|
|
{ |
331
|
2
|
|
|
2
|
1
|
57
|
my $self = shift( @_ ); |
332
|
2
|
|
50
|
|
|
7
|
my $type = shift( @_ ) || return( $self->error( "No event listener type was provided." ) ); |
333
|
2
|
|
|
|
|
21
|
my( $callback, $eh ); |
334
|
2
|
50
|
|
|
|
9
|
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
|
|
|
43
|
$callback = shift( @_ ) || return( $self->error( "No event listener callback was provided." ) ); |
342
|
|
|
|
|
|
|
} |
343
|
2
|
50
|
|
|
|
5
|
return( $self->error( "Event listener type \"$type\" contains illegal characters. It should contain only alphanumeric and _ characters." ) ) if( $type !~ /^\w+$/ ); |
344
|
2
|
50
|
33
|
|
|
30
|
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
|
|
|
14
|
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
|
|
|
|
|
11
|
my $opts = $self->_get_args_as_hash( @_ ); |
360
|
2
|
50
|
33
|
|
|
300
|
if( defined( $eh ) && !CORE::exists( $opts->{capture} ) ) |
361
|
|
|
|
|
|
|
{ |
362
|
0
|
|
|
|
|
0
|
$opts->{capture} = $eh->options->{capture}; |
363
|
|
|
|
|
|
|
} |
364
|
2
|
|
|
|
|
7
|
my @ok_params = qw( capture ); |
365
|
2
|
|
|
|
|
5
|
my $params = {}; |
366
|
2
|
|
|
|
|
11
|
@$params{ @ok_params } = CORE::delete( @$opts{ @ok_params } ); |
367
|
2
|
50
|
|
|
|
8
|
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
|
|
|
49
|
$params->{capture} //= 0; |
372
|
2
|
|
|
|
|
20
|
my $key = join( ';', $type, Scalar::Util::refaddr( $callback ), $params->{capture} ); |
373
|
2
|
50
|
|
|
|
22
|
$self->{event_listeners} = {} if( !CORE::exists( $self->{event_listeners} ) ); |
374
|
2
|
|
|
|
|
6
|
my $repo = $self->{event_listeners}; |
375
|
2
|
50
|
|
|
|
4
|
$repo->{ $type } = {} if( !CORE::exists( $repo->{ $type } ) ); |
376
|
2
|
|
|
|
|
18
|
$eh = CORE::delete( $repo->{ $type }->{ $key } ); |
377
|
2
|
50
|
|
|
|
15
|
$repo->{ $type }->{sequence} = $self->new_array if( !CORE::exists( $repo->{ $type }->{sequence} ) ); |
378
|
2
|
|
|
|
|
18
|
$repo->{ $type }->{sequence}->remove( $key ); |
379
|
2
|
|
|
|
|
1515
|
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 |