lapillus 0.0.2
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.
- 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
|