apotomo 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +10 -0
- data/Gemfile.lock +47 -0
- data/README +141 -0
- data/README.rdoc +141 -0
- data/Rakefile +78 -0
- data/TODO +36 -0
- data/app/cells/apotomo/child_switch_widget/switch.html.erb +1 -0
- data/app/cells/apotomo/child_switch_widget/switch.rhtml +1 -0
- data/app/cells/apotomo/deep_link_widget.rb +27 -0
- data/app/cells/apotomo/deep_link_widget/setup.html.erb +20 -0
- data/app/cells/apotomo/java_script_widget.rb +12 -0
- data/app/cells/apotomo/tab_panel_widget.rb +87 -0
- data/app/cells/apotomo/tab_panel_widget/display.html.erb +57 -0
- data/app/cells/apotomo/tab_widget.rb +18 -0
- data/app/cells/apotomo/tab_widget/display.html.erb +1 -0
- data/config/routes.rb +3 -0
- data/generators/widget/USAGE +15 -0
- data/generators/widget/templates/functional_test.rb +8 -0
- data/generators/widget/templates/view.html.erb +2 -0
- data/generators/widget/templates/view.html.haml +3 -0
- data/generators/widget/templates/widget.rb +8 -0
- data/generators/widget/widget_generator.rb +34 -0
- data/lib/apotomo.rb +59 -0
- data/lib/apotomo/caching.rb +37 -0
- data/lib/apotomo/container_widget.rb +10 -0
- data/lib/apotomo/deep_link_methods.rb +90 -0
- data/lib/apotomo/event.rb +9 -0
- data/lib/apotomo/event_handler.rb +23 -0
- data/lib/apotomo/event_methods.rb +102 -0
- data/lib/apotomo/invoke_event_handler.rb +24 -0
- data/lib/apotomo/javascript_generator.rb +57 -0
- data/lib/apotomo/persistence.rb +139 -0
- data/lib/apotomo/proc_event_handler.rb +18 -0
- data/lib/apotomo/rails/controller_methods.rb +161 -0
- data/lib/apotomo/rails/view_helper.rb +95 -0
- data/lib/apotomo/rails/view_methods.rb +7 -0
- data/lib/apotomo/request_processor.rb +92 -0
- data/lib/apotomo/stateful_widget.rb +8 -0
- data/lib/apotomo/transition.rb +46 -0
- data/lib/apotomo/tree_node.rb +186 -0
- data/lib/apotomo/version.rb +5 -0
- data/lib/apotomo/widget.rb +289 -0
- data/lib/apotomo/widget_shortcuts.rb +36 -0
- data/rails/init.rb +0 -0
- data/test/fixtures/application_widget_tree.rb +2 -0
- data/test/rails/controller_methods_test.rb +206 -0
- data/test/rails/rails_integration_test.rb +99 -0
- data/test/rails/view_helper_test.rb +77 -0
- data/test/rails/view_methods_test.rb +40 -0
- data/test/rails/widget_generator_test.rb +47 -0
- data/test/support/assertions_helper.rb +13 -0
- data/test/support/test_case_methods.rb +68 -0
- data/test/test_helper.rb +77 -0
- data/test/unit/apotomo_test.rb +20 -0
- data/test/unit/container_test.rb +20 -0
- data/test/unit/event_handler_test.rb +67 -0
- data/test/unit/event_methods_test.rb +83 -0
- data/test/unit/event_test.rb +30 -0
- data/test/unit/invoke_test.rb +123 -0
- data/test/unit/javascript_generator_test.rb +90 -0
- data/test/unit/onfire_integration_test.rb +19 -0
- data/test/unit/persistence_test.rb +240 -0
- data/test/unit/render_test.rb +203 -0
- data/test/unit/request_processor_test.rb +178 -0
- data/test/unit/stateful_widget_test.rb +135 -0
- data/test/unit/test_addressing.rb +111 -0
- data/test/unit/test_caching.rb +54 -0
- data/test/unit/test_jump_to_state.rb +89 -0
- data/test/unit/test_tab_panel.rb +72 -0
- data/test/unit/test_widget_shortcuts.rb +45 -0
- data/test/unit/transition_test.rb +33 -0
- data/test/unit/widget_shortcuts_test.rb +68 -0
- data/test/unit/widget_test.rb +24 -0
- metadata +215 -0
@@ -0,0 +1,9 @@
|
|
1
|
+
module Apotomo
|
2
|
+
# Events are created by Apotomo in #fire. They bubble up from their source to root and trigger
|
3
|
+
# event handlers.
|
4
|
+
class Event < Onfire::Event
|
5
|
+
def _dump(depth)
|
6
|
+
raise "You're trying to serialize an instance of Apotomo::Event. Don't do that."
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Apotomo
|
2
|
+
# EventHandlers are "callbacks", not knowing why they exist, but what to do.
|
3
|
+
class EventHandler
|
4
|
+
|
5
|
+
def process_event(event)
|
6
|
+
# do something, and return content.
|
7
|
+
nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
self.to_s == other.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
# Invoked by Onfire.
|
15
|
+
def call(event)
|
16
|
+
event.source.root.page_updates << process_event(event)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'apotomo/invoke_event_handler'
|
23
|
+
require 'apotomo/proc_event_handler'
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'apotomo/event_handler'
|
2
|
+
|
3
|
+
module Apotomo
|
4
|
+
# Introduces event-processing functions into the StatefulWidget.
|
5
|
+
|
6
|
+
module EventMethods
|
7
|
+
attr_writer :page_updates
|
8
|
+
# Replacement for the EventProcessor singleton queue.
|
9
|
+
def page_updates
|
10
|
+
@page_updates ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.extend(ClassMethods)
|
15
|
+
base.initialize_hooks << :add_class_event_handlers
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_class_event_handlers(*)
|
19
|
+
self.class.responds_to_event_options.each { |options| respond_to_event(*options) }
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
def responds_to_event(*options)
|
24
|
+
responds_to_event_options << options
|
25
|
+
end
|
26
|
+
alias_method :respond_to_event, :responds_to_event
|
27
|
+
|
28
|
+
def responds_to_event_options
|
29
|
+
@responds_to_event_options ||= []
|
30
|
+
end
|
31
|
+
end
|
32
|
+
# Instructs the widget to look out for <tt>type</tt> Events that are passing by while bubbling.
|
33
|
+
# If an appropriate event is encountered the widget will send the targeted widget (or itself) to another
|
34
|
+
# state, which implies an update of the invoked widget.
|
35
|
+
#
|
36
|
+
# You may configure the event handler with the following <tt>options</tt>:
|
37
|
+
# :with => (required) the state to invoke on the target widget
|
38
|
+
# :on => (optional) the targeted widget's id, defaults to <tt>self.name</tt>
|
39
|
+
# :from => (optional) the source id of the widget that triggered the event, defaults to any widget
|
40
|
+
#
|
41
|
+
# Example:
|
42
|
+
#
|
43
|
+
# trap = cell(:input_field, :smell_like_cheese, 'mouse_trap')
|
44
|
+
# trap.respond_to_event :mouseOver, :with => :catch_mouse
|
45
|
+
#
|
46
|
+
# This would instruct <tt>trap</tt> to catch a <tt>:mouseOver</tt> event from any widget (including itself) and
|
47
|
+
# to invoke the state <tt>:catch_mouse</tt> on itself as trigger.
|
48
|
+
#
|
49
|
+
#
|
50
|
+
# hunter = cell(:form, :hunt_for_mice, 'my_form')
|
51
|
+
# hunter << cell(:input_field, :smell_like_cheese, 'mouse_trap')
|
52
|
+
# hunter << cell(:text_area, :stick_like_honey, 'bear_trap')
|
53
|
+
# hunter.respond_to_event :captured, :from => 'mouse_trap', :with => :refill_cheese, :on => 'mouse_trap'
|
54
|
+
#
|
55
|
+
# As both the bear- and the mouse trap can trigger a <tt>:captured</tt> event the later <tt>respond_to_event</tt>
|
56
|
+
# would invoke <tt>:refill_cheese</tt> on the <tt>mouse_trap</tt> widget as soon as this and only this widget fired.
|
57
|
+
# It is important to understand the <tt>:from</tt> parameter as it filters the event source - it wouldn't make
|
58
|
+
# sense to refill the mouse trap if the bear trap snapped, would it?
|
59
|
+
|
60
|
+
def respond_to_event(type, options)
|
61
|
+
options[:once] = true if options[:once].nil?
|
62
|
+
|
63
|
+
handler_opts = {}
|
64
|
+
handler_opts[:widget_id] = options[:on] || self.name
|
65
|
+
handler_opts[:state] = options[:with]
|
66
|
+
|
67
|
+
handler = InvokeEventHandler.new(handler_opts)
|
68
|
+
|
69
|
+
return if options[:once] and event_table.all_handlers_for(type, options[:from]).include?(handler)
|
70
|
+
|
71
|
+
on(type, :do => handler, :from => options[:from])
|
72
|
+
end
|
73
|
+
|
74
|
+
def trigger(*args)
|
75
|
+
fire(*args)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Invokes <tt>state</tt> on the widget <em>and</end> updates itself on the page. This should
|
79
|
+
# never be called from outside but in setters when some internal value changed and must be
|
80
|
+
# displayed instantly.
|
81
|
+
#
|
82
|
+
# Implements the following pattern (TODO: remove example as soon as invoke! proofed):
|
83
|
+
#
|
84
|
+
# def title=(str)
|
85
|
+
# @title = str
|
86
|
+
# peek(:update, self.name, :display, self.name)
|
87
|
+
# trigger(:update)
|
88
|
+
# end
|
89
|
+
def invoke!(state)
|
90
|
+
### TODO: encapsulate in PageUpdateQueue:
|
91
|
+
Apotomo::EventProcessor.instance.processed_handlers << [name, invoke(:state)]
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
protected
|
97
|
+
# Get all handlers from self for the passed event (overriding Onfire#local_event_handlers).
|
98
|
+
def local_event_handlers(event)
|
99
|
+
event_table.all_handlers_for(event.type, event.source.name) # we key with widget_id.
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Apotomo
|
2
|
+
class InvokeEventHandler < EventHandler
|
3
|
+
attr_accessor :widget_id, :state
|
4
|
+
|
5
|
+
def initialize(opts={})
|
6
|
+
@widget_id = opts.delete(:widget_id)
|
7
|
+
@state = opts.delete(:state)
|
8
|
+
end
|
9
|
+
|
10
|
+
def process_event(event)
|
11
|
+
target = event.source.root.find_by_path(widget_id) ### DISCUSS: widget_id or widget_selector?
|
12
|
+
|
13
|
+
#::Rails.logger.debug "EventHandler: invoking #{target.name}##{state}"
|
14
|
+
### DISCUSS: let target access event?
|
15
|
+
### pass additional opts to #invoke?
|
16
|
+
### DISCUSS: pass block here?
|
17
|
+
target.opts[:event] = event
|
18
|
+
|
19
|
+
target.invoke(state)
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s; "InvokeEventHandler:#{widget_id}##{state}"; end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Apotomo
|
2
|
+
class JavascriptGenerator
|
3
|
+
def initialize(framework)
|
4
|
+
raise "No JS framework specified" if framework.blank?
|
5
|
+
extend "apotomo/javascript_generator/#{framework}".camelize.constantize
|
6
|
+
end
|
7
|
+
|
8
|
+
def <<(javascript)
|
9
|
+
"#{javascript}"
|
10
|
+
end
|
11
|
+
|
12
|
+
# Copied from ActionView::Helpers::JavascriptHelper.
|
13
|
+
JS_ESCAPE_MAP = {
|
14
|
+
'\\' => '\\\\',
|
15
|
+
'</' => '<\/',
|
16
|
+
"\r\n" => '\n',
|
17
|
+
"\n" => '\n',
|
18
|
+
"\r" => '\n',
|
19
|
+
'"' => '\\"',
|
20
|
+
"'" => "\\'" }
|
21
|
+
|
22
|
+
# Escape carrier returns and single and double quotes for JavaScript segments.
|
23
|
+
def self.escape(javascript)
|
24
|
+
return javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] } if javascript
|
25
|
+
|
26
|
+
''
|
27
|
+
end
|
28
|
+
|
29
|
+
def escape(javascript)
|
30
|
+
self.class.escape(javascript)
|
31
|
+
end
|
32
|
+
|
33
|
+
module Prototype
|
34
|
+
def prototype; end
|
35
|
+
def element(id); "$(\"#{id}\")"; end
|
36
|
+
def xhr(url); "new Ajax.Request(\"#{url}\")"; end
|
37
|
+
def update(id, markup); element(id) + '.update("'+escape(markup)+'")'; end
|
38
|
+
def replace(id, markup); element(id) + '.replace("'+escape(markup)+'")'; end
|
39
|
+
end
|
40
|
+
|
41
|
+
module Right
|
42
|
+
def right; end
|
43
|
+
def element(id); "$(\"#{id}\")"; end
|
44
|
+
def xhr(url); "new Xhr(\"#{url}\", {evalScripts:true}).send()"; end
|
45
|
+
def update(id, markup); element(id) + '.update("'+escape(markup)+'")'; end
|
46
|
+
def replace(id, markup); element(id) + '.replace("'+escape(markup)+'")'; end
|
47
|
+
end
|
48
|
+
|
49
|
+
module Jquery
|
50
|
+
def jquery; end
|
51
|
+
def element(id); "$(\"##{id}\")"; end
|
52
|
+
def xhr(url); "$.ajax({url: \"#{url}\"})"; end
|
53
|
+
def update(id, markup); element(id) + '.html("'+escape(markup)+'")'; end
|
54
|
+
def replace(id, markup); element(id) + '.replaceWith("'+escape(markup)+'")'; end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module Apotomo
|
2
|
+
# Methods needed to serialize the widget tree and back.
|
3
|
+
module Persistence
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
# For Ruby 1.8/1.9 compatibility.
|
9
|
+
def symbolized_instance_variables
|
10
|
+
instance_variables.map { |ivar| ivar.to_sym }
|
11
|
+
end
|
12
|
+
|
13
|
+
def freeze_ivars_to(storage)
|
14
|
+
frozen = {}
|
15
|
+
(symbolized_instance_variables - unfreezable_ivars).each do |ivar|
|
16
|
+
frozen[ivar] = instance_variable_get(ivar)
|
17
|
+
end
|
18
|
+
storage[path] = frozen
|
19
|
+
end
|
20
|
+
|
21
|
+
### FIXME: rewrite so that root might be stateless as well.
|
22
|
+
def freeze_data_to(storage)
|
23
|
+
freeze_ivars_to(storage)# if self.kind_of?(StatefulWidget)
|
24
|
+
children.each { |child| child.freeze_data_to(storage) if child.kind_of?(StatefulWidget) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def freeze_to(storage)
|
28
|
+
storage[:apotomo_root] = self # save structure.
|
29
|
+
storage[:apotomo_widget_ivars] = {}
|
30
|
+
freeze_data_to(storage[:apotomo_widget_ivars]) # save ivars.
|
31
|
+
end
|
32
|
+
|
33
|
+
def thaw_ivars_from(storage)
|
34
|
+
storage.fetch(path, {}).each do |k, v|
|
35
|
+
instance_variable_set(k, v)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def thaw_data_from(storage)
|
40
|
+
thaw_ivars_from(storage)
|
41
|
+
children.each { |child| child.thaw_data_from(storage) }
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
# Serializes the widget node structure (not children, not content).
|
46
|
+
def dump_node
|
47
|
+
field_sep = self.class.field_sep
|
48
|
+
"#{@name}#{field_sep}#{self.class}#{field_sep}#{root? ? @name : parent.name}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Serializes the tree structure.
|
52
|
+
def _dump(depth)
|
53
|
+
inject("") { |str, node| str << node.dump_node << self.class.node_sep }
|
54
|
+
end
|
55
|
+
|
56
|
+
module ClassMethods
|
57
|
+
def field_sep; '|'; end
|
58
|
+
def node_sep; "\n"; end
|
59
|
+
|
60
|
+
# Creates an empty widget instance from <tt>line</tt>.
|
61
|
+
def load_node(line)
|
62
|
+
name, klass, parent = line.split(field_sep)
|
63
|
+
[klass.constantize.new(name, nil), parent]
|
64
|
+
end
|
65
|
+
|
66
|
+
def _load(str)
|
67
|
+
nodes = {}
|
68
|
+
root = nil
|
69
|
+
str.split(node_sep).each do |line|
|
70
|
+
node, parent = load_node(line)
|
71
|
+
nodes[node.name] = node
|
72
|
+
|
73
|
+
if node.name == parent # we're at the root node.
|
74
|
+
root = node and next
|
75
|
+
end
|
76
|
+
|
77
|
+
nodes[parent].add(node)
|
78
|
+
end
|
79
|
+
root
|
80
|
+
end
|
81
|
+
|
82
|
+
def freeze_for(storage, root)
|
83
|
+
storage[:apotomo_stateful_branches] = []
|
84
|
+
storage[:apotomo_widget_ivars] = {}
|
85
|
+
|
86
|
+
stateful_branches_for(root).each do |branch|
|
87
|
+
branch.freeze_data_to(storage[:apotomo_widget_ivars]) # save ivars.
|
88
|
+
storage[:apotomo_stateful_branches] << [branch, branch.parent.name]
|
89
|
+
branch.root! # disconnect from tree.
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def thaw_for(storage, root)
|
94
|
+
branches = storage.delete(:apotomo_stateful_branches) || []
|
95
|
+
branches.each do |config|
|
96
|
+
branch = config.first
|
97
|
+
parent = root.find_widget(config.last) or raise "Couldn't find parent `#{config.last}` for `#{branch.name}`"
|
98
|
+
|
99
|
+
parent << branch
|
100
|
+
branch.thaw_data_from(storage.delete(:apotomo_widget_ivars) || {})
|
101
|
+
end
|
102
|
+
|
103
|
+
root
|
104
|
+
end
|
105
|
+
|
106
|
+
def thaw_from(storage)
|
107
|
+
root = storage[:apotomo_root]
|
108
|
+
root.thaw_data_from(storage.fetch(:apotomo_widget_ivars, {}))
|
109
|
+
root
|
110
|
+
end
|
111
|
+
|
112
|
+
def frozen_widget_in?(storage)
|
113
|
+
branches = storage[:apotomo_stateful_branches]
|
114
|
+
branches.present? and branches.first.first.kind_of? Apotomo::StatefulWidget
|
115
|
+
end
|
116
|
+
|
117
|
+
def flush_storage(storage)
|
118
|
+
storage[:apotomo_stateful_branches] = nil
|
119
|
+
storage[:apotomo_widget_ivars] = nil
|
120
|
+
end
|
121
|
+
|
122
|
+
# Find the first stateful widgets on each branch from +root+.
|
123
|
+
def stateful_branches_for(root)
|
124
|
+
to_traverse = [root]
|
125
|
+
stateful_roots = []
|
126
|
+
|
127
|
+
while node = to_traverse.shift
|
128
|
+
if node.kind_of?(StatefulWidget)
|
129
|
+
stateful_roots << node and next
|
130
|
+
end
|
131
|
+
to_traverse += node.children
|
132
|
+
end
|
133
|
+
|
134
|
+
stateful_roots
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Apotomo
|
2
|
+
class ProcEventHandler < EventHandler
|
3
|
+
attr_accessor :proc
|
4
|
+
|
5
|
+
def initialize(opts={})
|
6
|
+
@proc = opts.delete(:proc)
|
7
|
+
end
|
8
|
+
|
9
|
+
def process_event(event)
|
10
|
+
Rails.logger.debug "ProcEventHandler: calling #{@proc}"
|
11
|
+
#@proc.call(event)
|
12
|
+
event.source.controller.send(@proc, event)
|
13
|
+
nil ### DISCUSS: needed so that controller doesn't evaluate the "content".
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s; "ProcEventHandler:#{proc}"; end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'apotomo/request_processor'
|
2
|
+
require 'apotomo/rails/view_methods'
|
3
|
+
|
4
|
+
module Apotomo
|
5
|
+
module Rails
|
6
|
+
module ControllerMethods
|
7
|
+
include WidgetShortcuts
|
8
|
+
|
9
|
+
def self.included(base) #:nodoc:
|
10
|
+
base.class_eval do
|
11
|
+
extend WidgetShortcuts
|
12
|
+
extend ClassMethods
|
13
|
+
|
14
|
+
class_inheritable_array :uses_widgets_blocks
|
15
|
+
self.uses_widgets_blocks = []
|
16
|
+
|
17
|
+
helper ::Apotomo::Rails::ViewMethods
|
18
|
+
|
19
|
+
after_filter :apotomo_freeze
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def uses_widgets(&block)
|
25
|
+
uses_widgets_blocks << block
|
26
|
+
end
|
27
|
+
|
28
|
+
alias_method :has_widgets, :uses_widgets
|
29
|
+
end
|
30
|
+
|
31
|
+
def bound_use_widgets_blocks
|
32
|
+
session[:bound_use_widgets_blocks] ||= ProcHash.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def flush_bound_use_widgets_blocks
|
36
|
+
session[:bound_use_widgets_blocks] = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def apotomo_request_processor
|
40
|
+
return @apotomo_request_processor if @apotomo_request_processor
|
41
|
+
|
42
|
+
# happens once per request:
|
43
|
+
### DISCUSS: policy in production?
|
44
|
+
options = { :flush_widgets => params[:flush_widgets],
|
45
|
+
:js_framework => Apotomo.js_framework || :prototype,
|
46
|
+
} ### TODO: process rails options (flush_tree, version)
|
47
|
+
|
48
|
+
@apotomo_request_processor = Apotomo::RequestProcessor.new(session, options, self.class.uses_widgets_blocks)
|
49
|
+
|
50
|
+
flush_bound_use_widgets_blocks if @apotomo_request_processor.widgets_flushed?
|
51
|
+
|
52
|
+
|
53
|
+
@apotomo_request_processor
|
54
|
+
end
|
55
|
+
|
56
|
+
def apotomo_root
|
57
|
+
apotomo_request_processor.root
|
58
|
+
end
|
59
|
+
|
60
|
+
# Yields the root widget for manipulating the widget tree in a controller action.
|
61
|
+
# Note that the passed block is executed once per session and not in every request.
|
62
|
+
#
|
63
|
+
# Example:
|
64
|
+
# def login
|
65
|
+
# use_widgets do |root|
|
66
|
+
# root << cell(:login, :form, 'login_box')
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# @box = render_widget 'login_box'
|
70
|
+
# end
|
71
|
+
def use_widgets(&block)
|
72
|
+
root = apotomo_root ### DISCUSS: let RequestProcessor initialize so we get flushed, eventually. maybe add a :before filter for that? or move #use_widgets to RequestProcessor?
|
73
|
+
|
74
|
+
return if bound_use_widgets_blocks.include?(block)
|
75
|
+
|
76
|
+
yield root
|
77
|
+
|
78
|
+
bound_use_widgets_blocks << block # remember the proc.
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
def render_widget(widget, options={}, &block)
|
83
|
+
apotomo_request_processor.render_widget_for(widget, options, self, &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
def apotomo_freeze
|
87
|
+
apotomo_request_processor.freeze!
|
88
|
+
end
|
89
|
+
|
90
|
+
def render_event_response
|
91
|
+
page_updates = apotomo_request_processor.process_for({:type => params[:type], :source => params[:source]}, self)
|
92
|
+
|
93
|
+
return render_iframe_updates(page_updates) if params[:apotomo_iframe]
|
94
|
+
|
95
|
+
render :text => apotomo_request_processor.render_page_updates(page_updates), :content_type => Mime::JS
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the url to trigger a +type+ event from +:source+, which is a non-optional parameter.
|
99
|
+
# Additional +options+ will be appended to the query string.
|
100
|
+
#
|
101
|
+
# Note that this method will use the framework's internal routing if available (e.g. #url_for in Rails).
|
102
|
+
#
|
103
|
+
# Example:
|
104
|
+
# url_for_event(:paginate, :source => 'mouse', :page => 2)
|
105
|
+
# #=> http://apotomo.de/mouse/process_event_request?type=paginate&source=mouse&page=2
|
106
|
+
def url_for_event(type, options)
|
107
|
+
options.reverse_merge!(:type => type)
|
108
|
+
|
109
|
+
apotomo_event_path(apotomo_request_processor.address_for(options))
|
110
|
+
end
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
|
115
|
+
# Renders the page updates through an iframe. Copied from responds_to_parent,
|
116
|
+
# see http://github.com/markcatley/responds_to_parent .
|
117
|
+
def render_iframe_updates(page_updates)
|
118
|
+
script = apotomo_request_processor.render_page_updates(page_updates)
|
119
|
+
escaped_script = Apotomo::JavascriptGenerator.escape(script)
|
120
|
+
|
121
|
+
render :text => "<html><body><script type='text/javascript' charset='utf-8'>
|
122
|
+
var loc = document.location;
|
123
|
+
with(window.parent) { setTimeout(function() { window.eval('#{escaped_script}'); window.loc && loc.replace('about:blank'); }, 1) }
|
124
|
+
</script></body></html>", :content_type => 'text/html'
|
125
|
+
end
|
126
|
+
|
127
|
+
def respond_to_event(type, options)
|
128
|
+
handler = ProcEventHandler.new
|
129
|
+
handler.proc = options[:with]
|
130
|
+
### TODO: pass :from => (event source).
|
131
|
+
|
132
|
+
# attach once, not every request:
|
133
|
+
apotomo_root.evt_table.add_handler_once(handler, :event_type => type)
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
|
138
|
+
### DISCUSS: rename? should say "this controller action wants apotomo's deep linking!"
|
139
|
+
### DISCUSS: move to deep_link_methods?
|
140
|
+
def respond_to_url_change
|
141
|
+
return if apotomo_root.find_widget('deep_link') # add only once.
|
142
|
+
apotomo_root << widget("apotomo/deep_link_widget", :setup, 'deep_link')
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
class ProcHash < Array
|
147
|
+
def id_for_proc(proc)
|
148
|
+
proc.to_s.split('@').last
|
149
|
+
end
|
150
|
+
|
151
|
+
def <<(proc)
|
152
|
+
super(id_for_proc(proc))
|
153
|
+
end
|
154
|
+
|
155
|
+
def include?(proc)
|
156
|
+
super(id_for_proc(proc))
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|