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
data/README.rdoc
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
= Wee Web Framework
|
2
|
+
|
3
|
+
== Copyright and License
|
4
|
+
|
5
|
+
Copyright (c) 2004, 2005, 2009, 2010 by Michael Neumann (mneumann@ntecs.de).
|
6
|
+
|
7
|
+
Released under the same terms of license as Ruby.
|
8
|
+
|
9
|
+
== Introduction
|
10
|
+
|
11
|
+
Wee is a light-weight, very high-level and modern web-framework that makes
|
12
|
+
<b>W</b>eb <b>e</b>ngineering <b>e</b>asy. It mainly inherits many ideas and
|
13
|
+
features from Seaside[http://seaside.st/], but was written from scratch
|
14
|
+
without ever looking at the Seaside (or any other) sources. All code was
|
15
|
+
developed from ideas and lots of discussions with Avi Bryant.
|
16
|
+
|
17
|
+
== Features
|
18
|
+
|
19
|
+
=== Reusable components
|
20
|
+
|
21
|
+
Wee has _real_ components, which are like widgets in a GUI. Once written, you
|
22
|
+
can use them everywhere. They are completely independent and do not interfere
|
23
|
+
with other components. Components encapsulate state, a view and actions. Of
|
24
|
+
course you can use an external model or use templates for rendering.
|
25
|
+
|
26
|
+
=== Backtracking
|
27
|
+
|
28
|
+
See the <i>What is backtracking?</i> section below. In short, backtracking lets
|
29
|
+
the browser's back and forward-button play well together with your application.
|
30
|
+
|
31
|
+
=== Clean and concise
|
32
|
+
|
33
|
+
Wee is well thought out, is written in *and* supports clean and concise code.
|
34
|
+
Furthermore I think most parts are now very well documented.
|
35
|
+
|
36
|
+
=== Templating-independent
|
37
|
+
|
38
|
+
Wee does not depend on a special templating-engine. You can use a different
|
39
|
+
templating engine for each component if you want.
|
40
|
+
|
41
|
+
=== Powerful programmatic HTML generation
|
42
|
+
|
43
|
+
Wee ships with an easy to use and very powerful programmatic HTML-generation
|
44
|
+
library. For example you can create a select list easily with this piece of
|
45
|
+
code:
|
46
|
+
|
47
|
+
# select an object from these items
|
48
|
+
items = [1, 2, 3, 4]
|
49
|
+
|
50
|
+
# the labels shown to the user
|
51
|
+
labels = items.map {|i| i.to_s}
|
52
|
+
|
53
|
+
# render it
|
54
|
+
r.select_list(items).labels(labels).callback {|choosen| p choosen}
|
55
|
+
|
56
|
+
# render a multi-select list, with objects 2 and 4 selected
|
57
|
+
r.select_list(items).multi.labels(labels).selected([2,4])
|
58
|
+
|
59
|
+
The callback is called with the selected objects from the _items_ array. Items
|
60
|
+
can be any object, even whole components:
|
61
|
+
|
62
|
+
labels = ["msg1", "msg2"]
|
63
|
+
items = labels.collect {|m| MessageBox.new(m)}
|
64
|
+
r.select_list(items).labels(labels).callback {|choosen| call choosen.first}
|
65
|
+
|
66
|
+
== Observations and Limitations
|
67
|
+
|
68
|
+
* Components are thread-safe by nature as a fresh components-tree is created
|
69
|
+
for each session and requests inside a session are serialized.
|
70
|
+
|
71
|
+
== What is backtracking?
|
72
|
+
|
73
|
+
If you want, you can make the back-button of your browser work correctly
|
74
|
+
together with your web-application. Imagine you have a simple counter
|
75
|
+
application, which shows the current count and two links _inc_ and _dec_ with
|
76
|
+
which you can increase or decrease the current count. Starting with an inital
|
77
|
+
count of 0 you increase the counter up to 8, then click three times the back
|
78
|
+
button of your browser (now displays 5). Finally you decrease by one and
|
79
|
+
your counter shows what you'd have expected: 4. In contrast, traditional
|
80
|
+
web applications would have shown 7, because the back button usually does
|
81
|
+
not trigger a HTTP request and as such the server-side state still has
|
82
|
+
a value of 8 for the counter when the request to decrease comes in.
|
83
|
+
|
84
|
+
The solution to this problem is to take snapshots of the components state
|
85
|
+
after an action is performed and restoring the state before peforming
|
86
|
+
actions. Each action generates a new state, which is indicated by
|
87
|
+
a so-called <i>page-id</i> within the URL.
|
88
|
+
|
89
|
+
== Decorations
|
90
|
+
|
91
|
+
Decorations are used to modify the look and behaviour of a component without
|
92
|
+
modifying the components tree itself. A component can have more than one
|
93
|
+
decoration. Decorations are implemented as a linked list (Wee::Decoration#next
|
94
|
+
points to the next decoration), starting at Wee::Component#decoration, which
|
95
|
+
either points to the next decoration in the chain, or to itself.
|
96
|
+
|
97
|
+
== The request/response cycle
|
98
|
+
|
99
|
+
The request/response cycle in Wee is actually split into two separate phases.
|
100
|
+
|
101
|
+
=== Render Phase
|
102
|
+
|
103
|
+
The rendering phase is assumed to be side-effect free! So, you as a programmer
|
104
|
+
should take care to meet this assumption. Rendering is performed by method
|
105
|
+
Wee::Component#render!.
|
106
|
+
|
107
|
+
=== Action Phase (Invoking Callbacks)
|
108
|
+
|
109
|
+
Possible sources for callbacks are links (anchors) and all kinds of
|
110
|
+
form-elements like submit buttons, input-fields etc. There are two different
|
111
|
+
kinds of callbacks:
|
112
|
+
|
113
|
+
* Input callbacks (input-fields)
|
114
|
+
|
115
|
+
* Action callbacks (anchor, submit-button)
|
116
|
+
|
117
|
+
The distinction between input and action callbacks is important, as action
|
118
|
+
callbacks might depend on values of input-fields being assigned to instance
|
119
|
+
variables of the controlling component. Hence, Wee first invokes all input
|
120
|
+
callbacks before any action callback is triggered. Callback processing
|
121
|
+
is performed by method Wee::Component#process_callbacks.
|
122
|
+
|
123
|
+
The result of the action phase is an updated components state. As such, a
|
124
|
+
snapshot is taken of the new state and stored under a new page-id. Then, a
|
125
|
+
redirect requests is sent back to the client, including this new page-id.
|
126
|
+
The client automatically follows this redirect and triggers a render phase of
|
127
|
+
the new page.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rake/rdoctask'
|
2
|
+
|
3
|
+
Rake::RDocTask.new do |rd|
|
4
|
+
rd.main = "README.rdoc"
|
5
|
+
rd.rdoc_dir = 'doc/rdoc'
|
6
|
+
rd.rdoc_files.include('lib/**/*.rb', 'README.rdoc')
|
7
|
+
rd.options << '--inline-source'
|
8
|
+
rd.options << '--all'
|
9
|
+
rd.options << '--accessor=html_attr=HtmlAttribute'
|
10
|
+
rd.options << '--accessor=generic_tag=GenericTagBrush'
|
11
|
+
rd.options << '--accessor=generic_single_tag=GenericSingleTagBrush'
|
12
|
+
rd.options << '--accessor=brush_tag=Brush'
|
13
|
+
end
|
14
|
+
|
15
|
+
task :test do
|
16
|
+
sh 'mspec -I./lib -f s test/component_spec.rb'
|
17
|
+
end
|
18
|
+
|
19
|
+
task :package do
|
20
|
+
sh 'gem build wee.gemspec'
|
21
|
+
end
|
22
|
+
|
23
|
+
task :clean => [:clobber_rdoc]
|
24
|
+
|
25
|
+
task :default => [:test, :rdoc, :clean]
|
data/aWee.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
if File.read('lib/wee.rb') =~ /Version\s+=\s+"(\d+\.\d+\.\d+)"/
|
4
|
+
version = $1
|
5
|
+
else
|
6
|
+
raise "no version"
|
7
|
+
end
|
8
|
+
|
9
|
+
spec = Gem::Specification.new do |s|
|
10
|
+
s.name = 'mullen-wee'
|
11
|
+
s.version = version
|
12
|
+
s.summary = 'Wee is a framework for building highly dynamic web applications.'
|
13
|
+
s.description =
|
14
|
+
"Wee is a stateful component-orient web framework which supports "
|
15
|
+
"continuations as well as multiple page-states, aka backtracking. "
|
16
|
+
"It is largely inspired by Smalltalk's Seaside framework."
|
17
|
+
s.add_dependency('rack', '>= 1.0.0')
|
18
|
+
s.add_dependency('mspec', '>= 1.5.9')
|
19
|
+
s.add_dependency('fast_gettext', '>= 0.4.17')
|
20
|
+
s.files = Dir['**/*']
|
21
|
+
s.require_path = 'lib'
|
22
|
+
s.author = "Michael Neumann"
|
23
|
+
s.email = "mneumann@ntecs.de"
|
24
|
+
s.homepage = "http://rubyforge.org/projects/wee"
|
25
|
+
s.rubyforge_project = "wee"
|
26
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
$LOAD_PATH.unshift << "../lib"
|
2
|
+
require 'rubygems'
|
3
|
+
require 'wee'
|
4
|
+
require 'demo/messagebox'
|
5
|
+
require 'enumerator'
|
6
|
+
|
7
|
+
module ObjectSpaceBrowser
|
8
|
+
|
9
|
+
class Klasses < Wee::Component
|
10
|
+
|
11
|
+
def klasses
|
12
|
+
ObjectSpace.to_enum(:each_object, Class).sort_by{|k| k.name}
|
13
|
+
end
|
14
|
+
|
15
|
+
def choose(klass)
|
16
|
+
call Klass.new(klass)
|
17
|
+
end
|
18
|
+
|
19
|
+
def render(r)
|
20
|
+
r.h1 "Classes"
|
21
|
+
|
22
|
+
r.ul {
|
23
|
+
klasses.each do |klass|
|
24
|
+
r.li { r.anchor.callback_method(:choose, klass).with(klass.name) }
|
25
|
+
end
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Klass < Wee::Component
|
31
|
+
|
32
|
+
def initialize(klass)
|
33
|
+
super()
|
34
|
+
@klass = klass
|
35
|
+
set_instances
|
36
|
+
end
|
37
|
+
|
38
|
+
def choose(instance)
|
39
|
+
call Instance.new(instance)
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Fetches all instances of the klass sorted by object_id
|
44
|
+
|
45
|
+
def set_instances
|
46
|
+
@instances =
|
47
|
+
case @klass
|
48
|
+
when Symbol
|
49
|
+
Symbol.all_symbols.sort_by do |s| s.to_s end
|
50
|
+
else
|
51
|
+
ObjectSpace.to_enum(:each_object, @klass).sort_by{|i| i.object_id}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def render(r)
|
56
|
+
instances = @instances
|
57
|
+
r.h1 "Class #{@klass.name}"
|
58
|
+
r.h2 "#{@instances.length} Instances"
|
59
|
+
|
60
|
+
return if @instances.length.zero?
|
61
|
+
|
62
|
+
r.ul {
|
63
|
+
@instances.each do |instance|
|
64
|
+
r.li { r.anchor.callback_method(:choose, instance).with("0x%x" % instance.object_id) }
|
65
|
+
end
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
class Instance < Wee::Component
|
72
|
+
|
73
|
+
def initialize(instance)
|
74
|
+
super()
|
75
|
+
@instance = instance
|
76
|
+
end
|
77
|
+
|
78
|
+
def choose(instance)
|
79
|
+
call Instance.new(instance)
|
80
|
+
end
|
81
|
+
|
82
|
+
def back
|
83
|
+
answer
|
84
|
+
end
|
85
|
+
|
86
|
+
def render(r)
|
87
|
+
r.anchor.callback_method(:back).with("back")
|
88
|
+
|
89
|
+
r.break
|
90
|
+
r.h1 "Instance 0x%x of #{@instance.class.name}" % @instance.object_id
|
91
|
+
|
92
|
+
case @instance
|
93
|
+
when Array
|
94
|
+
r.bold("array elements: ")
|
95
|
+
r.break
|
96
|
+
r.ul do
|
97
|
+
@instance.each do |obj|
|
98
|
+
r.li { render_obj(r, obj) }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
when Hash
|
102
|
+
r.bold("hash elements: ")
|
103
|
+
r.break
|
104
|
+
r.table.border(1).with do
|
105
|
+
r.table_row do
|
106
|
+
r.table_data do r.bold("Key") end
|
107
|
+
r.table_data do r.bold("Value") end
|
108
|
+
end
|
109
|
+
|
110
|
+
@instance.each_pair do |k, v|
|
111
|
+
r.table_row do
|
112
|
+
r.table_data { render_obj(r, k) }
|
113
|
+
r.table_data { render_obj(r, v) }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
when String, Float, Fixnum, Bignum, Numeric, Integer, Symbol
|
119
|
+
r.encode_text(@instance.inspect)
|
120
|
+
end
|
121
|
+
|
122
|
+
return if @instance.instance_variables.empty?
|
123
|
+
r.break
|
124
|
+
|
125
|
+
render_instance_variables(r)
|
126
|
+
end
|
127
|
+
|
128
|
+
def render_instance_variables(r)
|
129
|
+
r.table.border(1).with do
|
130
|
+
r.table_row do
|
131
|
+
r.table_data do r.bold("Instance Variable") end
|
132
|
+
r.table_data do r.bold("Object") end
|
133
|
+
end
|
134
|
+
@instance.instance_variables.each do |var| render_ivar_row(r, var) end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def render_ivar_row(r, var)
|
139
|
+
r.table_row do
|
140
|
+
r.table_data(var)
|
141
|
+
r.table_data do
|
142
|
+
v = @instance.instance_variable_get(var)
|
143
|
+
render_obj(r, v)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def render_obj(r, obj)
|
149
|
+
r.anchor.callback_method(:choose, obj).with do
|
150
|
+
r.bold(obj.class.name)
|
151
|
+
r.space
|
152
|
+
r.text("(#{ obj.object_id })")
|
153
|
+
r.space
|
154
|
+
r.space
|
155
|
+
|
156
|
+
case obj
|
157
|
+
when String, Float, Integer, Symbol
|
158
|
+
r.encode_text(obj.inspect)
|
159
|
+
else
|
160
|
+
r.encode_text(obj.inspect[0, 40] + "...")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end # module ObjectSpaceBrowser
|
168
|
+
|
169
|
+
if $0 == __FILE__ then
|
170
|
+
|
171
|
+
OBJ = {
|
172
|
+
"hello" => { [1,2,3] => [5,6,7], "test" => :super },
|
173
|
+
"other" => %w(a b c d e f)
|
174
|
+
}
|
175
|
+
|
176
|
+
class Main < Wee::Component
|
177
|
+
def initialize
|
178
|
+
super
|
179
|
+
add_decoration(Wee::PageDecoration.new("Hello World"))
|
180
|
+
@instance = ObjectSpaceBrowser::Instance.new(OBJ)
|
181
|
+
end
|
182
|
+
|
183
|
+
def children() [@instance] end
|
184
|
+
|
185
|
+
def render(r)
|
186
|
+
r.render(@instance)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
Wee.run(Main)
|
191
|
+
end
|
data/examples/ajax.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
$LOAD_PATH.unshift "../lib"
|
2
|
+
require 'rubygems'
|
3
|
+
require 'wee'
|
4
|
+
require 'rack'
|
5
|
+
|
6
|
+
class AjaxCounter < Wee::Component
|
7
|
+
|
8
|
+
#require 'wee/jquery'
|
9
|
+
#def self.depends; [Wee::JQuery] end
|
10
|
+
|
11
|
+
require 'wee/rightjs'
|
12
|
+
def self.depends; [Wee::RightJS] end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@counter = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def state(s)
|
19
|
+
super
|
20
|
+
s.add_ivar(self, :@counter, @counter)
|
21
|
+
end
|
22
|
+
|
23
|
+
=begin
|
24
|
+
def style
|
25
|
+
"div.wee-AjaxCounter a { border: 1px solid blue; padding: 5px; background-color: #ABABAB; };"
|
26
|
+
end
|
27
|
+
|
28
|
+
def render(r)
|
29
|
+
r.render_style(self)
|
30
|
+
r.div.css_class('wee-AjaxCounter').oid.with {
|
31
|
+
r.anchor.update_component_on(:click) { @counter += 1 }.with(@counter.to_s)
|
32
|
+
}
|
33
|
+
end
|
34
|
+
=end
|
35
|
+
|
36
|
+
def render(r)
|
37
|
+
r.anchor.oid.update_component_on(:click) { @counter += 1 }.with(@counter.to_s)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
class HelloWorld < Wee::RootComponent
|
43
|
+
|
44
|
+
def self.depends; [AjaxCounter.depends] end
|
45
|
+
|
46
|
+
def title
|
47
|
+
'Wee + Ajax'
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize
|
51
|
+
@counters = (1..10).map { AjaxCounter.new }
|
52
|
+
end
|
53
|
+
|
54
|
+
def children() @counters end
|
55
|
+
|
56
|
+
def render(r)
|
57
|
+
render_hello(r)
|
58
|
+
r.div.callback_on(:click) { p "refresh" }.with("Refresh")
|
59
|
+
@counters.each {|c| r.render(c); r.break}
|
60
|
+
end
|
61
|
+
|
62
|
+
def render_hello(r)
|
63
|
+
@hello ||= "Hello"
|
64
|
+
r.div.id("hello").update_on(:click) {|r|
|
65
|
+
@hello.reverse!
|
66
|
+
render_hello(r)
|
67
|
+
}.with(@hello)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if __FILE__ == $0
|
72
|
+
Wee.run HelloWorld, :mount_path => '/ajax', :print_message => true
|
73
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
$LOAD_PATH.unshift '../../lib'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'wee'
|
4
|
+
|
5
|
+
class BearTrap < Wee::Component
|
6
|
+
attr_accessor :mouse
|
7
|
+
|
8
|
+
def initialize(is_charged=true)
|
9
|
+
@charged = is_charged
|
10
|
+
add_decoration Wee::OidDecoration.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def render(r)
|
14
|
+
img = @charged ? 'charged' : 'snapped'
|
15
|
+
brush = r.div.id('bear_trap').style("background: transparent url('/images/bear_trap_#{img}.png');")
|
16
|
+
if @charged
|
17
|
+
if @over
|
18
|
+
brush.update_on(:mouseout) {|r|
|
19
|
+
@over = false
|
20
|
+
r.render(self)
|
21
|
+
}
|
22
|
+
else
|
23
|
+
brush.update_on(:mouseover) {|r|
|
24
|
+
@over = true
|
25
|
+
@mouse.update(r)
|
26
|
+
if @mouse.cheese_count >= 3
|
27
|
+
@charged = false
|
28
|
+
end
|
29
|
+
r.render(self)
|
30
|
+
r.javascript("alert('gotcha')") unless @charged
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
brush.with { r.image.src('/images/cheese.png').id('cheese') }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Mouse < Wee::Component
|
39
|
+
attr_reader :cheese_count
|
40
|
+
|
41
|
+
def initialize(cheese_count=0)
|
42
|
+
@cheese_count = cheese_count
|
43
|
+
end
|
44
|
+
|
45
|
+
def render(r)
|
46
|
+
r.image.src("/images/mouse.png").id("mouse").width(90 * (@cheese_count+1))
|
47
|
+
end
|
48
|
+
|
49
|
+
def update(r)
|
50
|
+
@cheese_count += 1
|
51
|
+
r.render(self)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Main < Wee::Component
|
56
|
+
def initialize
|
57
|
+
super
|
58
|
+
add_decoration Wee::PageDecoration.new('A dark forest...', %w(/stylesheets/forest.css),
|
59
|
+
%w(/javascripts/jquery-1.3.2.min.js /javascripts/wee-jquery.js))
|
60
|
+
@trap = BearTrap.new(true)
|
61
|
+
@mouse = Mouse.new
|
62
|
+
@trap.mouse = @mouse
|
63
|
+
end
|
64
|
+
|
65
|
+
def children() [@trap, @mouse] end
|
66
|
+
|
67
|
+
def render(r)
|
68
|
+
r.div.id('forest').with {
|
69
|
+
r.render @trap
|
70
|
+
r.render @mouse
|
71
|
+
}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
Wee.run(Main, :public_path => 'public') if __FILE__ == $0
|