mullen-wee 2.2.0

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.
Files changed (73) hide show
  1. data/README.rdoc +127 -0
  2. data/Rakefile +25 -0
  3. data/aWee.gemspec +26 -0
  4. data/examples/ObjectSpaceBrowser.rb +191 -0
  5. data/examples/ajax.rb +73 -0
  6. data/examples/apotomo-webhunter/main.rb +75 -0
  7. data/examples/apotomo-webhunter/public/images/bear_trap_charged.png +0 -0
  8. data/examples/apotomo-webhunter/public/images/bear_trap_snapped.png +0 -0
  9. data/examples/apotomo-webhunter/public/images/cheese.png +0 -0
  10. data/examples/apotomo-webhunter/public/images/dark_forest.jpg +0 -0
  11. data/examples/apotomo-webhunter/public/images/mouse.png +0 -0
  12. data/examples/apotomo-webhunter/public/javascripts/jquery-1.3.2.min.js +19 -0
  13. data/examples/apotomo-webhunter/public/javascripts/wee-jquery.js +19 -0
  14. data/examples/apotomo-webhunter/public/stylesheets/forest.css +33 -0
  15. data/examples/arc_challenge.rb +42 -0
  16. data/examples/arc_challenge2.rb +46 -0
  17. data/examples/cheese_task.rb +27 -0
  18. data/examples/continuations.rb +28 -0
  19. data/examples/demo.rb +135 -0
  20. data/examples/demo/calculator.rb +63 -0
  21. data/examples/demo/calendar.rb +333 -0
  22. data/examples/demo/counter.rb +38 -0
  23. data/examples/demo/editable_counter.rb +36 -0
  24. data/examples/demo/example.rb +142 -0
  25. data/examples/demo/file_upload.rb +19 -0
  26. data/examples/demo/messagebox.rb +15 -0
  27. data/examples/demo/radio.rb +33 -0
  28. data/examples/demo/window.rb +71 -0
  29. data/examples/hw.rb +11 -0
  30. data/examples/i18n/app.rb +16 -0
  31. data/examples/i18n/locale/de/app.po +25 -0
  32. data/examples/i18n/locale/en/app.po +25 -0
  33. data/examples/pager.rb +102 -0
  34. data/lib/wee.rb +109 -0
  35. data/lib/wee/application.rb +89 -0
  36. data/lib/wee/callback.rb +109 -0
  37. data/lib/wee/component.rb +363 -0
  38. data/lib/wee/decoration.rb +251 -0
  39. data/lib/wee/dialog.rb +171 -0
  40. data/lib/wee/external_resource.rb +39 -0
  41. data/lib/wee/html_brushes.rb +795 -0
  42. data/lib/wee/html_canvas.rb +254 -0
  43. data/lib/wee/html_document.rb +52 -0
  44. data/lib/wee/html_writer.rb +71 -0
  45. data/lib/wee/id_generator.rb +81 -0
  46. data/lib/wee/jquery.rb +11 -0
  47. data/lib/wee/jquery/jquery-1.3.2.min.js +19 -0
  48. data/lib/wee/jquery/wee-jquery.js +19 -0
  49. data/lib/wee/locale.rb +56 -0
  50. data/lib/wee/lru_cache.rb +91 -0
  51. data/lib/wee/presenter.rb +44 -0
  52. data/lib/wee/renderer.rb +72 -0
  53. data/lib/wee/request.rb +56 -0
  54. data/lib/wee/response.rb +68 -0
  55. data/lib/wee/rightjs.rb +11 -0
  56. data/lib/wee/rightjs/rightjs-1.5.2.min.js +9 -0
  57. data/lib/wee/rightjs/wee-rightjs.js +18 -0
  58. data/lib/wee/root_component.rb +45 -0
  59. data/lib/wee/session.rb +366 -0
  60. data/lib/wee/state.rb +102 -0
  61. data/lib/wee/task.rb +16 -0
  62. data/test/bm_render.rb +34 -0
  63. data/test/component_spec.rb +40 -0
  64. data/test/stress/plotter.rb +84 -0
  65. data/test/stress/stress_client.rb +51 -0
  66. data/test/stress/stress_local.rb +86 -0
  67. data/test/stress/stress_server.rb +83 -0
  68. data/test/test_component.rb +106 -0
  69. data/test/test_html_canvas.rb +25 -0
  70. data/test/test_html_writer.rb +32 -0
  71. data/test/test_lru_cache.rb +51 -0
  72. data/test/test_request.rb +42 -0
  73. metadata +185 -0
@@ -0,0 +1,251 @@
1
+ require 'wee/presenter'
2
+
3
+ module Wee
4
+
5
+ #
6
+ # Abstract base class of all decorations. Forwards the methods
7
+ # #process_callbacks, #render! and #state to the next decoration in
8
+ # the chain. Subclasses should provide special behaviour in these methods,
9
+ # otherwise the decoration does not make sense.
10
+ #
11
+ # For example, a HeaderFooterDecoration class could draw a header and footer
12
+ # around the decorations or components below itself:
13
+ #
14
+ # class HeaderFooterDecoration < Wee::Decoration
15
+ # alias render! render_presenter!
16
+ # def render(r)
17
+ # r.text "header"
18
+ # r.render_decoration(@next)
19
+ # r.text "footer"
20
+ # end
21
+ # end
22
+ #
23
+ class Decoration < Presenter
24
+
25
+ #
26
+ # Points to the next decoration in the chain. A decoration is responsible for
27
+ # all decorations or components "below" it (everything that follows this
28
+ # decoration in the chain). In other words, it's the owner of everything
29
+ # "below" itself.
30
+ #
31
+ attr_accessor :next
32
+
33
+ #
34
+ # Is this decoration a global or a local one? By default all decorations are
35
+ # local unless this method is overwritten.
36
+ #
37
+ # A global decoration is added in front of the decoration chain, a local
38
+ # decoration is added in front of all other local decorations but after all
39
+ # global decorations.
40
+ #
41
+ def global?() false end
42
+
43
+ #
44
+ # Forwards method call to the next decoration in the chain.
45
+ #
46
+ def process_callbacks(callbacks)
47
+ @next.process_callbacks(callbacks)
48
+ end
49
+
50
+ alias render_presenter! render!
51
+ #
52
+ # Forwards method call to the next decoration in the chain.
53
+ #
54
+ def render!(r)
55
+ @next.render!(r)
56
+ end
57
+
58
+ #
59
+ # We have to save the @next attribute to be able to correctly backtrack
60
+ # calls, as method Wee::Component#call modifies it in the call to
61
+ # <tt>component.remove_decoration(answer)</tt>. Removing the
62
+ # answer-decoration has the advantage to be able to call a component more
63
+ # than once!
64
+ #
65
+ def state(s)
66
+ @next.state(s)
67
+ s.add_ivar(self, :@next, @next)
68
+ end
69
+
70
+ end # class Decoration
71
+
72
+ #
73
+ # A Wee::Delegate breaks the decoration chain and forwards the methods
74
+ # #process_callbacks, #render! and #state to the corresponding *chain*
75
+ # method of it's _delegate_ component (a Wee::Component).
76
+ #
77
+ class Delegate < Decoration
78
+
79
+ def initialize(delegate)
80
+ @delegate = delegate
81
+ end
82
+
83
+ #
84
+ # Forwards method to the corresponding top-level *chain* method of the
85
+ # _delegate_ component.
86
+ #
87
+ def process_callbacks(callbacks)
88
+ @delegate.decoration.process_callbacks(callbacks)
89
+ end
90
+
91
+ #
92
+ # Forwards method to the corresponding top-level *chain* method of the
93
+ # _delegate_ component.
94
+ #
95
+ def render!(r)
96
+ @delegate.decoration.render!(r)
97
+ end
98
+
99
+ #
100
+ # Forwards method to the corresponding top-level *chain* method of the
101
+ # _delegate_ component. We also take snapshots of all non-visible
102
+ # components, thus we follow the @next decoration (via super).
103
+ #
104
+ def state(s)
105
+ super
106
+ @delegate.decoration.state(s)
107
+ end
108
+
109
+ end # class Delegate
110
+
111
+ #
112
+ # A Wee::AnswerDecoration is wrapped around a component that will call
113
+ # Component#answer. This makes it possible to use such components without the
114
+ # need to call them (Component#call), e.g. as child components of other
115
+ # components.
116
+ #
117
+ class AnswerDecoration < Decoration
118
+
119
+ #
120
+ # Used to unwind the component call chain in Component#answer.
121
+ #
122
+ class Answer < Exception
123
+ attr_reader :args
124
+ def initialize(args) @args = args end
125
+ end
126
+
127
+ attr_accessor :answer_callback
128
+
129
+ class Interceptor
130
+ attr_accessor :action_callback, :answer_callback
131
+
132
+ def initialize(action_callback, answer_callback)
133
+ @action_callback, @answer_callback = action_callback, answer_callback
134
+ end
135
+
136
+ def call
137
+ @action_callback.call
138
+ rescue Answer => answer
139
+ # return to the calling component
140
+ @answer_callback.call(answer)
141
+ end
142
+ end
143
+
144
+ #
145
+ # When a component answers, <tt>@answer_callback.call(answer)</tt>
146
+ # will be executed, where +answer+ is of class Answer which includes the
147
+ # arguments passed to Component#answer.
148
+ #
149
+ def process_callbacks(callbacks)
150
+ if action_callback = super
151
+ Interceptor.new(action_callback, @answer_callback)
152
+ else
153
+ nil
154
+ end
155
+ end
156
+
157
+ end # class AnswerDecoration
158
+
159
+
160
+ class WrapperDecoration < Decoration
161
+
162
+ alias render! render_presenter!
163
+
164
+ #
165
+ # Overwrite this method, and call render_inner(r)
166
+ # where you want the inner content to be drawn.
167
+ #
168
+ def render(r)
169
+ render_inner(r)
170
+ end
171
+
172
+ def render_inner(r)
173
+ r.render_decoration(@next)
174
+ end
175
+
176
+ end # class WrapperDecoration
177
+
178
+
179
+ #
180
+ # Renders a <div> tag with a unique "id" around the wrapped component.
181
+ # Useful for components that want to update their content in-place using
182
+ # AJAX.
183
+ #
184
+ class OidDecoration < WrapperDecoration
185
+ def render(r)
186
+ r.div.oid.with { render_inner(r) }
187
+ end
188
+ end # class OidDecoration
189
+
190
+ #
191
+ # Renders a CSS style for a component class.
192
+ #
193
+ # Only works when used together with a PageDecoration,
194
+ # or an existing :styles divert location.
195
+ #
196
+ # The style is not rendered when in an AJAX request.
197
+ # This is the desired behaviour as it is assumed that
198
+ # a component is first rendered via a regular request
199
+ # and then updated via AJAX requests.
200
+ #
201
+ # It is only rendered once for all instances of a given
202
+ # component.
203
+ #
204
+ # A method #style must exist returning the CSS style.
205
+ #
206
+ class StyleDecoration < WrapperDecoration
207
+ def initialize(component)
208
+ @component = component
209
+ end
210
+
211
+ def render(r)
212
+ r.render_style(@component)
213
+ render_inner(r)
214
+ end
215
+ end # class StyleDecoration
216
+
217
+ class FormDecoration < WrapperDecoration
218
+
219
+ def global?() true end
220
+
221
+ def render(r)
222
+ r.form { render_inner(r) }
223
+ end
224
+
225
+ end # class FormDecoration
226
+
227
+ class PageDecoration < WrapperDecoration
228
+
229
+ def initialize(title='', stylesheets=[], javascripts=[])
230
+ @title = title
231
+ @stylesheets = stylesheets
232
+ @javascripts = javascripts
233
+ super()
234
+ end
235
+
236
+ def global?() true end
237
+
238
+ def render(r)
239
+ r.page.title(@title).head {
240
+ @stylesheets.each {|s| r.link_css(s) }
241
+ @javascripts.each {|j| r.javascript.src(j) }
242
+ r.style.type('text/css').with { r.define_divert(:styles) }
243
+ r.javascript.with { r.define_divert(:javascripts) }
244
+ }.with {
245
+ render_inner(r)
246
+ }
247
+ end
248
+
249
+ end # class PageDecoration
250
+
251
+ end # module Wee
@@ -0,0 +1,171 @@
1
+ require 'wee/component'
2
+
3
+ module Wee
4
+ class Dialog < Component; end
5
+
6
+ #
7
+ # Abstract class
8
+ #
9
+ class FormDialog < Dialog
10
+ def initialize(caption)
11
+ @caption = caption
12
+ end
13
+
14
+ def render(r)
15
+ r.div.css_class('wee').with {
16
+ render_caption(r)
17
+ render_form(r)
18
+ }
19
+ end
20
+
21
+ def render_caption(r)
22
+ r.h3 @caption if @caption
23
+ end
24
+
25
+ def render_form(r)
26
+ r.form.with {
27
+ render_body(r)
28
+ render_buttons(r)
29
+ }
30
+ end
31
+
32
+ def render_body(r)
33
+ end
34
+
35
+ def render_buttons(r)
36
+ return if buttons.empty?
37
+ r.div.css_class('dialog-buttons').with {
38
+ buttons.each do |title, return_value, sym, method|
39
+ sym ||= title.downcase
40
+ r.span.css_class("dialog-button-#{sym}").with {
41
+ if method
42
+ r.submit_button.callback_method(method).value(title)
43
+ else
44
+ r.submit_button.callback_method(:answer, return_value).value(title)
45
+ end
46
+ }
47
+ end
48
+ }
49
+ end
50
+
51
+ def buttons
52
+ []
53
+ end
54
+ end # class FormDialog
55
+
56
+ class MessageDialog < FormDialog
57
+ def initialize(caption, *buttons)
58
+ super(caption)
59
+ @buttons = buttons
60
+ end
61
+
62
+ def buttons
63
+ @buttons
64
+ end
65
+ end
66
+
67
+ class InformDialog < FormDialog
68
+ def buttons
69
+ [['Ok', nil, :ok]]
70
+ end
71
+ end # class InformDialog
72
+
73
+ class ConfirmDialog < FormDialog
74
+ def buttons
75
+ [['Yes', true, :yes], ['No', false, :no]]
76
+ end
77
+ end # class ConfirmDialog
78
+
79
+ class SingleSelectionDialog < FormDialog
80
+ attr_accessor :selected_item
81
+
82
+ def initialize(items, caption=nil, selected_item=nil)
83
+ super(caption)
84
+ @items = items
85
+ @selected_item = selected_item
86
+ end
87
+
88
+ def state(s) super
89
+ s.add_ivar(self, :@selected_item)
90
+ end
91
+
92
+ def render_body(r)
93
+ r.select_list(@items).selected(@selected_item).callback_method(:selected_item=)
94
+ end
95
+
96
+ def buttons
97
+ [['Ok', nil, :ok, :ok], ['Cancel', nil, :cancel, :cancel]]
98
+ end
99
+
100
+ def ok
101
+ answer @selected_item
102
+ end
103
+
104
+ def cancel
105
+ answer nil
106
+ end
107
+ end # class SingleSelectionDialog
108
+
109
+ class TextInputDialog < Wee::FormDialog
110
+ attr_accessor :text
111
+
112
+ def initialize(caption=nil, text="", size=50)
113
+ super(caption)
114
+ @text = text
115
+ @size = size
116
+ end
117
+
118
+ def state(s) super
119
+ s.add_ivar(self, :@text)
120
+ end
121
+
122
+ def render_body(r)
123
+ r.text_input.size(@size).callback_method(:set_text).value(@text || "")
124
+ end
125
+
126
+ def set_text(text)
127
+ @text = text.strip
128
+ end
129
+
130
+ def buttons
131
+ [['Ok', nil, :ok, :ok], ['Cancel', nil, :cancel, :cancel]]
132
+ end
133
+
134
+ def ok
135
+ answer @text
136
+ end
137
+
138
+ def cancel
139
+ answer nil
140
+ end
141
+ end # class TextInputDialog
142
+
143
+ class TextAreaDialog < TextInputDialog
144
+ def initialize(caption=nil, text="", cols=50, rows=5)
145
+ super(caption, text, cols)
146
+ @rows = rows
147
+ end
148
+
149
+ def render_body(r)
150
+ r.text_area.cols(@size).rows(@rows).callback_method(:set_text).with(@text || "")
151
+ end
152
+ end # class TextAreaDialog
153
+
154
+ #
155
+ # Extend class Component with shortcuts for the dialogs above
156
+ #
157
+ class Component
158
+ def confirm(question, &block)
159
+ call! ConfirmDialog.new(question), &block
160
+ end
161
+
162
+ def inform(message, &block)
163
+ call! InformDialog.new(message), &block
164
+ end
165
+
166
+ def choose_from(items, caption=nil, selected_item=nil, &block)
167
+ call! SingleSelectionDialog.new(items, caption, selected_item), &block
168
+ end
169
+ end # class Component
170
+
171
+ end # module Wee
@@ -0,0 +1,39 @@
1
+ module Wee
2
+
3
+ class ExternalResource
4
+ def initialize(mount_path=nil)
5
+ @mount_path = mount_path || "/" + self.class.name.to_s.downcase.gsub("::", "_")
6
+ end
7
+
8
+ def install(builder)
9
+ rd = resource_dir()
10
+ builder.map(@mount_path) do
11
+ run Rack::File.new(rd)
12
+ end
13
+ end
14
+
15
+ def javascripts
16
+ []
17
+ end
18
+
19
+ def stylesheets
20
+ []
21
+ end
22
+
23
+ protected
24
+
25
+ def resource_dir
26
+ raise
27
+ end
28
+
29
+ def file_relative(_file, *subdirs)
30
+ File.expand_path(File.join(File.dirname(_file), *subdirs))
31
+ end
32
+
33
+ def mount_path_relative(*paths)
34
+ paths.map {|path| "#{@mount_path}/#{path}"}
35
+ end
36
+
37
+ end # class ExternalResource
38
+
39
+ end