mullen-wee 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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,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.
@@ -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]
@@ -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
@@ -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