lapillus 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/examples/guest_book.rb +50 -0
- data/examples/hello_world.rb +12 -0
- data/examples/persons.rb +30 -0
- data/examples/tc_guest_book.rb +34 -0
- data/examples/tc_hello_world.rb +24 -0
- data/examples/tc_persons.rb +13 -0
- data/html/GuestBook.html +24 -0
- data/html/HelloWorld.html +5 -0
- data/html/LapillusTesterTestPage.html +12 -0
- data/html/Persons.html +7 -0
- data/html/TestPage.html +8 -0
- data/html/TestRemotePanel.html +8 -0
- data/lib/changelog.txt +6 -0
- data/lib/lapillus/base.rb +276 -0
- data/lib/lapillus/behaviours.rb +208 -0
- data/lib/lapillus/components.rb +116 -0
- data/lib/lapillus/containers.rb +234 -0
- data/lib/lapillus/dispatcher.rb +216 -0
- data/lib/lapillus/form_components.rb +88 -0
- data/lib/lapillus/lapillus_server.rb +21 -0
- data/lib/lapillus/lapillus_testers.rb +167 -0
- data/lib/lapillus/mongrel_server.rb +69 -0
- data/lib/lapillus/multiview.rb +45 -0
- data/lib/lapillus/pager.rb +104 -0
- data/lib/lapillus/process_upload.rb +11 -0
- data/lib/lapillus/web_application.rb +12 -0
- data/lib/lapillus/webrick_server.rb +90 -0
- data/lib/lapillus.rb +33 -0
- data/lib/license.txt +24 -0
- data/test/tc_base.rb +63 -0
- data/test/tc_behaviours.rb +106 -0
- data/test/tc_bookmarkablepagelink.rb +58 -0
- data/test/tc_components.rb +285 -0
- data/test/tc_containers.rb +173 -0
- data/test/tc_dispatcher.rb +106 -0
- data/test/tc_examples.rb +193 -0
- data/test/tc_form.rb +231 -0
- data/test/tc_fragments.rb +114 -0
- data/test/tc_hierarchy.rb +34 -0
- data/test/tc_lapillus.rb +30 -0
- data/test/tc_lapillus_testers.rb +108 -0
- data/test/tc_multiview.rb +91 -0
- data/test/tc_pager.rb +94 -0
- data/test/tc_rendering.rb +74 -0
- data/test/ts_all_test.rb +20 -0
- metadata +94 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'lapillus'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
include Lapillus
|
6
|
+
|
7
|
+
class Comment
|
8
|
+
attr_reader :date
|
9
|
+
attr_accessor :text
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@date = Date.today
|
13
|
+
@text = ''
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class CommentForm < Form
|
18
|
+
attr_reader :comment
|
19
|
+
textarea "text", :model => :comment, :property => :text
|
20
|
+
def initialize(id)
|
21
|
+
super(id)
|
22
|
+
@comment = Comment.new
|
23
|
+
end
|
24
|
+
def on_submit(button)
|
25
|
+
parent.comments << comment
|
26
|
+
parent.commentslist.refresh
|
27
|
+
@comment = Comment.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class GuestBook < Webpage
|
32
|
+
attr_reader :comments
|
33
|
+
listview "commentslist", :model => :comments do |comment|
|
34
|
+
add(Label.new("date", comment.date.to_s))
|
35
|
+
add(MultiLineLabel.new("text", comment.text))
|
36
|
+
end
|
37
|
+
def initialize
|
38
|
+
super()
|
39
|
+
@comments = []
|
40
|
+
add(CommentForm.new("commentform"))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
if __FILE__ == $0
|
48
|
+
server = LapillusServer.new(:root_url => "/", :port => 2000, :homepage => GuestBook)
|
49
|
+
server.start
|
50
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'lapillus'
|
3
|
+
include Lapillus
|
4
|
+
|
5
|
+
class HelloWorld < Webpage
|
6
|
+
label "message", :model => "Hello world!"
|
7
|
+
end
|
8
|
+
|
9
|
+
if __FILE__ == $0
|
10
|
+
server = LapillusServer.new(:root_url => "/", :port => 2000, :homepage => HelloWorld)
|
11
|
+
server.start
|
12
|
+
end
|
data/examples/persons.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'lapillus'
|
3
|
+
include Lapillus
|
4
|
+
|
5
|
+
class Person
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Persons < Webpage
|
14
|
+
attr_reader :persons
|
15
|
+
listview "personlist", :model => :persons do |person|
|
16
|
+
label "name", :model => person, :property => :name
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
super()
|
21
|
+
person1 = Person.new("jantje")
|
22
|
+
person2 = Person.new("pietje")
|
23
|
+
@persons = [person1, person2]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if __FILE__ == $0
|
28
|
+
server = LapillusServer.new(:root_url => "/", :port => 2000, :homepage => Persons)
|
29
|
+
server.start
|
30
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "test/unit"
|
2
|
+
require 'examples/guest_book'
|
3
|
+
|
4
|
+
class TC_GuestBook < Test::Unit::TestCase
|
5
|
+
def test_guestbook_render
|
6
|
+
expected = <<EOF
|
7
|
+
<html>
|
8
|
+
<body>
|
9
|
+
<form enctype="multipart/form-data" method="post" lapillus:id="commentform">
|
10
|
+
Add your comment here:
|
11
|
+
<p/>
|
12
|
+
<textarea name="commentform.text" lapillus:id="text"></textarea>
|
13
|
+
<p/>
|
14
|
+
<input type="submit" value="Submit"/>
|
15
|
+
<input name="page" type="hidden" value="GuestBook"/></form>
|
16
|
+
<p/>
|
17
|
+
<span lapillus:id="commentslist"/>
|
18
|
+
<!--<lapillus:remove>\n <p>\n\t 1/2/2004<br/>\n\t More comment text here.\n </p>\n </lapillus:remove> -->
|
19
|
+
</body>
|
20
|
+
</html>
|
21
|
+
EOF
|
22
|
+
|
23
|
+
guestbook = GuestBook.new
|
24
|
+
assert_equal(expected.strip, guestbook.render)
|
25
|
+
end
|
26
|
+
|
27
|
+
# def test_guestbook_post
|
28
|
+
# #xxxform = Form.new("form")
|
29
|
+
# guestbook = GuestBook.new
|
30
|
+
# values = {'commentform.text'=> "a nice story!"}
|
31
|
+
# guestbook.post(values)
|
32
|
+
# end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'examples/hello_world'
|
3
|
+
|
4
|
+
class TC_HelloWorld < Test::Unit::TestCase
|
5
|
+
def test_hello_world
|
6
|
+
page = HelloWorld.new
|
7
|
+
assert_equal(1, page.components.size)
|
8
|
+
assert page.message.has_model?
|
9
|
+
assert_equal("Hello world!", page.message.model)
|
10
|
+
assert_equal("Hello world!", page.message.value)
|
11
|
+
end
|
12
|
+
|
13
|
+
# def test_html
|
14
|
+
# hello_world = HelloWorld.new
|
15
|
+
# result = <<EOF
|
16
|
+
#<html>
|
17
|
+
#<body>
|
18
|
+
# <span lapillus:id="message">Hello World!</span>
|
19
|
+
#</body>
|
20
|
+
#</html>
|
21
|
+
#EOF
|
22
|
+
# assert_equal( result.strip, hello_world.render )
|
23
|
+
# end
|
24
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'examples/persons'
|
3
|
+
|
4
|
+
class TC_Persons < Test::Unit::TestCase
|
5
|
+
def test_persons
|
6
|
+
page = Persons.new
|
7
|
+
assert_equal(1, page.components.size)
|
8
|
+
assert_equal(2, page.personlist.size)
|
9
|
+
assert_equal("jantje", page.personlist[0].name.value)
|
10
|
+
assert_equal("pietje", page.personlist[1].name.value)
|
11
|
+
# puts page.render
|
12
|
+
end
|
13
|
+
end
|
data/html/GuestBook.html
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
<html>
|
2
|
+
<body>
|
3
|
+
<form lapillus:id = "commentform">
|
4
|
+
Add your comment here:
|
5
|
+
<p/>
|
6
|
+
<textarea lapillus:id = "text">This is a comment</textarea>
|
7
|
+
<p/>
|
8
|
+
<input type = "submit" value = "Submit"/>
|
9
|
+
</form>
|
10
|
+
<p/>
|
11
|
+
<span lapillus:id = "commentslist">
|
12
|
+
<p>
|
13
|
+
<span lapillus:id = "date">1/1/2004</span><br/>
|
14
|
+
<span lapillus:id = "text">Comment text goes here.</span>
|
15
|
+
</p>
|
16
|
+
</span>
|
17
|
+
<!--<lapillus:remove>
|
18
|
+
<p>
|
19
|
+
1/2/2004<br/>
|
20
|
+
More comment text here.
|
21
|
+
</p>
|
22
|
+
</lapillus:remove> -->
|
23
|
+
</body>
|
24
|
+
</html>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<html>
|
2
|
+
<body>
|
3
|
+
<span lapillus:id="label">label</span>
|
4
|
+
<span lapillus:id="link">link</span>
|
5
|
+
<form lapillus:id="form">
|
6
|
+
<input id="name" lapillus:id="name"/>
|
7
|
+
<input id="title" lapillus:id="title"/>
|
8
|
+
<input type="submit" value="button 1"/>
|
9
|
+
<input type="submit" value="button 2"/>
|
10
|
+
</form>
|
11
|
+
</body>
|
12
|
+
</html>
|
data/html/Persons.html
ADDED
data/html/TestPage.html
ADDED
data/lib/changelog.txt
ADDED
@@ -0,0 +1,276 @@
|
|
1
|
+
module Lapillus
|
2
|
+
|
3
|
+
class Component
|
4
|
+
attr_reader :identifier, :property, :behaviours
|
5
|
+
attr_writer :visible, :model
|
6
|
+
def initialize(id, model=nil, property=nil)
|
7
|
+
@identifier = id
|
8
|
+
@model = model
|
9
|
+
@property = property
|
10
|
+
@behaviours = {}
|
11
|
+
@visible = true
|
12
|
+
end
|
13
|
+
#TODO: make model defensive!
|
14
|
+
def model
|
15
|
+
if @model.kind_of?(Symbol)
|
16
|
+
parent.send(@model)
|
17
|
+
else
|
18
|
+
@model
|
19
|
+
end
|
20
|
+
end
|
21
|
+
def parent
|
22
|
+
raise "parent not set!" if @parent.nil?
|
23
|
+
@parent
|
24
|
+
end
|
25
|
+
def value
|
26
|
+
if property.nil?
|
27
|
+
model
|
28
|
+
else
|
29
|
+
model.send(property)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_render
|
34
|
+
end
|
35
|
+
|
36
|
+
def visible?
|
37
|
+
@visible
|
38
|
+
end
|
39
|
+
|
40
|
+
def has_model?
|
41
|
+
!@model.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
#TODO: test parent, hierarchy, path, page
|
45
|
+
def has_parent?
|
46
|
+
!@parent.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
#TODO: add test for path that starts with a panel instead of an Webpage or Form
|
50
|
+
#NOTE: its not only panel... only webpage and form we do not want to see...
|
51
|
+
def path
|
52
|
+
hier = hierarchy
|
53
|
+
if hier[0].kind_of?(Panel)
|
54
|
+
start = 0
|
55
|
+
else
|
56
|
+
start = 1
|
57
|
+
end
|
58
|
+
hier[start..-1].collect {|component| component.identifier}.join(".")
|
59
|
+
end
|
60
|
+
|
61
|
+
def webpage
|
62
|
+
hierarchy.first
|
63
|
+
end
|
64
|
+
|
65
|
+
#TODO: this method is only tested indirectly
|
66
|
+
def session
|
67
|
+
request_cycle = RequestCycle.get()
|
68
|
+
return request_cycle.session
|
69
|
+
end
|
70
|
+
|
71
|
+
#TODO: this method is only tested indirectly
|
72
|
+
def response_page= page
|
73
|
+
request_cycle = RequestCycle.get()
|
74
|
+
request_cycle.response_page = page
|
75
|
+
end
|
76
|
+
|
77
|
+
def add_behaviour(behaviour)
|
78
|
+
behaviour.parent=self
|
79
|
+
@behaviours[behaviour.class] = behaviour
|
80
|
+
end
|
81
|
+
|
82
|
+
def has_behaviour?(behaviour_class)
|
83
|
+
return behaviour(behaviour_class)
|
84
|
+
end
|
85
|
+
|
86
|
+
def behaviour(behaviour_class)
|
87
|
+
return @behaviours[behaviour_class]
|
88
|
+
end
|
89
|
+
|
90
|
+
# this method is meant for AJAX
|
91
|
+
def render_component
|
92
|
+
doc = REXML::Document.new webpage.default_htmlfile
|
93
|
+
result_dom = webpage.render_container(doc.root)
|
94
|
+
element = REXML::XPath.first( result_dom, "//*[@id=\"#{path}\"]" )
|
95
|
+
if element == nil
|
96
|
+
message = "Identifier "+identifier+" not found! in "+doc.to_s
|
97
|
+
raise message
|
98
|
+
end
|
99
|
+
to_send = element.children.inject("") {|result, child|
|
100
|
+
child.write(result)
|
101
|
+
}
|
102
|
+
# puts "DEBUG: "+to_send
|
103
|
+
return to_send
|
104
|
+
end
|
105
|
+
|
106
|
+
protected
|
107
|
+
def hierarchy
|
108
|
+
return parent.hierarchy + [self] if has_parent?
|
109
|
+
[self]
|
110
|
+
end
|
111
|
+
|
112
|
+
def render_container(container_element)
|
113
|
+
new_element = REXML::Element.new(container_element)
|
114
|
+
render_to_element(new_element)
|
115
|
+
render_behaviours(new_element)
|
116
|
+
return new_element
|
117
|
+
end
|
118
|
+
|
119
|
+
def render_behaviours(element)
|
120
|
+
@behaviours.each_value {|behaviour| behaviour.render_to_element(element)}
|
121
|
+
end
|
122
|
+
|
123
|
+
#TODO: not here!
|
124
|
+
def post(values)
|
125
|
+
end
|
126
|
+
|
127
|
+
def parent=parent
|
128
|
+
@parent = parent
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
def remove_behaviour(behaviour_class)
|
133
|
+
@behaviours.delete(behaviour_class)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class Container < Component
|
138
|
+
attr_reader :components
|
139
|
+
def self.add_component(id, clazz, model=nil)
|
140
|
+
if model.nil?
|
141
|
+
internal_add_component(id) { clazz.new(id) }
|
142
|
+
else
|
143
|
+
internal_add_component(id) { clazz.new(id, model) }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
private
|
147
|
+
def self.internal_add_component(id, &block)
|
148
|
+
stored_components << StoredComponent.new(id, block)
|
149
|
+
define_method id do
|
150
|
+
component(id)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
def self.internal_add_block(id, block)
|
154
|
+
stored_blocks[id]=block
|
155
|
+
end
|
156
|
+
def self.block(id)
|
157
|
+
stored_blocks[id]
|
158
|
+
end
|
159
|
+
|
160
|
+
public
|
161
|
+
def initialize(id, model=nil, property=nil)
|
162
|
+
super(id, model, property)
|
163
|
+
@components = []
|
164
|
+
classes_to_process.each {|clazz|
|
165
|
+
clazz.stored_components.each {|stored_component|
|
166
|
+
#TODO model symbol stuff
|
167
|
+
new_component = stored_component.block.call
|
168
|
+
new_component.parent = self #NOTE: I could do this in the constructor!
|
169
|
+
components << new_component
|
170
|
+
}
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
#deprecated
|
175
|
+
def add(component)
|
176
|
+
@components.push(component)
|
177
|
+
component.parent = self
|
178
|
+
end
|
179
|
+
|
180
|
+
#deprecated
|
181
|
+
def [](path)
|
182
|
+
pathparts = path.split('.')
|
183
|
+
result = nil
|
184
|
+
container = self
|
185
|
+
pathparts.each {|pathpart|
|
186
|
+
result = container.component(pathpart)
|
187
|
+
container = result
|
188
|
+
}
|
189
|
+
return result
|
190
|
+
end
|
191
|
+
|
192
|
+
#deprecated
|
193
|
+
def component(identifier)
|
194
|
+
result = components.find{|component|
|
195
|
+
component.identifier.eql?(identifier)
|
196
|
+
}
|
197
|
+
raise "Component #{identifier} does not exist in container #{self.path}!\n" if result.nil?
|
198
|
+
return result
|
199
|
+
end
|
200
|
+
|
201
|
+
#NOTE: is this really the responsibility of container?
|
202
|
+
def post(values)
|
203
|
+
components.each {|component| component.post(values)}
|
204
|
+
end
|
205
|
+
|
206
|
+
def render_container(container)
|
207
|
+
new_element = REXML::Element.new(container)
|
208
|
+
render_children(container, new_element)
|
209
|
+
render_to_element(new_element)
|
210
|
+
render_behaviours(new_element)
|
211
|
+
return new_element
|
212
|
+
end
|
213
|
+
|
214
|
+
protected
|
215
|
+
def render_children(container_input,container_output)
|
216
|
+
container_input.children.each do |child|
|
217
|
+
case child.node_type
|
218
|
+
when :text
|
219
|
+
container_output.add_text(child.value)
|
220
|
+
when :element
|
221
|
+
#puts "processing element: "+child.name
|
222
|
+
if child.name!="fragment"
|
223
|
+
component_id = child.attributes['lapillus:id']
|
224
|
+
if !component_id.nil?
|
225
|
+
child_component = self[component_id]
|
226
|
+
child_component.on_render
|
227
|
+
container_output.add(child_component.render_container(child)) if child_component.visible?
|
228
|
+
else
|
229
|
+
new_element = REXML::Element.new(child)
|
230
|
+
container_output.add(new_element)
|
231
|
+
render_children(child, new_element)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
when :comment
|
235
|
+
comment = REXML::Comment.new(child)
|
236
|
+
container_output.add(comment)
|
237
|
+
else
|
238
|
+
puts "unknown node type: "+child.node_type.to_s
|
239
|
+
end
|
240
|
+
end
|
241
|
+
return container_output
|
242
|
+
end
|
243
|
+
|
244
|
+
def render_to_element(element)
|
245
|
+
end
|
246
|
+
|
247
|
+
private
|
248
|
+
def classes_to_process
|
249
|
+
classes = []
|
250
|
+
class_to_start_with = self.class
|
251
|
+
while (class_to_start_with.superclass!=Component)
|
252
|
+
classes << class_to_start_with
|
253
|
+
class_to_start_with = class_to_start_with.superclass
|
254
|
+
end
|
255
|
+
return classes
|
256
|
+
end
|
257
|
+
|
258
|
+
def self.stored_components
|
259
|
+
@stored_components ||= []
|
260
|
+
end
|
261
|
+
def self.stored_blocks
|
262
|
+
@stored_blocks ||= {}
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
#Note: Internal
|
267
|
+
class StoredComponent
|
268
|
+
attr_reader :identifier, :block
|
269
|
+
|
270
|
+
def initialize(id, block)
|
271
|
+
@identifier = id
|
272
|
+
@block = block
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|