13. Listeners and Event Producers

Sometimes, you may have a service that produces events. In terms of a GUI, you might have a button that emits events liked “clicked”, “mouse over”, “mouse out”, “mouse down”, and so forth. Copland provides a framework for indicating that instances of a particular service point should listen for events from instances of another service point.

13.1. Event Producers

At one end of this functionality is the event producer. Using the above example, this would be the button. It is the service that generates and emits the events.

Any service can be an event producer—all it requires is that the service implement an #add_listener method by which other services may be added as interested parties. Also, although there is nothing that prevents you from using non-singleton services as event producers, it really only works correctly with services using the singleton and singleton-deferred model.

When an event occurs, the producer should notify those registered listeners by invoking a method on them. The method should be named “on_#{event}” (where event is the name of the event), and it should accept the producer as the first argument, followed by any number of other arguments (as needed for the given event). The listener method should only be invoked if the listener responds to that method; otherwise, it should be silently skipped.

Optionally, if a listener does not respond to a specific event, the producer may look for a method called “on_any_event”; if the listener responds to that, that method may be invoked instead, with the producer as the first parameter and the event as the second, followed by any additional arguments.

To make it easier to create event producers, you can mixin the Copland::EventProducer module into your existing services. It provides several methods, including #add_listener and #fire_event.

  require 'copland/event-producer'

  class MyProducerService
    include Copland::EventProducer

    def click
      do_something_about_it
      fire_event :clicked
    end

    def mouse_over
      do_something_else_about_it
      fire_event :mouse_over
    end
  end

13.2. Listeners

Listeners are at the other end of the producer/listener link. As with the event producer, any service can be a listener. It just needs to implement a method for each event that it is interested in, and then indicate (in its service point) that it wants to be registered as a listener of a particular service. Unlike event producers, the listeners can use any service model they like.

As described above, the listener methods are named “on_#{event}”, where “event” is the name of the event to listen to. Alternatively, a listener may declare a method called “on_any_event”, which will be called for any event that does not have an explicit listener method declared for.

  class MyListenerService
    def on_click( producer, *args )
      puts "Argh! It was clicked!" 
    end

    def on_any_event( producer, event, *args )
      puts "Something happened: #{event}" 
    end
  end

To register interest in the producer, use the listen-to element when defining the service point. This element expects an array of service point names. Every time the service point is instantiated, the list of listen-to elements will be processed and the new service will be added to each of those service points in turn, as a listener:

  service-points:

    Producer:
      implementor: MyProducerService

    Listener:
      implementor: MyListenerService
      listen-to:
        - Producer

13.3. The Registry as an Event Producer

Currently, Copland only comes with one event producer built in—the registry itself. Every time you instantiate a registry, it adds itself to itself as the “copland.Registry” service. When the registry is shutdown, it emits a :registry_shutdown event to all of its listeners.

Thus, if you have a service that you want to be able to do some cleanup when the registry is being shutdown, you can have that service listen to “copland.Registry”, and implement a listener method called on_registry_shutdown (or on_any_event).