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.
- data/README.rdoc +127 -0
- data/Rakefile +25 -0
- data/aWee.gemspec +26 -0
- data/examples/ObjectSpaceBrowser.rb +191 -0
- data/examples/ajax.rb +73 -0
- data/examples/apotomo-webhunter/main.rb +75 -0
- data/examples/apotomo-webhunter/public/images/bear_trap_charged.png +0 -0
- data/examples/apotomo-webhunter/public/images/bear_trap_snapped.png +0 -0
- data/examples/apotomo-webhunter/public/images/cheese.png +0 -0
- data/examples/apotomo-webhunter/public/images/dark_forest.jpg +0 -0
- data/examples/apotomo-webhunter/public/images/mouse.png +0 -0
- data/examples/apotomo-webhunter/public/javascripts/jquery-1.3.2.min.js +19 -0
- data/examples/apotomo-webhunter/public/javascripts/wee-jquery.js +19 -0
- data/examples/apotomo-webhunter/public/stylesheets/forest.css +33 -0
- data/examples/arc_challenge.rb +42 -0
- data/examples/arc_challenge2.rb +46 -0
- data/examples/cheese_task.rb +27 -0
- data/examples/continuations.rb +28 -0
- data/examples/demo.rb +135 -0
- data/examples/demo/calculator.rb +63 -0
- data/examples/demo/calendar.rb +333 -0
- data/examples/demo/counter.rb +38 -0
- data/examples/demo/editable_counter.rb +36 -0
- data/examples/demo/example.rb +142 -0
- data/examples/demo/file_upload.rb +19 -0
- data/examples/demo/messagebox.rb +15 -0
- data/examples/demo/radio.rb +33 -0
- data/examples/demo/window.rb +71 -0
- data/examples/hw.rb +11 -0
- data/examples/i18n/app.rb +16 -0
- data/examples/i18n/locale/de/app.po +25 -0
- data/examples/i18n/locale/en/app.po +25 -0
- data/examples/pager.rb +102 -0
- data/lib/wee.rb +109 -0
- data/lib/wee/application.rb +89 -0
- data/lib/wee/callback.rb +109 -0
- data/lib/wee/component.rb +363 -0
- data/lib/wee/decoration.rb +251 -0
- data/lib/wee/dialog.rb +171 -0
- data/lib/wee/external_resource.rb +39 -0
- data/lib/wee/html_brushes.rb +795 -0
- data/lib/wee/html_canvas.rb +254 -0
- data/lib/wee/html_document.rb +52 -0
- data/lib/wee/html_writer.rb +71 -0
- data/lib/wee/id_generator.rb +81 -0
- data/lib/wee/jquery.rb +11 -0
- data/lib/wee/jquery/jquery-1.3.2.min.js +19 -0
- data/lib/wee/jquery/wee-jquery.js +19 -0
- data/lib/wee/locale.rb +56 -0
- data/lib/wee/lru_cache.rb +91 -0
- data/lib/wee/presenter.rb +44 -0
- data/lib/wee/renderer.rb +72 -0
- data/lib/wee/request.rb +56 -0
- data/lib/wee/response.rb +68 -0
- data/lib/wee/rightjs.rb +11 -0
- data/lib/wee/rightjs/rightjs-1.5.2.min.js +9 -0
- data/lib/wee/rightjs/wee-rightjs.js +18 -0
- data/lib/wee/root_component.rb +45 -0
- data/lib/wee/session.rb +366 -0
- data/lib/wee/state.rb +102 -0
- data/lib/wee/task.rb +16 -0
- data/test/bm_render.rb +34 -0
- data/test/component_spec.rb +40 -0
- data/test/stress/plotter.rb +84 -0
- data/test/stress/stress_client.rb +51 -0
- data/test/stress/stress_local.rb +86 -0
- data/test/stress/stress_server.rb +83 -0
- data/test/test_component.rb +106 -0
- data/test/test_html_canvas.rb +25 -0
- data/test/test_html_writer.rb +32 -0
- data/test/test_lru_cache.rb +51 -0
- data/test/test_request.rb +42 -0
- 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
|
data/lib/wee/dialog.rb
ADDED
@@ -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
|