mullen-wee 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|