apotomo 1.0.5 → 1.1.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|