require 'cells'
require 'onfire'
require 'hooks'
require 'apotomo/event'
require 'apotomo/widget_shortcuts'
require 'apotomo/rails/view_helper'
require 'apotomo/rails/controller_methods' # FIXME.
require 'apotomo/widget/tree_node'
require 'apotomo/widget/event_methods'
require 'apotomo/widget/javascript_methods'
module Apotomo
# == Accessing Parameters
#
# Apotomo tries to prevent you from having to access the global #params hash. We have the following
# concepts to retrieve input data.
#
# 1. Configuration values are available both in render and triggered states. Pass those in #widget
# when creating the widget tree. Use #options for reading.
#
# has_widgets do |root|
# root << widget(:mouse_widget, 'mum', :favorites => ["Gouda", "Chedar"])
#
# and read in your widget state
#
# def display
# @cheese = options[:favorites].first
#
# 2. Request data from forms etc. is available through event.data in the triggered states.
# Use the #[] shortcut to access values directly.
#
# def update(evt)
# @cheese = Cheese.find evt[:cheese_id]
class Widget < Cell::Base
DEFAULT_VIEW_PATHS = [
File.join('app', 'widgets'),
File.join('app', 'widgets', 'layouts')
]
include Hooks
# Use this for setup code you're calling in every state. Almost like a +before_filter+ except that it's
# invoked after the initialization in #has_widgets.
#
# Example:
#
# class MouseWidget < Apotomo::Widget
# after_initialize :setup_cheese
#
# # we need @cheese in every state:
# def setup_cheese(*)
# @cheese = Cheese.find options[:cheese_id]
define_hook :after_initialize
define_hook :has_widgets
define_hook :after_add
attr_writer :visible
include TreeNode
include Onfire
include EventMethods
include WidgetShortcuts
include JavascriptMethods
helper Apotomo::Rails::ViewHelper
helper Apotomo::Rails::ActionViewMethods
abstract!
undef :display # We don't want #display to be listed in #internal_methods.
alias_method :widget_id, :name
# Runs callbacks for +name+ hook in instance context.
def run_widget_hook(name, *args)
self.class.callbacks_for_hook(name).each { |blk| instance_exec(*args, &blk) }
end
def add_has_widgets_blocks(*)
run_widget_hook(:has_widgets, self)
end
after_initialize :add_has_widgets_blocks
def initialize(parent_controller, id, options={})
super(parent_controller, options) # do that as long as cells do need a parent_controller.
@name = id
@visible = true
run_hook :after_initialize, self
end
def visible?
@visible
end
# Invokes +state+ and hopefully returns the rendered content.
def invoke(state, *args)
return render_state(state, *args) if state_accepts_args?(state)
render_state(state)
end
# Renders and returns a view for the current state. That's why it is usually called at the end of
# a state method.
#
# ==== Options
# * :view - Renders +view+. Defaults to the current state name.
# * :state - Invokes the +state+ method and returns whatever the state returns.
# * See http://rdoc.info/gems/cells/3.5.4/Cell/Rails#render-instance_method
#
# Example:
# class MouseWidget < Apotomo::Widget
# def eat
# render
# end
#
# render the view eat.haml.
#
# render :js => "alert('SQUEAK!');"
#
# issues a squeaking alert dialog on the page.
def render(*args, &block)
super
end
alias_method :emit, :render
def param(name)
msg = "Deprecated. Use #options for widget constructor options or #params for request data."
ActiveSupport::Deprecation.warn(msg)
raise msg
end
# Returns the widget named +widget_id+ if it's a descendent or self.
def find_widget(widget_id)
find {|node| node.name.to_s == widget_id.to_s}
end
def address_for_event(type, options={})
options.reverse_merge! :source => name,
:type => type,
:controller => parent_controller.controller_path # DISCUSS: dependency to parent_controller.
end
def url_for_event(type, options={})
apotomo_event_path address_for_event(type, options)
end
def self.controller_path
@controller_path ||= name.sub(/Widget$/, '').underscore unless anonymous?
end
# Renders the +widget+ (instance or id).
def render_widget(widget_id, state=:display, *args)
if widget_id.kind_of?(Widget)
widget = widget_id
else
widget = find_widget(widget_id) or raise "Couldn't render non-existent widget `#{widget_id}`"
end
widget.invoke(state, *args)
end
end
end