package MooseX::Role::Listenable;

# ABSTRACT: A parameterized role for observable objects

=head1 NAME

MooseX::Role::Listenable - A parameterized role for observable objects

=head1 SYNOPSIS

  # a class with an observable feature- notifies observers
  # when door opened
  package Car;
  use Moose;
  with 'MooseX::Role::Listenable' => {event => 'door_opened'};
  sub open_door {
      ... # actually open the door
      $self->door_opened; # notify observers
  }

  # an observer class that can listen to door_opened events
  package Dashboard;
  use Moose;
  sub door_opened { print "Got door_opened event!\n" }

  # attach observer to observable
  $car->add_door_opened_listener($dashboard);

  # detach
  $car->remove_door_opened_listener($dashboard);

=head1 DESCRIPTION

A simple implemenation of the observable pattern. By adding this to a class:

  with 'MooseX::Role::Listenable' => {event => 'some_event_name'};

You are making the class observable for the event 'some_event_name'. You
can call the method C<some_event_name()> on the object, and all listeners
added with C<add_some_event_name_listener()> will be notified. Listeners
will be notified by calling their method C<some_event_name()>.

Note the list of listeners is a C<Set::Object::Weak>, so be sure to keep
a reference to them somewhere else.

=head1 SEE ALSO

C<Class::Listener>, C<Class::Observable>, and C<Aspect::Library::Listenable>.

=head1 AUTHOR

Ran Eilam <eilara@cpan.org>

=head1 COPYRIGHT

Ran Eilam <eilara@cpan.org>

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut

use MooseX::Role::Parameterized;
use Set::Object::Weak;

parameter event => (isa => 'Str', required => 1);

role {
    my $p = shift;
    my $event = $p->event;
    my $list = "_${event}_listeners";

    has $list => (
        is         => 'ro',
        lazy_build => 1,
        isa        => 'Set::Object::Weak',
    );

    method "_build_$list" => sub { Set::Object::Weak->new };

    method "add_${event}_listener" => sub {
        my ($self, $listener) = @_;
        $self->$list->insert($listener);
    };

    method "remove_${event}_listener" => sub {
        my ($self, $listener) = @_;
        $self->$list->remove($listener);
    };

    method $event => sub {
        my ($self, @args) = @_;
        $_->$event(@args) for $self->$list->members;
    };
};

1;