apotomo 1.0.5 → 1.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/CHANGES.textile +35 -7
- data/Gemfile +0 -2
- data/README.rdoc +9 -9
- data/apotomo.gemspec +3 -3
- data/lib/apotomo.rb +4 -47
- data/lib/apotomo/event.rb +2 -0
- data/lib/apotomo/event_handler.rb +0 -3
- data/lib/apotomo/event_methods.rb +6 -2
- data/lib/apotomo/invoke_event_handler.rb +5 -3
- data/lib/apotomo/javascript_generator.rb +19 -16
- data/lib/apotomo/rails/controller_methods.rb +84 -131
- data/lib/apotomo/rails/view_helper.rb +15 -31
- data/lib/apotomo/railtie.rb +24 -0
- data/lib/apotomo/request_processor.rb +17 -48
- data/lib/apotomo/test_case.rb +5 -5
- data/lib/apotomo/tree_node.rb +52 -61
- data/lib/apotomo/version.rb +1 -1
- data/lib/apotomo/widget.rb +70 -146
- data/lib/apotomo/widget/javascript_methods.rb +39 -0
- data/lib/apotomo/widget_shortcuts.rb +14 -40
- data/lib/generators/apotomo/widget_generator.rb +8 -9
- data/lib/generators/erb/widget_generator.rb +17 -0
- data/lib/generators/haml/widget_generator.rb +20 -0
- data/lib/generators/{apotomo/templates → templates}/view.erb +1 -1
- data/lib/generators/templates/view.haml +4 -0
- data/lib/generators/{apotomo/templates → templates}/widget.rb +1 -1
- data/lib/generators/{apotomo/templates → templates}/widget_test.rb +1 -1
- data/lib/generators/test_unit/widget_generator.rb +14 -0
- data/test/rails/caching_test.rb +10 -17
- data/test/rails/controller_methods_test.rb +9 -81
- data/test/rails/rails_integration_test.rb +76 -60
- data/test/rails/view_helper_test.rb +17 -28
- data/test/rails/widget_generator_test.rb +19 -31
- data/test/support/test_case_methods.rb +6 -20
- data/test/test_helper.rb +15 -25
- data/test/unit/event_handler_test.rb +1 -0
- data/test/unit/event_methods_test.rb +20 -8
- data/test/unit/event_test.rb +5 -0
- data/test/unit/javascript_generator_test.rb +19 -19
- data/test/unit/render_test.rb +17 -112
- data/test/unit/request_processor_test.rb +73 -111
- data/test/unit/test_case_test.rb +13 -7
- data/test/unit/widget_shortcuts_test.rb +24 -53
- data/test/unit/widget_test.rb +76 -36
- data/test/widgets/mouse/eat.erb +1 -0
- data/test/{fixtures → widgets}/mouse/eating.html.erb +0 -0
- data/test/{fixtures → widgets}/mouse/educate.html.erb +0 -0
- data/test/{fixtures → widgets}/mouse/feed.html.erb +0 -0
- data/test/{fixtures → widgets}/mouse/make_me_squeak.html.erb +0 -0
- data/test/{fixtures → widgets}/mouse/posing.html.erb +0 -0
- data/test/widgets/mouse/snuggle.html.erb +1 -0
- metadata +32 -50
- data/lib/apotomo/container_widget.rb +0 -10
- data/lib/apotomo/persistence.rb +0 -112
- data/lib/apotomo/rails/view_methods.rb +0 -7
- data/lib/apotomo/stateful_widget.rb +0 -29
- data/lib/apotomo/transition.rb +0 -46
- data/lib/generators/apotomo/templates/view.haml +0 -4
- data/test/dummy/log/production.log +0 -0
- data/test/dummy/log/server.log +0 -0
- data/test/dummy/public/javascripts/application.js +0 -2
- data/test/dummy/public/javascripts/controls.js +0 -965
- data/test/dummy/public/javascripts/dragdrop.js +0 -974
- data/test/dummy/public/javascripts/effects.js +0 -1123
- data/test/dummy/public/javascripts/prototype.js +0 -6001
- data/test/dummy/public/javascripts/rails.js +0 -175
- data/test/dummy/script/rails +0 -6
- data/test/dummy/tmp/app/cells/mouse_widget.rb +0 -11
- data/test/dummy/tmp/app/cells/mouse_widget/snuggle.html.erb +0 -7
- data/test/dummy/tmp/app/cells/mouse_widget/squeak.html.erb +0 -7
- data/test/dummy/tmp/test/widgets/mouse_widget_test.rb +0 -12
- data/test/fixtures/application_widget_tree.rb +0 -2
- data/test/fixtures/mouse/snuggle.html.erb +0 -1
- data/test/rails/view_methods_test.rb +0 -38
- data/test/unit/container_test.rb +0 -21
- data/test/unit/invoke_test.rb +0 -126
- data/test/unit/persistence_test.rb +0 -201
- data/test/unit/stateful_widget_test.rb +0 -58
- data/test/unit/test_addressing.rb +0 -110
- data/test/unit/test_jump_to_state.rb +0 -89
- data/test/unit/test_tab_panel.rb +0 -71
- data/test/unit/transition_test.rb +0 -34
@@ -1,7 +1,21 @@
|
|
1
1
|
module Apotomo
|
2
2
|
module Rails
|
3
|
+
# == #url_for_event
|
4
|
+
#
|
5
|
+
# = url_for_event(:paginate, :page => 2)
|
6
|
+
# #=> http://apotomo.de/mouse/process_event_request?type=paginate&source=mouse&page=2
|
7
|
+
#
|
8
|
+
# == #widget_id
|
9
|
+
#
|
10
|
+
# = widget_id
|
11
|
+
# #=> :mouse
|
12
|
+
#
|
13
|
+
# == #children
|
14
|
+
#
|
15
|
+
# - children.each do |kid|
|
16
|
+
# = render_widget kid
|
3
17
|
module ViewHelper
|
4
|
-
|
18
|
+
delegate :children, :url_for_event, :widget_id, :to => :controller
|
5
19
|
|
6
20
|
# Returns the app JavaScript generator.
|
7
21
|
def js_generator
|
@@ -20,31 +34,6 @@ module Apotomo
|
|
20
34
|
concat('<iframe id="apotomo_iframe" name="apotomo_iframe" style="display: none;"></iframe>'.html_safe) << form_tag(url_for_event(type, options), html_options, &block)
|
21
35
|
end
|
22
36
|
|
23
|
-
# Returns the url to trigger a +type+ event from the currently rendered widget.
|
24
|
-
# The source can be changed with +:source+. Additional +options+ will be appended to the query string.
|
25
|
-
#
|
26
|
-
# Note that this method will use the framework's internal routing if available (e.g. #url_for in Rails).
|
27
|
-
#
|
28
|
-
# Example:
|
29
|
-
# url_for_event(:paginate, :page => 2)
|
30
|
-
# #=> http://apotomo.de/mouse/process_event_request?type=paginate&source=mouse&page=2
|
31
|
-
def url_for_event(type, options={})
|
32
|
-
controller.url_for_event(type, options)
|
33
|
-
end
|
34
|
-
|
35
|
-
### TODO: test me.
|
36
|
-
### DISCUSS: rename to rendered_children ?
|
37
|
-
def content
|
38
|
-
@rendered_children.collect{|e| e.last}.join("\n").html_safe
|
39
|
-
end
|
40
|
-
|
41
|
-
# needs: suppress_javascript
|
42
|
-
def widget_javascript(*args, &block)
|
43
|
-
return if @suppress_js ### FIXME: implement with ActiveHelper and :locals.
|
44
|
-
|
45
|
-
javascript_tag(*args, &block)
|
46
|
-
end
|
47
|
-
|
48
37
|
# Wraps your content in a +div+ and sets the id. Feel free to pass additional html options.
|
49
38
|
#
|
50
39
|
# Example:
|
@@ -61,11 +50,6 @@ module Apotomo
|
|
61
50
|
options.reverse_merge!(:id => widget_id)
|
62
51
|
content_tag(:div, options, &block)
|
63
52
|
end
|
64
|
-
|
65
|
-
# Returns the widget id you passed in a has_widgets block.
|
66
|
-
def widget_id
|
67
|
-
controller.name
|
68
|
-
end
|
69
53
|
end
|
70
54
|
end
|
71
55
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "rails/railtie"
|
2
|
+
|
3
|
+
module Apotomo
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
rake_tasks do
|
6
|
+
load "apotomo/apotomo.rake"
|
7
|
+
end
|
8
|
+
|
9
|
+
# As we are a Railtie only, the routes won't be loaded automatically. Beside that, we want our
|
10
|
+
# route to be the very first (otherwise #resources might supersede it).
|
11
|
+
initializer 'apotomo.prepend_routes', :after => :add_routing_paths do |app|
|
12
|
+
app.routes_reloader.paths.unshift(File.dirname(__FILE__) + "/../../config/routes.rb")
|
13
|
+
end
|
14
|
+
|
15
|
+
# Include a lazy loader via has_widgets.
|
16
|
+
initializer 'apotomo.add_has_widgets' do |app|
|
17
|
+
ActionController::Base.extend Apotomo::Rails::ControllerMethodsLoader
|
18
|
+
end
|
19
|
+
|
20
|
+
initializer 'apotomo.setup_view_paths', :after => 'cells.setup_view_paths' do |app|
|
21
|
+
Apotomo::Widget.setup_view_paths!
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,70 +1,39 @@
|
|
1
1
|
module Apotomo
|
2
2
|
class RequestProcessor
|
3
|
-
|
3
|
+
include Hooks
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
define_hook :after_initialize
|
6
|
+
define_hook :after_fire
|
7
|
+
|
8
|
+
attr_reader :root
|
9
|
+
|
10
|
+
|
11
|
+
def initialize(controller, options={}, has_widgets_blocks=[])
|
9
12
|
@root = Widget.new(controller, 'root', :display)
|
10
13
|
|
11
14
|
attach_stateless_blocks_for(has_widgets_blocks, @root, controller)
|
12
15
|
|
13
|
-
|
14
|
-
@root = ::Apotomo::StatefulWidget.thaw_for(controller, session, @root)
|
15
|
-
else
|
16
|
-
#@root = flushed_root
|
17
|
-
|
18
|
-
flushed_root ### FIXME: set internal mode to flushed
|
19
|
-
end
|
16
|
+
run_hook :after_initialize, self
|
20
17
|
end
|
21
18
|
|
22
19
|
def attach_stateless_blocks_for(blocks, root, controller)
|
23
20
|
blocks.each { |blk| controller.instance_exec(root, &blk) }
|
24
21
|
end
|
25
22
|
|
26
|
-
|
27
|
-
|
28
|
-
@widgets_flushed = true
|
29
|
-
#widget('apotomo/widget', 'root')
|
30
|
-
end
|
31
|
-
|
32
|
-
def widgets_flushed?; @widgets_flushed; end
|
33
|
-
|
34
|
-
# Fires the request event in the widget tree and collects the rendered page updates.
|
23
|
+
# Called when the browser wants an url_for_event address. This fires the request event in
|
24
|
+
# the widget tree and collects the rendered page updates.
|
35
25
|
def process_for(request_params)
|
36
26
|
source = self.root.find_widget(request_params[:source]) or raise "Source #{request_params[:source].inspect} non-existent."
|
37
27
|
|
38
|
-
source.fire(request_params[:type].to_sym)
|
28
|
+
source.fire(request_params[:type].to_sym, request_params) # set data to params for now.
|
29
|
+
|
30
|
+
run_hook :after_fire, self
|
39
31
|
source.root.page_updates ### DISCUSS: that's another dependency.
|
40
32
|
end
|
41
33
|
|
42
|
-
|
43
|
-
def
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
# Serializes the current widget tree to the storage that was passed in the constructor.
|
48
|
-
# Call this at the end of a request.
|
49
|
-
def freeze!
|
50
|
-
Apotomo::StatefulWidget.freeze_for(@session, root)
|
51
|
-
end
|
52
|
-
|
53
|
-
# Renders the widget named <tt>widget_id</tt>, passing optional <tt>opts</tt> and a block to it.
|
54
|
-
# Use this in your #render_widget wrapper.
|
55
|
-
def render_widget_for(widget_id, opts, &block)
|
56
|
-
if widget_id.kind_of?(::Apotomo::Widget)
|
57
|
-
widget = widget_id
|
58
|
-
else
|
59
|
-
widget = root.find_widget(widget_id)
|
60
|
-
raise "Couldn't render non-existent widget `#{widget_id}`" unless widget
|
61
|
-
end
|
62
|
-
|
63
|
-
|
64
|
-
### TODO: pass options in invoke.
|
65
|
-
widget.opts = opts unless opts.empty?
|
66
|
-
|
67
|
-
widget.invoke(&block)
|
34
|
+
# Renders the widget named +widget_id+. Any additional args is passed through to Widget#invoke.
|
35
|
+
def render_widget_for(*args)
|
36
|
+
root.render_widget(*args)
|
68
37
|
end
|
69
38
|
|
70
39
|
# Computes the address hash for a +:source+ widget and an event +:type+.
|
data/lib/apotomo/test_case.rb
CHANGED
@@ -42,7 +42,7 @@ module Apotomo
|
|
42
42
|
def setup
|
43
43
|
super
|
44
44
|
@controller.instance_eval do
|
45
|
-
def
|
45
|
+
def controller_path
|
46
46
|
'barn'
|
47
47
|
end
|
48
48
|
end
|
@@ -53,7 +53,7 @@ module Apotomo
|
|
53
53
|
# Returns the widget tree from TestCase.has_widgets.
|
54
54
|
def root
|
55
55
|
blk = self.class.has_widgets_blocks or raise "Please setup a widget tree using TestCase.has_widgets"
|
56
|
-
@root ||=
|
56
|
+
@root ||= Apotomo::Widget.new(parent_controller, "root").tap do |root|
|
57
57
|
self.instance_exec(root, &blk)
|
58
58
|
end
|
59
59
|
end
|
@@ -63,8 +63,8 @@ module Apotomo
|
|
63
63
|
end
|
64
64
|
|
65
65
|
# Renders the widget +name+.
|
66
|
-
def render_widget(
|
67
|
-
@last_invoke = root.
|
66
|
+
def render_widget(*args)
|
67
|
+
@last_invoke = root.render_widget(*args)
|
68
68
|
end
|
69
69
|
|
70
70
|
# Triggers an event of +type+. You have to pass <tt>:source</tt> as options.
|
@@ -74,7 +74,7 @@ module Apotomo
|
|
74
74
|
# trigger :submit, :source => "post-comments"
|
75
75
|
def trigger(type, options)
|
76
76
|
source = root.find_widget(options.delete(:source))
|
77
|
-
source.
|
77
|
+
source.options.merge!(options) # TODO: this is just a try-out (what about children?).
|
78
78
|
source.fire(type)
|
79
79
|
root.page_updates # DISCUSS: use ControllerMethods?
|
80
80
|
end
|
data/lib/apotomo/tree_node.rb
CHANGED
@@ -4,27 +4,24 @@
|
|
4
4
|
module TreeNode
|
5
5
|
include Enumerable
|
6
6
|
|
7
|
-
attr_reader :
|
8
|
-
|
7
|
+
attr_reader :name, :childrenHash
|
8
|
+
attr_accessor :parent
|
9
9
|
|
10
10
|
def self.included(base)
|
11
11
|
base.after_initialize :initialize_tree_node
|
12
12
|
end
|
13
13
|
|
14
14
|
def initialize_tree_node(*)
|
15
|
-
|
15
|
+
root!
|
16
16
|
|
17
|
-
@childrenHash =
|
17
|
+
@childrenHash = {}
|
18
18
|
@children = []
|
19
19
|
end
|
20
20
|
|
21
21
|
# Print the string representation of this node.
|
22
22
|
def to_s
|
23
|
-
|
24
|
-
|
25
|
-
(root?() ? "ROOT" : "#{@parent.name}") +
|
26
|
-
" Children: #{@children.length}" +
|
27
|
-
" Total Nodes: #{s}"
|
23
|
+
"Node ID: #{widget_id} Parent: " + (root? ? "ROOT" : "#{parent.name}") +
|
24
|
+
" Children: #{children.length}" + " Total Nodes: #{size}"
|
28
25
|
end
|
29
26
|
|
30
27
|
# Convenience synonym for Tree#add method.
|
@@ -32,7 +29,7 @@ module TreeNode
|
|
32
29
|
# children hierarchies in the tree.
|
33
30
|
# E.g. root << child << grand_child
|
34
31
|
def <<(child)
|
35
|
-
|
32
|
+
add(child)
|
36
33
|
end
|
37
34
|
|
38
35
|
# Adds the specified child node to the receiver node.
|
@@ -40,13 +37,13 @@ module TreeNode
|
|
40
37
|
# The child is added as the last child in the current
|
41
38
|
# list of children for the receiver node.
|
42
39
|
def add(child)
|
43
|
-
|
40
|
+
raise "Child already added" if @childrenHash.has_key?(child.name)
|
44
41
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
42
|
+
@childrenHash[child.name] = child
|
43
|
+
@children << child
|
44
|
+
child.parent = self
|
45
|
+
|
46
|
+
child.run_widget_hook(:after_add, child, self)
|
50
47
|
child
|
51
48
|
end
|
52
49
|
|
@@ -55,59 +52,55 @@ module TreeNode
|
|
55
52
|
# if an alternate reference exists.
|
56
53
|
# Returns the child node.
|
57
54
|
def remove!(child)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
55
|
+
@childrenHash.delete(child.name)
|
56
|
+
@children.delete(child)
|
57
|
+
child.root! unless child == nil
|
58
|
+
child
|
62
59
|
end
|
63
60
|
|
64
61
|
# Removes this node from its parent. If this is the root node,
|
65
62
|
# then does nothing.
|
66
|
-
def
|
67
|
-
|
63
|
+
def remove_from_parent!
|
64
|
+
@parent.remove!(self) unless root?
|
68
65
|
end
|
69
66
|
|
70
67
|
# Removes all children from the receiver node.
|
71
68
|
def remove_all!
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
69
|
+
for child in @children
|
70
|
+
child.root!
|
71
|
+
end
|
72
|
+
@childrenHash.clear
|
73
|
+
@children.clear
|
74
|
+
self
|
78
75
|
end
|
79
76
|
|
80
77
|
|
81
78
|
# Private method which sets this node as a root node.
|
82
|
-
def setAsRoot!
|
83
|
-
@parent = nil
|
84
|
-
end
|
85
|
-
|
86
79
|
def root!
|
87
|
-
|
80
|
+
@parent = nil
|
88
81
|
end
|
89
|
-
|
82
|
+
|
90
83
|
# Indicates whether this node is a root node. Note that
|
91
84
|
# orphaned children will also be reported as root nodes.
|
92
85
|
def root?
|
93
|
-
|
86
|
+
@parent == nil
|
94
87
|
end
|
95
88
|
|
96
89
|
# Returns an array of all the immediate children.
|
97
90
|
# If a block is given, yields each child node to the block.
|
98
91
|
def children
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
92
|
+
if block_given?
|
93
|
+
@children.each { |child| yield child }
|
94
|
+
else
|
95
|
+
@children
|
96
|
+
end
|
104
97
|
end
|
105
98
|
|
106
99
|
# Returns every node (including the receiver node) from the
|
107
100
|
# tree to the specified block.
|
108
|
-
def each
|
109
|
-
|
110
|
-
|
101
|
+
def each(&block)
|
102
|
+
yield self
|
103
|
+
children { |child| child.each(&block) }
|
111
104
|
end
|
112
105
|
|
113
106
|
# Returns the requested node from the set of immediate
|
@@ -117,44 +110,42 @@ module TreeNode
|
|
117
110
|
# children is accessed (see Tree#children).
|
118
111
|
# If the key is not _numeric_, then it is assumed to be
|
119
112
|
# the *name* of the child node to be returned.
|
120
|
-
def [](
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
@childrenHash[key]
|
127
|
-
end
|
113
|
+
def [](name)
|
114
|
+
if name.kind_of?(Integer)
|
115
|
+
children[name]
|
116
|
+
else
|
117
|
+
childrenHash[name]
|
118
|
+
end
|
128
119
|
end
|
129
120
|
|
130
121
|
# Returns the total number of nodes in this tree, rooted
|
131
122
|
# at the receiver node.
|
132
123
|
def size
|
133
|
-
|
124
|
+
children.inject(1) {|sum, node| sum + node.size}
|
134
125
|
end
|
135
126
|
|
136
127
|
# Pretty prints the tree starting with the receiver node.
|
137
128
|
def printTree(tab = 0)
|
138
|
-
|
139
|
-
|
129
|
+
puts((' ' * tab) + self.to_s)
|
130
|
+
children {|child| child.printTree(tab + 4)}
|
140
131
|
end
|
141
132
|
|
142
133
|
# Returns the root for this node.
|
143
134
|
def root
|
144
|
-
|
145
|
-
|
146
|
-
|
135
|
+
root = self
|
136
|
+
root = root.parent while !root.root?
|
137
|
+
root
|
147
138
|
end
|
148
139
|
|
149
140
|
# Provides a comparision operation for the nodes. Comparision
|
150
141
|
# is based on the natural character-set ordering for the
|
151
142
|
# node names.
|
152
143
|
def <=>(other)
|
153
|
-
|
154
|
-
|
144
|
+
return +1 if other == nil
|
145
|
+
self.name <=> other.name
|
155
146
|
end
|
156
147
|
|
157
|
-
protected :parent=, :
|
148
|
+
protected :parent=, :root!
|
158
149
|
|
159
150
|
def find_by_path(selector)
|
160
151
|
next_node = self
|
@@ -165,7 +156,7 @@ module TreeNode
|
|
165
156
|
}
|
166
157
|
end
|
167
158
|
|
168
|
-
|
159
|
+
next_node
|
169
160
|
end
|
170
161
|
|
171
162
|
|
data/lib/apotomo/version.rb
CHANGED
data/lib/apotomo/widget.rb
CHANGED
@@ -5,12 +5,42 @@ require 'hooks'
|
|
5
5
|
require 'apotomo/tree_node'
|
6
6
|
require 'apotomo/event'
|
7
7
|
require 'apotomo/event_methods'
|
8
|
-
require 'apotomo/transition'
|
9
8
|
require 'apotomo/widget_shortcuts'
|
10
9
|
require 'apotomo/rails/view_helper'
|
10
|
+
require 'apotomo/rails/controller_methods' # FIXME.
|
11
|
+
|
12
|
+
require 'apotomo/widget/javascript_methods'
|
13
|
+
|
11
14
|
|
12
15
|
module Apotomo
|
16
|
+
# == Accessing Parameters
|
17
|
+
#
|
18
|
+
# Apotomo tries to prevent you from having to access the global #params hash. We have the following
|
19
|
+
# concepts to retrieve input data.
|
20
|
+
#
|
21
|
+
# 1. Configuration values are available both in render and triggered states. Pass those in #widget
|
22
|
+
# when creating the widget tree. Use #options for reading.
|
23
|
+
#
|
24
|
+
# has_widgets do |root|
|
25
|
+
# root << widget(:mouse_widget, 'mum', :favorites => ["Gouda", "Chedar"])
|
26
|
+
#
|
27
|
+
# and read in your widget state
|
28
|
+
#
|
29
|
+
# def display
|
30
|
+
# @cheese = options[:favorites].first
|
31
|
+
#
|
32
|
+
# 2. Request data from forms etc. is available through <tt>event.data</tt> in the triggered states.
|
33
|
+
# Use the <tt>#[]</tt> shortcut to access values directly.
|
34
|
+
#
|
35
|
+
# def update(evt)
|
36
|
+
# @cheese = Cheese.find evt[:cheese_id]
|
13
37
|
class Widget < Cell::Base
|
38
|
+
|
39
|
+
DEFAULT_VIEW_PATHS = [
|
40
|
+
File.join('app', 'widgets'),
|
41
|
+
File.join('app', 'widgets', 'layouts')
|
42
|
+
]
|
43
|
+
|
14
44
|
include Hooks
|
15
45
|
|
16
46
|
# Use this for setup code you're calling in every state. Almost like a +before_filter+ except that it's
|
@@ -23,27 +53,31 @@ module Apotomo
|
|
23
53
|
#
|
24
54
|
# # we need @cheese in every state:
|
25
55
|
# def setup_cheese(*)
|
26
|
-
# @cheese = Cheese.find
|
56
|
+
# @cheese = Cheese.find options[:cheese_id]
|
27
57
|
define_hook :after_initialize
|
28
58
|
define_hook :has_widgets
|
29
59
|
define_hook :after_add
|
30
60
|
|
31
|
-
|
32
|
-
|
33
|
-
|
61
|
+
attr_writer :visible
|
62
|
+
|
34
63
|
include TreeNode
|
35
64
|
|
36
65
|
include Onfire
|
37
|
-
include EventMethods
|
38
66
|
|
39
|
-
include
|
67
|
+
include EventMethods
|
40
68
|
include WidgetShortcuts
|
69
|
+
include JavascriptMethods
|
41
70
|
|
42
71
|
helper Apotomo::Rails::ViewHelper
|
72
|
+
helper Apotomo::Rails::ActionViewMethods
|
43
73
|
|
44
74
|
abstract!
|
75
|
+
|
45
76
|
undef :display # We don't want #display to be listed in #internal_methods.
|
46
77
|
|
78
|
+
alias_method :widget_id, :name
|
79
|
+
|
80
|
+
|
47
81
|
# Runs callbacks for +name+ hook in instance context.
|
48
82
|
def run_widget_hook(name, *args)
|
49
83
|
self.class.callbacks_for_hook(name).each { |blk| instance_exec(*args, &blk) }
|
@@ -55,180 +89,55 @@ module Apotomo
|
|
55
89
|
after_initialize :add_has_widgets_blocks
|
56
90
|
|
57
91
|
|
58
|
-
|
59
|
-
|
60
|
-
super(parent_controller, opts) # do that as long as cells do need a parent_controller.
|
92
|
+
def initialize(parent_controller, id, options={})
|
93
|
+
super(parent_controller, options) # do that as long as cells do need a parent_controller.
|
61
94
|
|
62
95
|
@name = id
|
63
|
-
@start_state = start_state
|
64
|
-
|
65
96
|
@visible = true
|
66
|
-
@cell = self ### DISCUSS: needed?
|
67
|
-
|
68
|
-
@params = parent_controller.params.dup.merge(opts)
|
69
97
|
|
70
98
|
run_hook :after_initialize, self
|
71
99
|
end
|
72
100
|
|
73
|
-
def last_state
|
74
|
-
action_name
|
75
|
-
end
|
76
|
-
|
77
101
|
def visible?
|
78
102
|
@visible
|
79
103
|
end
|
80
104
|
|
81
|
-
#
|
82
|
-
|
83
|
-
|
84
|
-
logger.debug "\ninvoke on #{name} with #{state.inspect}"
|
85
|
-
|
86
|
-
if state.blank?
|
87
|
-
state = next_state_for(last_state) || @start_state
|
88
|
-
end
|
89
|
-
|
90
|
-
logger.debug "#{name}: transition: #{last_state} to #{state}"
|
91
|
-
logger.debug " ...#{state}"
|
92
|
-
|
93
|
-
#render_state(state)
|
94
|
-
|
95
|
-
return render_state(state, event) if method(state).arity == 1
|
96
|
-
|
97
|
-
opts[:event] = event
|
105
|
+
# Invokes +state+ and hopefully returns the rendered content.
|
106
|
+
def invoke(state, *args)
|
107
|
+
return render_state(state, *args) if state_accepts_args?(state)
|
98
108
|
render_state(state)
|
99
109
|
end
|
100
110
|
|
101
|
-
|
102
111
|
# Render the view for the current state. Usually called at the end of a state method.
|
103
112
|
#
|
104
113
|
# ==== Options
|
105
114
|
# * <tt>:view</tt> - Specifies the name of the view file to render. Defaults to the current state name.
|
106
115
|
# * <tt>:template_format</tt> - Allows using a format different to <tt>:html</tt>.
|
107
116
|
# * <tt>:layout</tt> - If set to a valid filename inside your cell's view_paths, the current state view will be rendered inside the layout (as known from controller actions). Layouts should reside in <tt>app/cells/layouts</tt>.
|
108
|
-
# * <tt>:render_children</tt> - If false, automatic rendering of child widgets is turned off. Defaults to true.
|
109
|
-
# * <tt>:invoke</tt> - Explicitly define the state to be invoked on a child when rendering.
|
110
117
|
# * see Cell::Base#render for additional options
|
111
118
|
#
|
112
|
-
# Note that <tt>:text => ...</tt> and <tt>:update => true</tt> will turn off <tt>:frame</tt>.
|
113
|
-
#
|
114
119
|
# Example:
|
115
|
-
# class
|
116
|
-
# def
|
120
|
+
# class MouseWidget < Apotomo::StatefulWidget
|
121
|
+
# def eat
|
117
122
|
# # ... do something
|
118
123
|
# render
|
119
124
|
# end
|
120
125
|
#
|
121
|
-
# will just render the view <tt>
|
122
|
-
#
|
123
|
-
# def eating
|
124
|
-
# # ... do something
|
125
|
-
# render :view => :bored, :layout => "metal"
|
126
|
-
# end
|
127
|
-
#
|
128
|
-
# will use the view <tt>bored.html</tt> as template and even put it in the layout
|
129
|
-
# <tt>metal</tt> that's located at <tt>$RAILS_ROOT/app/cells/layouts/metal.html.erb</tt>.
|
126
|
+
# will just render the view <tt>eat.haml</tt>.
|
130
127
|
#
|
131
128
|
# render :js => "alert('SQUEAK!');"
|
132
129
|
#
|
133
130
|
# issues a squeaking alert dialog on the page.
|
134
|
-
def render(
|
135
|
-
|
136
|
-
return ""
|
137
|
-
end
|
138
|
-
|
139
|
-
if options[:text]
|
140
|
-
options.reverse_merge!(:render_children => false)
|
141
|
-
end
|
142
|
-
|
143
|
-
options.reverse_merge! :render_children => true,
|
144
|
-
:locals => {},
|
145
|
-
:invoke => {},
|
146
|
-
:suppress_js => false
|
147
|
-
|
148
|
-
|
149
|
-
rendered_children = render_children_for(options)
|
150
|
-
|
151
|
-
options[:locals].reverse_merge!(:rendered_children => rendered_children)
|
152
|
-
|
153
|
-
@suppress_js = options[:suppress_js] ### FIXME: implement with ActiveHelper and :locals.
|
154
|
-
|
155
|
-
|
156
|
-
render_view_for(options, action_name) # defined in Cell::Base.
|
131
|
+
def render(*args, &block)
|
132
|
+
super
|
157
133
|
end
|
158
134
|
|
159
135
|
alias_method :emit, :render
|
160
136
|
|
161
|
-
# Wraps the rendered content in a replace statement targeted at your +Apotomo.js_framework+ setting.
|
162
|
-
# Use +:selector+ to change the selector.
|
163
|
-
#
|
164
|
-
# Example:
|
165
|
-
#
|
166
|
-
# Assuming you set
|
167
|
-
# Apotomo.js_framework = :jquery
|
168
|
-
#
|
169
|
-
# and call replace in a state
|
170
|
-
#
|
171
|
-
# replace :view => :squeak, :selector => "div#mouse"
|
172
|
-
# #=> "$(\"div#mouse\").replaceWith(\"<div id=\\\"mum\\\">squeak!<\\/div>\")"
|
173
|
-
def replace(options={})
|
174
|
-
content = render(options)
|
175
|
-
Apotomo.js_generator.replace(options[:selector] || self.name, content)
|
176
|
-
end
|
177
|
-
|
178
|
-
# Same as replace except that the content is wrapped in an update statement.
|
179
|
-
#
|
180
|
-
# Example for +:jquery+:
|
181
|
-
#
|
182
|
-
# update :view => :squeak
|
183
|
-
# #=> "$(\"mum\").html(\"<div id=\\\"mum\\\">squeak!<\\/div>\")"
|
184
|
-
def update(options={})
|
185
|
-
content = render(options)
|
186
|
-
Apotomo.js_generator.update(options[:selector] || self.name, content)
|
187
|
-
end
|
188
|
-
|
189
|
-
# Force the FSM to go into <tt>state</tt>, regardless whether it's a valid
|
190
|
-
# transition or not.
|
191
|
-
### TODO: document the need for return.
|
192
|
-
def jump_to_state(state)
|
193
|
-
logger.debug "STATE JUMP! to #{state}"
|
194
|
-
|
195
|
-
render_state(state)
|
196
|
-
end
|
197
|
-
|
198
|
-
|
199
|
-
def visible_children
|
200
|
-
children.find_all { |kid| kid.visible? }
|
201
|
-
end
|
202
|
-
|
203
|
-
def render_children_for(options)
|
204
|
-
return {} unless options[:render_children]
|
205
|
-
|
206
|
-
render_children(options[:invoke])
|
207
|
-
end
|
208
|
-
|
209
|
-
def render_children(invoke_options={})
|
210
|
-
ActiveSupport::OrderedHash.new.tap do |rendered_children|
|
211
|
-
visible_children.each do |kid|
|
212
|
-
child_state = decide_state_for(kid, invoke_options)
|
213
|
-
logger.debug " #{kid.name} -> #{child_state}"
|
214
|
-
|
215
|
-
rendered_children[kid.name] = render_child(kid, child_state)
|
216
|
-
end
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
def render_child(cell, state)
|
221
|
-
cell.invoke(state)
|
222
|
-
end
|
223
|
-
|
224
|
-
def decide_state_for(child, invoke_options)
|
225
|
-
invoke_options.stringify_keys[child.name.to_s]
|
226
|
-
end
|
227
|
-
|
228
|
-
|
229
|
-
### DISCUSS: use #param only for accessing request data.
|
230
137
|
def param(name)
|
231
|
-
|
138
|
+
msg = "Deprecated. Use #options for widget constructor options or #params for request data."
|
139
|
+
ActiveSupport::Deprecation.warn(msg)
|
140
|
+
raise msg
|
232
141
|
end
|
233
142
|
|
234
143
|
|
@@ -247,6 +156,21 @@ module Apotomo
|
|
247
156
|
apotomo_event_path address_for_event(type, options)
|
248
157
|
end
|
249
158
|
|
250
|
-
|
159
|
+
|
160
|
+
def self.controller_path
|
161
|
+
@controller_path ||= name.sub(/Widget$/, '').underscore unless anonymous?
|
162
|
+
end
|
163
|
+
|
164
|
+
# Renders the +widget+ (instance or id).
|
165
|
+
def render_widget(widget_id, state=:display, *args)
|
166
|
+
if widget_id.kind_of?(Widget)
|
167
|
+
widget = widget_id
|
168
|
+
else
|
169
|
+
widget = find_widget(widget_id) or raise "Couldn't render non-existent widget `#{widget_id}`"
|
170
|
+
end
|
171
|
+
|
172
|
+
widget.invoke(state, *args)
|
173
|
+
end
|
251
174
|
end
|
252
175
|
end
|
176
|
+
|