Skip to content

Commit b51186f

Browse files
author
Mike Dvorkin
committed
Merge branch 'ffcrm' of git://github.com/crossroads/fat_free_crm into crossroads/ffcrm
2 parents 3ac5058 + b94bc5b commit b51186f

File tree

2 files changed

+159
-5
lines changed

2 files changed

+159
-5
lines changed

lib/fat_free_crm/callback.rb

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,23 @@ module FatFreeCRM
1919
module Callback
2020
@@classes = [] # Classes that inherit from FatFreeCRM::Callback::Base.
2121
@@responder = {} # Class instances that respond to (i.e. implement) hook methods.
22+
# Also includes class instances that implement a
23+
# set of view hook operations (insert_after, replace, etc).
2224

2325
# Adds a class inherited from from FatFreeCRM::Callback::Base.
2426
#--------------------------------------------------------------------------
2527
def self.add(klass)
2628
@@classes << klass
2729
end
2830

31+
# [Controller] and [Legacy View] Hooks
32+
# -----------------------------------------------------------------------------
33+
2934
# Finds class instance that responds to given method.
3035
#------------------------------------------------------------------------------
3136
def self.responder(method)
3237
@@responder[method] ||= @@classes.map { |klass| klass.instance }.select { |instance| instance.respond_to?(method) }
3338
end
34-
3539
# Invokes the hook named :method and captures its output. The hook returns:
3640
# - empty array if no hook with this name was detected.
3741
# - array with single item returned by the hook.
@@ -43,14 +47,58 @@ def self.hook(method, caller, context = {})
4347
end
4448
end
4549

50+
51+
# [View] Hooks
52+
# -----------------------------------------------------------------------------
53+
54+
# Find class instances that contain operations for the given view hook.
55+
#------------------------------------------------------------------------------
56+
def self.view_responder(method)
57+
@@responder[method] ||= @@classes.map { |klass| klass.instance }.select { |instance| instance.class.view_hooks[method] }
58+
end
59+
# Invokes the view hook Proc stored under :hook and captures its output.
60+
# => Instead of defining methods on the class, view hooks are
61+
# stored as Procs in a hash. This allows the same hook to be manipulated in
62+
# multiple ways from within a single Callback subclass.
63+
# The hook returns:
64+
# - empty hash if no hook with this name was detected.
65+
# - a hash of arrays containing Procs and positions to insert content.
66+
#--------------------------------------------------------------------------
67+
def self.view_hook(hook, caller, context = {})
68+
view_responder(hook).inject(Hash.new([])) do |response, instance|
69+
# Process each operation within each view hook, storing the data in a hash.
70+
instance.class.view_hooks[hook].each do |op|
71+
response[op[:position]] += [op[:proc].call(caller, context)]
72+
end
73+
response
74+
end
75+
end
76+
4677
#--------------------------------------------------------------------------
4778
class Base
4879
include Singleton
49-
5080
def self.inherited(child)
5181
FatFreeCRM::Callback.add(child)
82+
# Positioning hash to determine where content is placed.
83+
child.class_eval do
84+
@view_hooks = Hash.new([])
85+
end
5286
super
5387
end
88+
89+
class << self
90+
attr_accessor :view_hooks
91+
92+
def add_view_hook(hook, proc, position)
93+
@view_hooks[hook] += [{:proc => proc,
94+
:position => position}]
95+
end
96+
97+
def insert_before(hook, &block); add_view_hook(hook, block, :before); end
98+
def insert_after(hook, &block); add_view_hook(hook, block, :after); end
99+
def replace(hook, &block); add_view_hook(hook, block, :replace); end
100+
def remove(hook); add_view_hook(hook, Proc.new{""}, :replace); end
101+
end
54102

55103
end # class Base
56104

@@ -59,9 +107,32 @@ def self.inherited(child)
59107
# data otherwise.
60108
#--------------------------------------------------------------------------
61109
module Helper
62-
def hook(method, caller, context = {})
63-
data = FatFreeCRM::Callback.hook(method, caller, context)
64-
caller.class.to_s.start_with?("ActionView") ? data.join : data
110+
def hook(method, caller, context = {}, &block)
111+
is_view_hook = caller.class.to_s.start_with?("ActionView")
112+
113+
# If a block was given, hooks are able to replace, append or prepend view content.
114+
if block_given? and is_view_hook
115+
hooks = FatFreeCRM::Callback.view_hook(method, caller, context)
116+
# Add content to the view in the following order:
117+
# -- before
118+
# -- replace || original block
119+
# -- after
120+
view_data = []
121+
hooks[:before].each{|data| view_data << data }
122+
# Only render the original view block if there are no pending :replace operations
123+
if hooks[:replace].empty?
124+
view_data << capture(&block)
125+
else
126+
hooks[:replace].each{|data| view_data << data }
127+
end
128+
hooks[:after].each{|data| view_data << data }
129+
view_data.join
130+
131+
else
132+
# Hooks called without blocks are either controller or legacy view hooks
133+
data = FatFreeCRM::Callback.hook(method, caller, context)
134+
is_view_hook ? data.join : data
135+
end
65136
end
66137
end # module Helper
67138

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2+
3+
module CallbackSpecHelper
4+
class TestCallback < FatFreeCRM::Callback::Base
5+
# --------------------------------------------
6+
insert_before :test_before do |view, context|
7+
"BEFORE-"
8+
end
9+
# --------------------------------------------
10+
insert_after :test_after do |view, context|
11+
"-AFTER"
12+
end
13+
# --------------------------------------------
14+
replace :test_replace do |view, context|
15+
"REPLACE"
16+
end
17+
# --------------------------------------------
18+
remove :test_remove
19+
# --------------------------------------------
20+
insert_before :test_before_and_after do |view, context|
21+
"BEFORE-"
22+
end
23+
insert_after :test_before_and_after do |view, context|
24+
"-AFTER"
25+
end
26+
# --------------------------------------------
27+
insert_before :test_before_and_after_with_replace do |view, context|
28+
"BEFORE-"
29+
end
30+
insert_after :test_before_and_after_with_replace do |view, context|
31+
"-AFTER"
32+
end
33+
replace :test_before_and_after_with_replace do |view, context|
34+
"REPLACED"
35+
end
36+
# --------------------------------------------
37+
def test_legacy(view, context={})
38+
"LEGACY"
39+
end
40+
end
41+
42+
def test_hook(position)
43+
Haml::Engine.new(%Q^
44+
= hook("test_#{position}".to_sym, ActionView::Base.new) do
45+
BLOCK^).render.gsub("\n",'')
46+
end
47+
48+
end
49+
50+
include FatFreeCRM::Callback::Helper
51+
include ActionView::Helpers::CaptureHelper
52+
include CallbackSpecHelper
53+
54+
describe FatFreeCRM::Callback do
55+
it "should insert content before the block" do
56+
test_hook(:before).should == "BEFORE-BLOCK"
57+
end
58+
59+
it "should insert content after the block" do
60+
test_hook(:after).should == "BLOCK-AFTER"
61+
end
62+
63+
it "should replace the content of the block" do
64+
test_hook(:replace).should == "REPLACE"
65+
end
66+
67+
it "should remove the block" do
68+
test_hook(:remove).should == ""
69+
end
70+
71+
it "should be able to insert content before and after the block" do
72+
test_hook(:before_and_after).should == "BEFORE-BLOCK-AFTER"
73+
end
74+
75+
it "should be able to insert content before and after a replaced block" do
76+
test_hook(:before_and_after_with_replace).should == "BEFORE-REPLACED-AFTER"
77+
end
78+
79+
it "should still work for legacy hooks" do
80+
Haml::Engine.new("= hook(:test_legacy, ActionView::Base.new)").render.
81+
gsub("\n",'').should == "LEGACY"
82+
end
83+
end

0 commit comments

Comments
 (0)