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,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