File Coverage

lib/Backbone/Events.pm
Criterion Covered Total %
statement 72 72 100.0
branch 14 16 87.5
condition 8 11 72.7
subroutine 14 14 100.0
pod 7 7 100.0
total 115 120 95.8


line stmt bran cond sub pod time code
1             package Backbone::Events;
2             $Backbone::Events::VERSION = '0.0.2';
3 9     9   565329 use Carp qw(confess);
  9         19  
  9         635  
4 9     9   5020 use List::MoreUtils qw(none);
  9         92196  
  9         62  
5 9     9   5098 use Scalar::Util qw(blessed);
  9         25  
  9         837  
6 9     9   50 use Moo::Role;
  9         9  
  9         77  
7 9     9   86738 use namespace::autoclean -also => qr/^__bbe_/;
  9         1960460  
  9         86  
8              
9             # ABSTRACT: a port of the Backbone.js event API
10              
11              
12             has _bbe_events => (
13                 is => 'ro',
14                 default => sub { {} },
15             );
16              
17             has _bbe_id => (
18                 is => 'ro',
19                 default => sub { __bbe_new_id() },
20             );
21              
22             has _bbe_listening_to => (
23                 is => 'ro',
24                 default => sub { {} },
25             );
26              
27             our $__bbe_last_id;
28             sub __bbe_new_id { ++$__bbe_last_id }
29              
30             sub _bbe_trigger {
31 44     44   1872     my ($self, $event_ref, $event, @args) = @_;
32 44         1840     my $cb = $event_ref->{cb};
33              
34 44 100       1806     if ($event_ref->{ns} eq 'all') {
35 8         17         $cb->($event, @args);
36                 } else {
37 36         1865         $cb->(@args);
38                 }
39              
40 44 100       7349     if ($event_ref->{once}) {
41 9         1792         my ($event, $listen_id) = @{$event_ref}{qw(event listen_id)};
  9         3669  
42 9   100     23199         $self->off($event, $cb, listen_id => $listen_id//'');
43                 }
44             }
45              
46             sub __bbe_wrap_multiple_events {
47                 my ($orig, $self, $events, @args) = @_;
48                 if (ref $events eq 'HASH') {
49                     $self->$orig($_, $events->{$_}, @args) for keys %$events;
50                 } elsif ($events and $events =~ /\s+/) {
51                     my $result;
52                     $result = $self->$orig($_, @args) for split /\s+/, $events;
53             # return last result
54                     return $result;
55                 } else {
56                     return $self->$orig($events, @args);
57                 }
58             }
59              
60             sub ___bbe_wrap_multiple_events2 {
61 60     60   102138     my ($orig, $self, $other, $events, @args) = @_;
62 60 100 100     5470     if (ref $events eq 'HASH') {
    100          
63 1         5         $self->$orig($other, $_, $events->{$_}, @args) for keys %$events;
64                 } elsif ($events and $events =~ /\s+/) {
65 1         1         my $result;
66 1         6         $result = $self->$orig($other, $_, @args) for split /\s+/, $events;
67             # return last result
68 1         3         return $result;
69                 } else {
70 58         5303         return $self->$orig($other, $events, @args);
71                 }
72             }
73              
74             sub __bbe_parse_ns {
75                 my ($event) = @_;
76                 my ($ns, $type) = split(':', $event//'', 2);
77                 return ($ns//'', $type//'');
78             }
79              
80             sub __bbe_query {
81                 my ($ids, $q) = @_;
82                 return grep {
83                     my $id = $_;
84                     my $match = 1;
85                     for my $field (keys %$q) {
86                         my $have = $ids->{$id}{$field} // '';
87                         my $want = $q->{$field};
88              
89                         my $type = ref $want;
90                         if ($type eq 'ARRAY') {
91                             if (none {$_ eq $have} @$want) {
92                                 $match = 0;
93                                 last;
94                             }
95                         } else {
96                             if ($want ne $have) {
97                                 $match = 0;
98                                 last;
99                             }
100                         }
101                     }
102                     $match;
103                 } keys %$ids;
104             }
105              
106             sub __bbe_does_events {
107                 my ($obj) = @_;
108                 return $obj
109                     && blessed($obj)
110                     && $obj->DOES(__PACKAGE__);
111             }
112              
113             around on => \&__bbe_wrap_multiple_events;
114             sub on {
115 55     55 1 4226     my ($self, $event, $cb, %opts) = @_;
116 55         4370     my ($ns, $type) = __bbe_parse_ns($event);
117 55         4392     $self->_bbe_events->{__bbe_new_id()} = {
118                     %opts,
119                     cb => $cb,
120                     ns => $ns,
121                     type => $type,
122                 };
123 55         18256     return $cb;
124             }
125              
126             around off => \&__bbe_wrap_multiple_events;
127             sub off {
128 39     39 1 3988     my ($self, $event, $cb, %opts) = @_;
129 39         4008     my ($ns, $type) = __bbe_parse_ns($event);
130              
131 39         4250     my @ids = __bbe_query($self->_bbe_events, {
132                     %opts,
133                     ( cb => $cb )x!! $cb,
134                     ( ns => $ns )x!! $ns,
135                     ( type => $type )x!! $type,
136                 });
137 39         4257     delete @{$self->_bbe_events}{@ids};
  39         48389  
138             }
139              
140             around trigger => \&__bbe_wrap_multiple_events;
141             sub trigger {
142 52     52 1 1837     my ($self, $event, @args) = @_;
143 52         1939     my ($ns, $type) = __bbe_parse_ns($event);
144              
145 52 100       2161     my @ids = __bbe_query($self->_bbe_events, {
146                     ns => [ 'all', $ns ],
147                     type => [ $type ? ($type, '') : ('') ],
148                 });
149              
150 52         2112     for my $id (@ids) {
151 44         1958         my $event_ref = $self->_bbe_events->{$id};
152 44         1869         $self->_bbe_trigger($event_ref, $event, @args);
153                 }
154             }
155              
156             around once => \&__bbe_wrap_multiple_events;
157             sub once {
158 10     10 1 1830     my ($self, $event, $cb) = @_;
159 10         18382     $self->on($event, $cb, once => 1);
160 10         9029     return $cb;
161             }
162              
163             around listen_to => \&___bbe_wrap_multiple_events2;
164             sub listen_to {
165 29     29 1 2217     my ($self, $other, $event, $cb, %opts) = @_;
166 29 50 33     2310     confess "Cannot call listen_to on object that does not consume Backbone::Events"
167                     if $other and not __bbe_does_events($other);
168              
169 29         30471     my ($ns, $type) = __bbe_parse_ns($event);
170 29         2024     $self->_bbe_listening_to->{__bbe_new_id()} = {
171                     %opts,
172                     cb => $cb,
173                     event => $event,
174                     ns => $ns,
175                     other => $other,
176                     other_id => $other->_bbe_id,
177                     type => $type,
178                 };
179 29         19742     $other->on($event, $cb, %opts, listen_id => $self->_bbe_id);
180              
181 29         9107     return $cb;
182             }
183              
184             around stop_listening => \&___bbe_wrap_multiple_events2;
185             sub stop_listening {
186 25     25 1 938     my ($self, $other, $event, $cb) = @_;
187 25         972     my ($ns, $type) = __bbe_parse_ns($event);
188 25 50 66     975     confess "Cannot call stop_listening on object that does not consume Backbone::Events"
189                     if $other and not __bbe_does_events($other);
190              
191 25         982     my $query = {
192                     ( cb => $cb )x!! $cb,
193                     ( ns => $ns )x!! $ns,
194                     ( type => $type )x!! $type,
195                 };
196 25 100       926     $query->{other_id} = $other->_bbe_id if $other;
197 25         971     my @ids = __bbe_query($self->_bbe_listening_to, $query);
198              
199 25         924     for my $id (@ids) {
200 20         935         my $listen_ref = $self->_bbe_listening_to->{$id};
201 20         879         my $other_obj = $listen_ref->{other};
202 20         897         my @args = @{$listen_ref}{qw(event cb)};
  20         1805  
203 20         9119         $other_obj->off(@args, listen_id => $self->_bbe_id);
204                 }
205 25         913     delete @{$self->_bbe_listening_to}{@ids};
  25         7200  
206             }
207              
208             around listen_to_once => \&___bbe_wrap_multiple_events2;
209             sub listen_to_once {
210 8     8 1 2257     my ($self, $other, $event, $cb) = @_;
211 8         22477     $self->listen_to($other, $event, $cb, once => 1);
212 8         8983     return $cb;
213             }
214              
215             1;
216              
217             __END__
218            
219             =pod
220            
221             =encoding UTF-8
222            
223             =head1 NAME
224            
225             Backbone::Events - a port of the Backbone.js event API
226            
227             =head1 VERSION
228            
229             version 0.0.2
230            
231             =head1 SYNOPSIS
232            
233             package MyProducer {
234             use Moo;
235             with 'Backbone::Events';
236             };
237             my $pub = MyProducer->new;
238            
239             package MySubscriber {
240             use Moo;
241             with 'Backbone::Events';
242             };
243             my $sub = MySubscriber->new;
244            
245             $sub->listen_to($pub, 'some-event', sub { ... })
246             ...
247             $pub->trigger('some-event', qw(args for callback));
248            
249             =head1 DESCRIPTION
250            
251             Backbone::Events is a Moo::Role which provides a simple interface for binding
252             and triggering custom named events. Events do not have to be declared before
253             they are bound, and may take passed arguments.
254            
255             Events can be optionally namespaced by prepending the event with the
256             namespace: '$namespace:$event'.
257            
258             =head1 METHODS
259            
260             =head2 on($event, $callback)
261            
262             Bind a callback to an object.
263            
264             Callbacks bound to the special 'all' event will be triggered when any event
265             occurs, and are passed the name of the event as the first argument.
266            
267             Returns the callback that was passed. This is mainly so anonymous functions
268             can be returned, and later passed back to 'off'.
269            
270             =head2 off([$event], [$callback])
271            
272             Remove a previously-bound callback from an object.
273            
274             =head2 trigger($event, @args)
275            
276             Trigger callbacks for the given event.
277            
278             =head2 once($event, $callback)
279            
280             Just like 'on', but causes the bound callback to fire only once before being
281             removed.
282            
283             Returns the callback that was passed. This is mainly so anonymous functions
284             can be returned, and later passed back to 'off'.
285            
286             =head2 listen_to($other, $event, $callback)
287            
288             Tell an object to listen to a particular event on an other object.
289             The other object must consume the Backbone::Events role.
290            
291             Returns the callback that was passed. This is mainly so anonymous functions
292             can be returned, and later passed back to 'stop_listening'.
293            
294             =head2 stop_listening([$other], [$event], [$callback])
295            
296             Tell an object to stop listening to events.
297            
298             =head2 listen_to_once($other, $event, $callback)
299            
300             Just like 'listen_to', but causes the bound callback to fire only once before
301             being removed.
302            
303             Returns the callback that was passed. This is mainly so anonymous functions
304             can be returned, and later passed back to 'stop_listening'.
305            
306             =head1 SEE ALSO
307            
308             L<http://backbonejs.org/#Events>
309            
310             =head1 AUTHOR
311            
312             Mark Flickinger
313            
314             =head1 COPYRIGHT AND LICENSE
315            
316             This software is copyright (c) 2015 by Mark Flickinger.
317            
318             This is free software; you can redistribute it and/or modify it under
319             the same terms as the Perl 5 programming language system itself.
320            
321             =cut
322