require 'apotomo/invoke_event_handler'
module Apotomo
# Event-related methods and onfire bridge for Widget.
module EventMethods
extend ActiveSupport::Concern
included do
after_initialize :add_class_event_handlers
inheritable_attr :responds_to_event_options
self.responds_to_event_options = []
end
attr_writer :page_updates
def page_updates
@page_updates ||= []
end
module ClassMethods
# Instructs the widget to look out for +type+ events. If an appropriate event starts from or passes the widget,
# the defined trigger state is executed.
#
# class MouseWidget < Apotomo::Widget
# responds_to_event :squeak
#
# def squeak(evt)
# update
# end
#
# Calls #squeak when a :squeak event is encountered.
#
# == Options
# Any option except the event +type+ is optional.
#
# [:with => state]
# executes +state+, defaults to +type+.
# responds_to_event :squeak, :with => :chirp
# will invoke the +#chirp+ state method.
# [:on => id]
# execute the trigger state on another widget.
# responds_to_event :squeak, :on => :cat
# will invoke the +#squeak+ state on the +:cat+ widget.
# [:from => id]
# executes the state only if the event origins from +id+.
# responds_to_event :squeak, :from => :kid
# will invoke the +#squeak+ state if +:kid+ triggered and if +:kid+ is a decendent of the current widget.
# [:passing => id]
# attaches the observer to another widget. Useful if you want to catch bubbling events in +root+.
# responds_to_event :squeak, :passing => :root
# will invoke the state on the current widget if the event passes +:root+ (which is highly probable).
#
# == Inheritance
# Note that the observers are inherited. This allows deriving a widget class without having to redefine the
# responds_to_event blocks.
def responds_to_event(*options)
# DISCUSS: this is a Hooks.declarative_attr candidate, too.
return set_global_event_handler(*options) if options.dup.extract_options![:passing]
responds_to_event_options << options
end
private
# Adds an event handler to a non-local widget. Called in #responds_to_event when the
# :passing option is set.
#
# This usually leads to something like
# root.respond_to_event :click, :on => 'jerry'
def set_global_event_handler(type, options)
after_add do
opts = options.reverse_merge(:on => widget_id)
root.find_widget(opts.delete(:passing)).respond_to_event(type, opts)
end
end
end
# Same as #responds_to_event but executed on the widget instance, only.
def respond_to_event(type, options={})
options = options.reverse_merge(:once => true,
:with => type,
:on => self.name)
handler = InvokeEventHandler.new(:widget_id => options[:on], :state => options[:with])
return if options[:once] and event_table.all_handlers_for(type, options[:from]).include?(handler)
on(type, :call => handler, :from => options[:from])
end
# Fire an event of +type+ and let it bubble up. You may add arbitrary payload data to the event.
#
# Example:
#
# trigger(:dropped, :area => 59)
#
# which can be queried in a triggered state.
#
# def on_drop(event)
# if event[:area] == 59
def trigger(*args)
fire(*args)
end
# Get all handlers from self for the passed event (overriding Onfire#local_event_handlers).
def handlers_for_event(event)
event_table.all_handlers_for(event.type, event.source.name) # we key with widget_id.
end
protected
def event_for(*args) # defined in Onfire: we want Apotomo::Event.
Event.new(*args)
end
# Actually executes the #responds_to_event calls from the class on the instance.
def add_class_event_handlers(*)
self.class.responds_to_event_options.each { |options| respond_to_event(*options) }
end
end
end