-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
callback.rb
145 lines (126 loc) · 5.58 KB
/
callback.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# Copyright (c) 2008-2013 Michael Dvorkin and contributors.
#
# Fat Free CRM is freely distributable under the terms of MIT license.
# See MIT-LICENSE file or http://www.opensource.org/licenses/mit-license.php
#------------------------------------------------------------------------------
module FatFreeCRM
module Callback
@@classes = [] # Classes that inherit from FatFreeCRM::Callback::Base.
@@responder = {} # Class instances that respond to (i.e. implement) hook methods.
# Also includes class instances that implement a
# set of view hook operations (insert_after, replace, etc).
# Adds a class inherited from from FatFreeCRM::Callback::Base.
#--------------------------------------------------------------------------
def self.add(klass)
@@classes << klass
end
# [Controller] and [Legacy View] Hooks
# -----------------------------------------------------------------------------
# Finds class instance that responds to given method.
#------------------------------------------------------------------------------
def self.responder(method)
@@responder[method] ||= @@classes.map(&:instance).select { |instance| instance.respond_to?(method) }
end
# Invokes the hook named :method and captures its output.
#--------------------------------------------------------------------------
def self.hook(method, caller, context = {})
str = "".html_safe
responder(method).map do |m|
str << m.send(method, caller, context) if m.respond_to?(method)
end
str
end
# [View] Hooks
# -----------------------------------------------------------------------------
# Find class instances that contain operations for the given view hook.
#------------------------------------------------------------------------------
def self.view_responder(method)
@@responder[method] ||= @@classes.map(&:instance).select { |instance| instance.class.view_hooks[method] }
end
# Invokes the view hook Proc stored under :hook and captures its output.
# => Instead of defining methods on the class, view hooks are
# stored as Procs in a hash. This allows the same hook to be manipulated in
# multiple ways from within a single Callback subclass.
# The hook returns:
# - empty hash if no hook with this name was detected.
# - a hash of arrays containing Procs and positions to insert content.
#--------------------------------------------------------------------------
def self.view_hook(hook, caller, context = {})
view_responder(hook).each_with_object(Hash.new([])) do |instance, response|
# Process each operation within each view hook, storing the data in a hash.
instance.class.view_hooks[hook].each do |op|
response[op[:position]] += [op[:proc].call(caller, context)]
end
response
end
end
#--------------------------------------------------------------------------
class Base
include Singleton
def self.inherited(child)
FatFreeCRM::Callback.add(child)
# Positioning hash to determine where content is placed.
child.class_eval do
@view_hooks = Hash.new([])
end
super
end
class << self
attr_accessor :view_hooks
def add_view_hook(hook, proc, position)
@view_hooks[hook] += [{ proc: proc,
position: position }]
end
def insert_before(hook, &block)
add_view_hook(hook, block, :before)
end
def insert_after(hook, &block)
add_view_hook(hook, block, :after)
end
def replace(hook, &block)
add_view_hook(hook, block, :replace)
end
def remove(hook)
add_view_hook(hook, proc { "" }, :replace)
end
end
end # class Base
# This makes it possible to call hook() without FatFreeCRM::Callback prefix.
# Returns stringified data when called from within templates, and the actual
# data otherwise.
#--------------------------------------------------------------------------
module Helper
def hook(method, caller, context = {}, &block)
is_view_hook = caller.is_haml?
# If a block was given, hooks are able to replace, append or prepend view content.
if is_view_hook
hooks = FatFreeCRM::Callback.view_hook(method, caller, context)
# Add content to the view in the following order:
# -- before
# -- replace || original block
# -- after
view_data = "".html_safe
hooks[:before].each { |data| view_data << data }
# Only render the original view block if there are no pending :replace operations
if hooks[:replace].empty?
view_data << if block_given?
capture(&block)
else
# legacy view hooks
FatFreeCRM::Callback.hook(method, caller, context)
end
else
hooks[:replace].each { |data| view_data << data }
end
hooks[:after].each { |data| view_data << data }
view_data
else
# Hooks called without blocks are either controller or legacy view hooks
FatFreeCRM::Callback.hook(method, caller, context)
end
end
end # module Helper
end # module Callback
end # module FatFreeCRM
ActionView::Base.send(:include, FatFreeCRM::Callback::Helper)
ActionController::Base.send(:include, FatFreeCRM::Callback::Helper)