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.
Files changed (46) hide show
  1. data/examples/guest_book.rb +50 -0
  2. data/examples/hello_world.rb +12 -0
  3. data/examples/persons.rb +30 -0
  4. data/examples/tc_guest_book.rb +34 -0
  5. data/examples/tc_hello_world.rb +24 -0
  6. data/examples/tc_persons.rb +13 -0
  7. data/html/GuestBook.html +24 -0
  8. data/html/HelloWorld.html +5 -0
  9. data/html/LapillusTesterTestPage.html +12 -0
  10. data/html/Persons.html +7 -0
  11. data/html/TestPage.html +8 -0
  12. data/html/TestRemotePanel.html +8 -0
  13. data/lib/changelog.txt +6 -0
  14. data/lib/lapillus/base.rb +276 -0
  15. data/lib/lapillus/behaviours.rb +208 -0
  16. data/lib/lapillus/components.rb +116 -0
  17. data/lib/lapillus/containers.rb +234 -0
  18. data/lib/lapillus/dispatcher.rb +216 -0
  19. data/lib/lapillus/form_components.rb +88 -0
  20. data/lib/lapillus/lapillus_server.rb +21 -0
  21. data/lib/lapillus/lapillus_testers.rb +167 -0
  22. data/lib/lapillus/mongrel_server.rb +69 -0
  23. data/lib/lapillus/multiview.rb +45 -0
  24. data/lib/lapillus/pager.rb +104 -0
  25. data/lib/lapillus/process_upload.rb +11 -0
  26. data/lib/lapillus/web_application.rb +12 -0
  27. data/lib/lapillus/webrick_server.rb +90 -0
  28. data/lib/lapillus.rb +33 -0
  29. data/lib/license.txt +24 -0
  30. data/test/tc_base.rb +63 -0
  31. data/test/tc_behaviours.rb +106 -0
  32. data/test/tc_bookmarkablepagelink.rb +58 -0
  33. data/test/tc_components.rb +285 -0
  34. data/test/tc_containers.rb +173 -0
  35. data/test/tc_dispatcher.rb +106 -0
  36. data/test/tc_examples.rb +193 -0
  37. data/test/tc_form.rb +231 -0
  38. data/test/tc_fragments.rb +114 -0
  39. data/test/tc_hierarchy.rb +34 -0
  40. data/test/tc_lapillus.rb +30 -0
  41. data/test/tc_lapillus_testers.rb +108 -0
  42. data/test/tc_multiview.rb +91 -0
  43. data/test/tc_pager.rb +94 -0
  44. data/test/tc_rendering.rb +74 -0
  45. data/test/ts_all_test.rb +20 -0
  46. metadata +94 -0
@@ -0,0 +1,208 @@
1
+ require 'rexml/attribute'
2
+ require 'yaml'
3
+
4
+ module Lapillus
5
+
6
+ class Behaviour < Component
7
+ def initialize(id="", value=nil)
8
+ super(id, value)
9
+ end
10
+
11
+ # def add_javascript_to_header root, src
12
+ # # add the neccesary javascript links to the head of the html file
13
+ # head=root.get_elements("/html/head")[0]
14
+ # raise "Error: no html head found in:\n#{root}" if head.nil?
15
+ #
16
+ # script_already_set=false
17
+ # head.each_element_with_attribute('src', src, 1, 'script'){ |e| script_already_set=true }
18
+ # return if script_already_set
19
+ #
20
+ # script= REXML::Element.new('script')
21
+ # script.add_attributes({'type'=>'text/javascript', 'language'=>'javascript', 'src'=>src})
22
+ # script.add_text(";")
23
+ # head.add_element(script)
24
+ # end
25
+
26
+ end
27
+
28
+ class AttributeModifier < Behaviour
29
+ def render_to_element(element)
30
+ attribute = REXML::Attribute.new(identifier, value)
31
+ def attribute.to_string
32
+ return "#@expanded_name=\"#@value\""
33
+ end
34
+ element.add_attribute(attribute)
35
+ end
36
+ end
37
+
38
+ #TODO test
39
+ class UniqueIdentifier < AttributeModifier
40
+ def initialize
41
+ super("id")
42
+ end
43
+ def value
44
+ parent.path
45
+ end
46
+ end
47
+
48
+ class FadeIn < Behaviour
49
+ def render_to_element(element)
50
+ attribute = REXML::Attribute.new(identifier, "new Effect.Appear('#{value}');")
51
+ def attribute.to_string
52
+ return "#@expanded_name=\"#@value\""
53
+ end
54
+ element.add_attribute(attribute)
55
+ end
56
+ end
57
+
58
+ class Draggable < UniqueIdentifier
59
+
60
+ def initialize(options=[])
61
+ super()
62
+ @extra_options=options.to_a
63
+ @option=Hash.new
64
+ end
65
+
66
+ def handle= handleclass
67
+ @option[:handle] = "'#{handleclass}'"
68
+ end
69
+
70
+ def revert= value
71
+ @option[:revert]= value ? "true" : "false"
72
+ end
73
+
74
+ def snap= value
75
+ @option[:snap]= value ? "true" : "false"
76
+ end
77
+
78
+ def zindex= value
79
+ @option[:zindex]= value
80
+ end
81
+
82
+ def constraint= value
83
+ @option[:constraint]="'#{value}'"
84
+ end
85
+
86
+ def ghosting= value
87
+ @option[:ghosting]= value ? "true" : "false"
88
+ end
89
+
90
+ def start_effect= value
91
+ @option[:starteffect]= value
92
+ end
93
+
94
+ def revert_effect= value
95
+ @option[:reverteffect]= value
96
+ end
97
+
98
+ def end_effect= value
99
+ @option[:endeffect]= value
100
+ end
101
+
102
+ def change= value
103
+ @option[:change]= value
104
+ end
105
+
106
+ def render_to_element(element)
107
+ super
108
+ #add_javascript_to_header(element.root_node,"/editiemachine/context/javascript/prototype.js")
109
+ #add_javascript_to_header(element.root_node,"/editiemachine/context/javascript/scriptaculous.js")
110
+
111
+ script = REXML::Element.new('script')
112
+ script.add_attributes({'type'=>'text/javascript', 'language'=>'javascript'})
113
+ options=@option.to_a.collect{|o| "#{o[0]}:#{o[1]}" }.sort.concat(@extra_options).join(",")
114
+ text=REXML::Text.new("\n// <![CDATA[\nnew Draggable('#{parent.path}', {#{options}});\n// ]]>\n")
115
+ def text.to_s
116
+ @normalized=@string
117
+ end
118
+ script.add_text(text)
119
+ element.add_element(script)
120
+ end
121
+ end
122
+
123
+ # duplication between OnClick and AjaxLink
124
+ # todo: add test
125
+ class OnClick < Behaviour
126
+ def initialize(component_to_refresh=nil)
127
+ super(nil, component_to_refresh)
128
+ component_to_refresh.add_behaviour(UniqueIdentifier.new) if has_model?
129
+ end
130
+
131
+ def render_to_element(element)
132
+ if has_model?
133
+ javascript = "new Ajax.Updater('#{value.path}', '', { evalScripts: true, method:'get', parameters: { listener:'#{parent.path}' }}); return false;"
134
+ else
135
+ javascript = "new Ajax.Request('', { method: 'get', parameters: { listener:'#{parent.path}' }}); return false;"
136
+ end
137
+ attribute = REXML::Attribute.new("onclick", javascript)
138
+ # TODO: this should be the default for attributes in
139
+ # Lapillus somewhere!
140
+ def attribute.to_string
141
+ return "#@expanded_name=\"#@value\""
142
+ end
143
+ element.add_attribute(attribute)
144
+ end
145
+ end
146
+
147
+ # value is the component to rerender when a link is clicked
148
+ # the component should have an id attribute which is set to its path
149
+ class AjaxLink < Container
150
+ def initialize(id, value=nil)
151
+ super(id, value)
152
+ value.add_behaviour(UniqueIdentifier.new) if has_model?
153
+ end
154
+
155
+ def render_to_element(element)
156
+ if has_model?
157
+ javascript = "new Ajax.Updater('#{value.path}', '', { evalScripts: true, method:'get', parameters: { listener:'#{path}' }}); return false;"
158
+ else
159
+ javascript = "new Ajax.Request('', { method: 'get', parameters: { listener:'#{path}' }}); return false;"
160
+ end
161
+ attribute = REXML::Attribute.new("onclick", javascript)
162
+ def attribute.to_string
163
+ return "#@expanded_name=\"#@value\""
164
+ end
165
+ element.add_attribute(attribute)
166
+ element.add_attribute("href","#")
167
+ end
168
+
169
+ def on_click_handler=function
170
+ @function=function
171
+ end
172
+
173
+ def on_click
174
+ instance_eval(&@function)
175
+ end
176
+
177
+ def render_component
178
+ value.render_component if has_model?
179
+ end
180
+ end
181
+
182
+ class Droppable < UniqueIdentifier
183
+ def render_to_element(element)
184
+ javascript = <<EOF
185
+ Droppables.add('#{parent.path}', {
186
+ onDrop: function(element) {
187
+ new Ajax.Updater('#{parent.path}', '', {
188
+ method:'get', parameters: {
189
+ listener:'#{parent.path}',
190
+ component:element.id
191
+ }
192
+ }); return false;
193
+ }
194
+ });
195
+ EOF
196
+ super
197
+ script= REXML::Element.new('script')
198
+ script.add_attributes({'type'=>'text/javascript', 'language'=>'javascript'})
199
+ text=REXML::Text.new("\n// <![CDATA[\n#{javascript}\n// ]]>\n")
200
+ def text.to_s
201
+ @normalized=@string
202
+ end
203
+ script.add_text(text)
204
+ element.add_element(script)
205
+ end
206
+ end
207
+
208
+ end
@@ -0,0 +1,116 @@
1
+ require 'lapillus/base'
2
+
3
+ module Lapillus
4
+
5
+ class Label < Component
6
+ def render_to_element(element)
7
+ element.text = value
8
+ end
9
+
10
+ def value
11
+ super.to_s
12
+ end
13
+
14
+ def to_s
15
+ result = "#{identifier}:"
16
+ result += " #{value}" if has_model?
17
+ result += " (not visible)" if(!visible?)
18
+ return result
19
+ end
20
+ end
21
+
22
+ def Container.label(id, options={})
23
+ options.keys.each {|key|
24
+ raise "Unknown key: #{key}" if key!=:model and key!=:property
25
+ }
26
+ internal_add_component(id) { Label.new(id, options[:model], options[:property]) }
27
+ end
28
+
29
+ #TODO: Add test!
30
+ class ListItem < Container
31
+ def label(id, options={})
32
+ options.keys.each {|key|
33
+ raise "Unknown key: #{key}" if key!=:model and key!=:property
34
+ }
35
+ add(Label.new(id, options[:model], options[:property]))
36
+ end
37
+ end
38
+
39
+ class MultiLineLabel < Component
40
+ attr_writer :html_content
41
+
42
+ private
43
+ attr_reader :html_content
44
+
45
+ public
46
+ def render_to_element(element)
47
+ element.text = ""
48
+ re = Regexp.new('\n\n+')
49
+ if((value =~ re)!=nil)
50
+ split_paragraphs(value,element,re)
51
+ else
52
+ split_breaks(value,element)
53
+ end
54
+ end
55
+
56
+ def split_paragraphs(text,parent,re)
57
+ paragr_array = text.split(re)
58
+ paragr_array.each { |elem|
59
+ split_breaks(elem,parent.add_element("p"))
60
+ }
61
+ end
62
+
63
+ def split_breaks(text,parent)
64
+ break_array = text.split(/\n/,-1)
65
+ if !break_array.empty?
66
+ add_text(parent, break_array[0])
67
+ break_array[1..-1].each { |elem|
68
+ parent.add_element("br")
69
+ add_text(parent, elem)
70
+ }
71
+ end
72
+ end
73
+
74
+ def add_text(element, text)
75
+ text = REXML::Text.new(text)
76
+ text.raw = html_content
77
+ element.add_text(text)
78
+ end
79
+ end
80
+
81
+ #TODO: root_url could be extracted from application object
82
+ #but the question is how can an image object easily get the application object?
83
+
84
+ class Image < Component
85
+ attr_reader :root_url
86
+
87
+ def initialize(id, model, root_url)
88
+ super(id, model)
89
+ raise "root_url is not set!" if root_url.nil?
90
+ @root_url = root_url
91
+ end
92
+
93
+ def render_to_element(element)
94
+ url = "/images/#{value}"
95
+ url = root_url + url if root_url != "/"
96
+ element.add_attribute("src", url)
97
+ end
98
+ end
99
+
100
+ def Container.image(id, options={})
101
+ options.keys.each {|key|
102
+ raise "Unknown key: #{key}" if key!=:model and key!=:root_url
103
+ }
104
+ internal_add_component(id) { Image.new(id, options[:model], options[:root_url]) }
105
+ end
106
+
107
+ class ListItem < Container
108
+ def image(id, options={})
109
+ options.keys.each {|key|
110
+ raise "Unknown key: #{key}" if key!=:model and key!=:root_url
111
+ }
112
+ add(Image.new(id, options[:model], options[:root_url]))
113
+ end
114
+ end
115
+
116
+ end
@@ -0,0 +1,234 @@
1
+ require 'lapillus/base'
2
+ require 'rexml/document'
3
+
4
+ #TODO: remove in ruby 1.9
5
+ #NOTE: see http://blog.jayfields.com/2006/09/ruby-instanceexec-aka-instanceeval.html
6
+ class Object
7
+ module InstanceExecHelper; end
8
+ include InstanceExecHelper
9
+ def instance_exec(*args, &block)
10
+ begin
11
+ old_critical, Thread.critical = Thread.critical, true
12
+ n = 0
13
+ n += 1 while respond_to?(mname="__instance_exec#{n}")
14
+ InstanceExecHelper.module_eval{ define_method(mname, &block) }
15
+ ensure
16
+ Thread.critical = old_critical
17
+ end
18
+ begin
19
+ ret = send(mname, *args)
20
+ ensure
21
+ InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
22
+ end
23
+ ret
24
+ end
25
+ end
26
+
27
+ module REXML
28
+ class Attribute
29
+ #TODO: what if " occurs inside to_s
30
+ def to_string
31
+ "#@expanded_name=\"#{to_s}\""
32
+ end
33
+ end
34
+ end
35
+
36
+ module Lapillus
37
+ class Webpage < Container
38
+
39
+ def initialize()
40
+ super("")
41
+ end
42
+
43
+ def render(html=default_htmlfile)
44
+ # @html is al File
45
+ # html = @html if !@html.nil?
46
+ doc = REXML::Document.new html
47
+ root_element = doc.root
48
+ new_root_element = render_container(root_element)
49
+ return doc.doctype.to_s + new_root_element.to_s.gsub('&apos;',"'").gsub('&quot;','"')
50
+ end
51
+
52
+ def default_htmlfile
53
+ htmlfile = "html/" + self.class.to_s + ".html"
54
+ File.new( htmlfile )
55
+ end
56
+ end
57
+
58
+ class ListView < Container
59
+ def build_refresh_etc
60
+ raise "value is nil, it could be that the model/property is not set correctly!" if value.nil?
61
+ block = parent.class.block(identifier)
62
+ raise "listview without a block! #{self.path}" if block.nil?
63
+ value.each_with_index { |item, index|
64
+ new_component = ListItem.new(index, item)
65
+ new_component.parent = self #NOTE: I could do this in the constructor!
66
+ new_component.instance_exec(item, &block)
67
+ @components << new_component
68
+ }
69
+ end
70
+ def refresh #TODO write test
71
+ @components = []
72
+ end
73
+ def components
74
+ if @components.empty?
75
+ build_refresh_etc
76
+ end
77
+ @components
78
+ end
79
+ def size #NOTE: move to container?
80
+ return components.size
81
+ end
82
+ #TODO: remove duplication
83
+ def [](index)
84
+ index = index.to_i if index.kind_of?(String)
85
+ return components[index]
86
+ end
87
+ def component(index)
88
+ index = index.to_i if index.kind_of?(String)
89
+ return components[index]
90
+ end
91
+
92
+ #TODO: result is a duplicate with super class method!
93
+ def render_container(listview_element)
94
+ result = REXML::Element.new(listview_element)
95
+ components.each {|container|
96
+ container.render_children(listview_element,result)
97
+ }
98
+ return result
99
+ end
100
+ end
101
+
102
+ class ListItem < Container
103
+ def method_missing(method)
104
+ component(method.to_s)
105
+ end
106
+ end
107
+
108
+ class Container < Component
109
+ def self.listview(id, options={}, &block)
110
+ options.keys.each {|key|
111
+ raise "Unknown key: #{key}" if key!=:model and key!=:property
112
+ }
113
+ raise "no block supplied!" if !block_given?
114
+ internal_add_component(id) { ListView.new(id, options[:model], options[:property]) }
115
+ internal_add_block(id, block)
116
+ end
117
+ end
118
+
119
+ class Form < Container
120
+ def initialize(id, value=nil)
121
+ super(id, value)
122
+ end
123
+ def on_submit_handler=function
124
+ @function=function
125
+ end
126
+ def on_submit(button=nil)
127
+ instance_eval(&@function)
128
+ end
129
+ def render_to_element(element)
130
+ element.add_attribute("method", "post")
131
+ element.attributes['enctype'] = 'multipart/form-data'
132
+ input = element.add_element("input")
133
+ input.attributes['type'] = 'hidden'
134
+ input.attributes['name'] = 'page'
135
+ input.attributes['value'] = webpage.class.to_s
136
+ end
137
+ def post(values)
138
+ components.each {|component| component.post(values)}
139
+ if values.keys.find {|key| key.include?(identifier)}
140
+ on_submit(values['submit'].nil? ? nil : values['submit'].string)
141
+ end
142
+ end
143
+
144
+ end
145
+
146
+ class BookmarkablePageLink < Container
147
+ attr_reader :page_parameters, :page_url
148
+ def initialize(id, page_url, page_parameters)
149
+ super(id, nil)
150
+ @page_url = page_url
151
+ @page_parameters = page_parameters
152
+ end
153
+
154
+ def render_to_element(element)
155
+ element.add_attribute("href", url)
156
+ end
157
+
158
+ def url
159
+ "#{@page_url}/"+page_parameters.sort.collect {|key, value| key.to_s+'/'+value.to_s}.join('/')
160
+ end
161
+ end
162
+
163
+ #TODO: merge constructor of BookmarkableWebpage and the standard Webpage class
164
+ class BookmarkableWebpage < Webpage
165
+ def initialize(page_parameters=Hash[])
166
+ super()
167
+ end
168
+ end
169
+
170
+ class Fragment < Container
171
+ def initialize(id, fragment_id, model=nil)
172
+ super(id, model)
173
+ @fragment_id = fragment_id
174
+ end
175
+
176
+ def render_container(container_element)
177
+ root_node=container_element.root
178
+ fragment = REXML::XPath.first( root_node, "//*[@lapillus:id=\"#{@fragment_id}\"]" )
179
+ raise "Fragment with identifier "+@fragment_id+" not found!" if fragment == nil
180
+ new_element = REXML::Element.new(container_element)
181
+ render_children(fragment, new_element)
182
+ return new_element
183
+ end
184
+ end
185
+
186
+ class Panel < Container
187
+ end
188
+
189
+ class Container
190
+ def self.panel(id, clazz, model=nil, options={})
191
+ add_component(id, clazz, model)
192
+ end
193
+ end
194
+
195
+ class ListItem
196
+ def panel(id, clazz, model=nil, options={})
197
+ add(clazz.new(id, model))
198
+ end
199
+ end
200
+
201
+ #TODO: whats in a name
202
+ class RemotePanel < Container
203
+ def render_container(container_element)
204
+ #NOTE: duplicated in Webpage class above
205
+ doc = REXML::Document.new default_htmlfile
206
+ root_node = doc.root
207
+ fragment = REXML::XPath.first( root_node, "//body")
208
+ raise "Body element not found!" if fragment == nil
209
+ new_element = REXML::Element.new(container_element)
210
+ render_children(fragment, new_element)
211
+ render_to_element(new_element)
212
+ render_behaviours(new_element)
213
+ return new_element
214
+ end
215
+
216
+ #NOTE: duplicate between render and render_container
217
+ def render(html=default_htmlfile)
218
+ # @html is al File
219
+ # html = @html if !@html.nil?
220
+ doc = REXML::Document.new html
221
+ root_element = doc.root
222
+ new_element = REXML::Element.new(root_element)
223
+ render_children(root_element, new_element)
224
+ return doc.doctype.to_s + new_element.to_s
225
+ end
226
+
227
+ #duplicate with methods in webpage!
228
+ def default_htmlfile
229
+ htmlfile = "html/" + self.class.to_s + ".html"
230
+ File.new( htmlfile )
231
+ end
232
+
233
+ end
234
+ end