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.
Files changed (83) hide show
  1. data/.gitignore +5 -0
  2. data/CHANGES.textile +35 -7
  3. data/Gemfile +0 -2
  4. data/README.rdoc +9 -9
  5. data/apotomo.gemspec +3 -3
  6. data/lib/apotomo.rb +4 -47
  7. data/lib/apotomo/event.rb +2 -0
  8. data/lib/apotomo/event_handler.rb +0 -3
  9. data/lib/apotomo/event_methods.rb +6 -2
  10. data/lib/apotomo/invoke_event_handler.rb +5 -3
  11. data/lib/apotomo/javascript_generator.rb +19 -16
  12. data/lib/apotomo/rails/controller_methods.rb +84 -131
  13. data/lib/apotomo/rails/view_helper.rb +15 -31
  14. data/lib/apotomo/railtie.rb +24 -0
  15. data/lib/apotomo/request_processor.rb +17 -48
  16. data/lib/apotomo/test_case.rb +5 -5
  17. data/lib/apotomo/tree_node.rb +52 -61
  18. data/lib/apotomo/version.rb +1 -1
  19. data/lib/apotomo/widget.rb +70 -146
  20. data/lib/apotomo/widget/javascript_methods.rb +39 -0
  21. data/lib/apotomo/widget_shortcuts.rb +14 -40
  22. data/lib/generators/apotomo/widget_generator.rb +8 -9
  23. data/lib/generators/erb/widget_generator.rb +17 -0
  24. data/lib/generators/haml/widget_generator.rb +20 -0
  25. data/lib/generators/{apotomo/templates → templates}/view.erb +1 -1
  26. data/lib/generators/templates/view.haml +4 -0
  27. data/lib/generators/{apotomo/templates → templates}/widget.rb +1 -1
  28. data/lib/generators/{apotomo/templates → templates}/widget_test.rb +1 -1
  29. data/lib/generators/test_unit/widget_generator.rb +14 -0
  30. data/test/rails/caching_test.rb +10 -17
  31. data/test/rails/controller_methods_test.rb +9 -81
  32. data/test/rails/rails_integration_test.rb +76 -60
  33. data/test/rails/view_helper_test.rb +17 -28
  34. data/test/rails/widget_generator_test.rb +19 -31
  35. data/test/support/test_case_methods.rb +6 -20
  36. data/test/test_helper.rb +15 -25
  37. data/test/unit/event_handler_test.rb +1 -0
  38. data/test/unit/event_methods_test.rb +20 -8
  39. data/test/unit/event_test.rb +5 -0
  40. data/test/unit/javascript_generator_test.rb +19 -19
  41. data/test/unit/render_test.rb +17 -112
  42. data/test/unit/request_processor_test.rb +73 -111
  43. data/test/unit/test_case_test.rb +13 -7
  44. data/test/unit/widget_shortcuts_test.rb +24 -53
  45. data/test/unit/widget_test.rb +76 -36
  46. data/test/widgets/mouse/eat.erb +1 -0
  47. data/test/{fixtures → widgets}/mouse/eating.html.erb +0 -0
  48. data/test/{fixtures → widgets}/mouse/educate.html.erb +0 -0
  49. data/test/{fixtures → widgets}/mouse/feed.html.erb +0 -0
  50. data/test/{fixtures → widgets}/mouse/make_me_squeak.html.erb +0 -0
  51. data/test/{fixtures → widgets}/mouse/posing.html.erb +0 -0
  52. data/test/widgets/mouse/snuggle.html.erb +1 -0
  53. metadata +32 -50
  54. data/lib/apotomo/container_widget.rb +0 -10
  55. data/lib/apotomo/persistence.rb +0 -112
  56. data/lib/apotomo/rails/view_methods.rb +0 -7
  57. data/lib/apotomo/stateful_widget.rb +0 -29
  58. data/lib/apotomo/transition.rb +0 -46
  59. data/lib/generators/apotomo/templates/view.haml +0 -4
  60. data/test/dummy/log/production.log +0 -0
  61. data/test/dummy/log/server.log +0 -0
  62. data/test/dummy/public/javascripts/application.js +0 -2
  63. data/test/dummy/public/javascripts/controls.js +0 -965
  64. data/test/dummy/public/javascripts/dragdrop.js +0 -974
  65. data/test/dummy/public/javascripts/effects.js +0 -1123
  66. data/test/dummy/public/javascripts/prototype.js +0 -6001
  67. data/test/dummy/public/javascripts/rails.js +0 -175
  68. data/test/dummy/script/rails +0 -6
  69. data/test/dummy/tmp/app/cells/mouse_widget.rb +0 -11
  70. data/test/dummy/tmp/app/cells/mouse_widget/snuggle.html.erb +0 -7
  71. data/test/dummy/tmp/app/cells/mouse_widget/squeak.html.erb +0 -7
  72. data/test/dummy/tmp/test/widgets/mouse_widget_test.rb +0 -12
  73. data/test/fixtures/application_widget_tree.rb +0 -2
  74. data/test/fixtures/mouse/snuggle.html.erb +0 -1
  75. data/test/rails/view_methods_test.rb +0 -38
  76. data/test/unit/container_test.rb +0 -21
  77. data/test/unit/invoke_test.rb +0 -126
  78. data/test/unit/persistence_test.rb +0 -201
  79. data/test/unit/stateful_widget_test.rb +0 -58
  80. data/test/unit/test_addressing.rb +0 -110
  81. data/test/unit/test_jump_to_state.rb +0 -89
  82. data/test/unit/test_tab_panel.rb +0 -71
  83. 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
- # needs :@controller
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
- attr_reader :session, :root
3
+ include Hooks
4
4
 
5
- def initialize(controller, session, options={}, has_widgets_blocks=[])
6
- @session = session
7
- @widgets_flushed = false
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
- if options[:flush_widgets].blank? and ::Apotomo::StatefulWidget.frozen_widget_in?(session)
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
- def flushed_root
27
- StatefulWidget.flush_storage(session)
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
- ### FIXME: remove me!
43
- def render_page_updates(page_updates)
44
- page_updates.join("\n")
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+.
@@ -42,7 +42,7 @@ module Apotomo
42
42
  def setup
43
43
  super
44
44
  @controller.instance_eval do
45
- def controller_name
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 ||= widget("apotomo/widget", "root").tap do |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(name, options={})
67
- @last_invoke = root.find_widget(name).tap { |w| w.opts = options }.invoke # DISCUSS: use ControllerMethods?
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.instance_variable_set :@params, options # TODO: this is just a try-out (what about children?).
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
@@ -4,27 +4,24 @@
4
4
  module TreeNode
5
5
  include Enumerable
6
6
 
7
- attr_reader :content, :name, :parent
8
- attr_writer :content, :parent
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
- self.setAsRoot!
15
+ root!
16
16
 
17
- @childrenHash = Hash.new
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
- s = size()
24
- "Node ID: #{@name} Content: #{@content} Parent: " +
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
- add(child)
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
- raise "Child already added" if @childrenHash.has_key?(child.name)
40
+ raise "Child already added" if @childrenHash.has_key?(child.name)
44
41
 
45
- @childrenHash[child.name] = child
46
- @children << child
47
- child.parent = self
48
-
49
- child.run_widget_hook(:after_add, child, self)
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
- @childrenHash.delete(child.name)
59
- @children.delete(child)
60
- child.setAsRoot! unless child == nil
61
- return child
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 removeFromParent!
67
- @parent.remove!(self) unless root?
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
- for child in @children
73
- child.setAsRoot!
74
- end
75
- @childrenHash.clear
76
- @children.clear
77
- self
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
- setAsRoot!
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
- @parent == nil
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
- if block_given?
100
- @children.each {|child| yield child}
101
- else
102
- @children
103
- end
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 &block
109
- yield self
110
- children { |child| child.each(&block) }
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 [](key)
121
- raise "Key needs to be provided" if key == nil
122
-
123
- if key.kind_of?(Integer)
124
- @children[key]
125
- else
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
- @children.inject(1) {|sum, node| sum + node.size}
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
- puts((' ' * tab) + self.to_s)
139
- children {|child| child.printTree(tab + 4)}
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
- root = self
145
- root = root.parent while !root.root?
146
- root
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
- return +1 if other == nil
154
- self.name <=> other.name
144
+ return +1 if other == nil
145
+ self.name <=> other.name
155
146
  end
156
147
 
157
- protected :parent=, :setAsRoot!
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
- return next_node
159
+ next_node
169
160
  end
170
161
 
171
162
 
@@ -1,3 +1,3 @@
1
1
  module Apotomo
2
- VERSION = '1.0.5'
2
+ VERSION = '1.1.0.rc1'
3
3
  end
@@ -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 @opts[:cheese_id]
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
- attr_accessor :opts
32
- attr_writer :visible
33
-
61
+ attr_writer :visible
62
+
34
63
  include TreeNode
35
64
 
36
65
  include Onfire
37
- include EventMethods
38
66
 
39
- include Transition
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
- # Constructor which needs a unique id for the widget and one or multiple start states.
59
- def initialize(parent_controller, id, start_state, opts={})
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
- # Returns the rendered content for the widget by running the state method for <tt>state</tt>.
82
- # This might lead us to some other state since the state method could call #jump_to_state.
83
- def invoke(state=nil, event=nil)
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 MouseCell < Apotomo::StatefulWidget
116
- # def eating
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>eating.html</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(options={}, &block)
135
- if options[:nothing]
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
- @params[name]
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
- alias_method :widget_id, :name
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
+