sws 0.4

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 (158) hide show
  1. data/doc/DOC.otl +34 -0
  2. data/doc/Makefile +13 -0
  3. data/doc/architecture.dia +0 -0
  4. data/doc/docbook/architecture.png +0 -0
  5. data/doc/docbook/concepts.docbook +474 -0
  6. data/doc/docbook/installation.docbook +57 -0
  7. data/doc/docbook/introduction.docbook +130 -0
  8. data/doc/docbook/sws_manual.docbook +35 -0
  9. data/doc/docbook/todo.docbook +38 -0
  10. data/doc/docbook/tutorial.docbook +594 -0
  11. data/examples/README +1 -0
  12. data/examples/addressbook/CardBrowse/CardBrowse.html +43 -0
  13. data/examples/addressbook/CardBrowse/CardBrowse.rb +65 -0
  14. data/examples/addressbook/CardBrowse/CardBrowse.sws +92 -0
  15. data/examples/addressbook/Login/LoginPage.html +12 -0
  16. data/examples/addressbook/Login/LoginPage.rb +19 -0
  17. data/examples/addressbook/Login/LoginPage.sws +15 -0
  18. data/examples/addressbook/README +1 -0
  19. data/examples/addressbook/addressbook.rb +70 -0
  20. data/examples/addressbook/application.yaml +8 -0
  21. data/examples/addressbook/db.yaml +7 -0
  22. data/examples/component_demo/CheckBoxDemo/CheckBoxDemo.html +11 -0
  23. data/examples/component_demo/CheckBoxDemo/CheckBoxDemo.rb +21 -0
  24. data/examples/component_demo/CheckBoxDemo/CheckBoxDemo.sws +25 -0
  25. data/examples/component_demo/ComponentDemo.rb +21 -0
  26. data/examples/component_demo/ConditionalDemo/ConditionalDemo.html +18 -0
  27. data/examples/component_demo/ConditionalDemo/ConditionalDemo.rb +2 -0
  28. data/examples/component_demo/ConditionalDemo/ConditionalDemo.sws +22 -0
  29. data/examples/component_demo/FileUploadDemo/FileUploadDemo.html +10 -0
  30. data/examples/component_demo/FileUploadDemo/FileUploadDemo.rb +9 -0
  31. data/examples/component_demo/FileUploadDemo/FileUploadDemo.sws +33 -0
  32. data/examples/component_demo/FormFieldsDemo/FormFieldsDemo.html +12 -0
  33. data/examples/component_demo/FormFieldsDemo/FormFieldsDemo.rb +21 -0
  34. data/examples/component_demo/FormFieldsDemo/FormFieldsDemo.sws +40 -0
  35. data/examples/component_demo/FormListsDemo/FormListsDemo.html +11 -0
  36. data/examples/component_demo/FormListsDemo/FormListsDemo.rb +37 -0
  37. data/examples/component_demo/FormListsDemo/FormListsDemo.sws +47 -0
  38. data/examples/component_demo/GenericDemo/GenericDemo.html +4 -0
  39. data/examples/component_demo/GenericDemo/GenericDemo.rb +2 -0
  40. data/examples/component_demo/GenericDemo/GenericDemo.sws +10 -0
  41. data/examples/component_demo/HyperlinkDemo/HyperlinkDemo.html +8 -0
  42. data/examples/component_demo/HyperlinkDemo/HyperlinkDemo.rb +20 -0
  43. data/examples/component_demo/HyperlinkDemo/HyperlinkDemo.sws +19 -0
  44. data/examples/component_demo/ImageLinkDemo/ImageLinkDemo.html +11 -0
  45. data/examples/component_demo/ImageLinkDemo/ImageLinkDemo.rb +2 -0
  46. data/examples/component_demo/ImageLinkDemo/ImageLinkDemo.sws +14 -0
  47. data/examples/component_demo/PageWrapper/PageWrapper.html +23 -0
  48. data/examples/component_demo/PageWrapper/PageWrapper.rb +2 -0
  49. data/examples/component_demo/PageWrapper/PageWrapper.sws +42 -0
  50. data/examples/component_demo/README +1 -0
  51. data/examples/component_demo/RepetitionDemo/RepetitionDemo.html +13 -0
  52. data/examples/component_demo/RepetitionDemo/RepetitionDemo.rb +19 -0
  53. data/examples/component_demo/RepetitionDemo/RepetitionDemo.sws +20 -0
  54. data/examples/component_demo/StringDemo/StringDemo.html +5 -0
  55. data/examples/component_demo/StringDemo/StringDemo.rb +16 -0
  56. data/examples/component_demo/StringDemo/StringDemo.sws +14 -0
  57. data/examples/component_demo/application.yaml +28 -0
  58. data/examples/component_demo/poweredby.jpg +0 -0
  59. data/examples/component_demo/style.css +1 -0
  60. data/examples/movies/Menu/Menu.html +3 -0
  61. data/examples/movies/Menu/Menu.rb +7 -0
  62. data/examples/movies/Menu/Menu.sws +7 -0
  63. data/examples/movies/MovieBrowse/MovieBrowse.html +68 -0
  64. data/examples/movies/MovieBrowse/MovieBrowse.rb +178 -0
  65. data/examples/movies/MovieBrowse/MovieBrowse.sws +127 -0
  66. data/examples/movies/README +1 -0
  67. data/examples/movies/UserBrowse/UserBrowse.html +50 -0
  68. data/examples/movies/UserBrowse/UserBrowse.rb +69 -0
  69. data/examples/movies/UserBrowse/UserBrowse.sws +49 -0
  70. data/examples/movies/application.yaml +11 -0
  71. data/examples/movies/da.rb +36 -0
  72. data/examples/movies/dbmovies.rb +44 -0
  73. data/examples/movies/frameworks/TestFramework/framework.yaml +4 -0
  74. data/examples/movies/frameworks/TestFramework/resources/im1.jpg +0 -0
  75. data/examples/movies/images/pbr1b.jpg +0 -0
  76. data/examples/movies/movies.rb +28 -0
  77. data/examples/movies/movies.sds +119 -0
  78. data/examples/movies/movies.sqlite +0 -0
  79. data/examples/movies/movies_mysql.sql +28 -0
  80. data/examples/movies/movies_postgres.sql +27 -0
  81. data/examples/movies/movies_sqlite.sql +28 -0
  82. data/lib/sws.rb +89 -0
  83. data/lib/sws/Core/components/CheckBox/CheckBox.api +5 -0
  84. data/lib/sws/Core/components/CheckBox/CheckBox.rb +45 -0
  85. data/lib/sws/Core/components/CheckBoxList/CheckBoxList.api +13 -0
  86. data/lib/sws/Core/components/CheckBoxList/CheckBoxList.rb +54 -0
  87. data/lib/sws/Core/components/Conditional/Conditional.api +3 -0
  88. data/lib/sws/Core/components/Conditional/Conditional.html +1 -0
  89. data/lib/sws/Core/components/Conditional/Conditional.rb +39 -0
  90. data/lib/sws/Core/components/Conditional/Conditional.sws +2 -0
  91. data/lib/sws/Core/components/Content/Content.rb +18 -0
  92. data/lib/sws/Core/components/ExceptionPage/ExceptionPage.html +13 -0
  93. data/lib/sws/Core/components/ExceptionPage/ExceptionPage.rb +18 -0
  94. data/lib/sws/Core/components/ExceptionPage/ExceptionPage.sws +16 -0
  95. data/lib/sws/Core/components/FileUpload/FileUpload.api +16 -0
  96. data/lib/sws/Core/components/FileUpload/FileUpload.rb +62 -0
  97. data/lib/sws/Core/components/Form/Form.api +9 -0
  98. data/lib/sws/Core/components/Form/Form.html +3 -0
  99. data/lib/sws/Core/components/Form/Form.rb +55 -0
  100. data/lib/sws/Core/components/Form/Form.sws +12 -0
  101. data/lib/sws/Core/components/GenericContainer/GenericContainer.api +10 -0
  102. data/lib/sws/Core/components/GenericContainer/GenericContainer.html +1 -0
  103. data/lib/sws/Core/components/GenericContainer/GenericContainer.rb +39 -0
  104. data/lib/sws/Core/components/GenericContainer/GenericContainer.sws +12 -0
  105. data/lib/sws/Core/components/GenericElement/GenericElement.api +10 -0
  106. data/lib/sws/Core/components/GenericElement/GenericElement.rb +34 -0
  107. data/lib/sws/Core/components/HiddenField/HiddenField.api +7 -0
  108. data/lib/sws/Core/components/HiddenField/HiddenField.rb +37 -0
  109. data/lib/sws/Core/components/Hyperlink/Hyperlink.api +13 -0
  110. data/lib/sws/Core/components/Hyperlink/Hyperlink.html +1 -0
  111. data/lib/sws/Core/components/Hyperlink/Hyperlink.rb +102 -0
  112. data/lib/sws/Core/components/Hyperlink/Hyperlink.sws +12 -0
  113. data/lib/sws/Core/components/Image/Image.api +11 -0
  114. data/lib/sws/Core/components/Image/Image.rb +49 -0
  115. data/lib/sws/Core/components/ImageButton/ImageButton.api +16 -0
  116. data/lib/sws/Core/components/ImageButton/ImageButton.rb +76 -0
  117. data/lib/sws/Core/components/Link/Link.api +11 -0
  118. data/lib/sws/Core/components/Link/Link.rb +39 -0
  119. data/lib/sws/Core/components/PasswordField/PasswordField.api +7 -0
  120. data/lib/sws/Core/components/PasswordField/PasswordField.rb +41 -0
  121. data/lib/sws/Core/components/RadioButton/RadioButton.api +7 -0
  122. data/lib/sws/Core/components/RadioButton/RadioButton.rb +44 -0
  123. data/lib/sws/Core/components/RadioButtonList/RadioButtonList.api +20 -0
  124. data/lib/sws/Core/components/RadioButtonList/RadioButtonList.rb +76 -0
  125. data/lib/sws/Core/components/Repetition/Repetition.api +10 -0
  126. data/lib/sws/Core/components/Repetition/Repetition.html +1 -0
  127. data/lib/sws/Core/components/Repetition/Repetition.rb +137 -0
  128. data/lib/sws/Core/components/Repetition/Repetition.sws +2 -0
  129. data/lib/sws/Core/components/ResetButton/ResetButton.api +5 -0
  130. data/lib/sws/Core/components/ResetButton/ResetButton.rb +28 -0
  131. data/lib/sws/Core/components/Select/Select.api +24 -0
  132. data/lib/sws/Core/components/Select/Select.rb +57 -0
  133. data/lib/sws/Core/components/String/String.api +5 -0
  134. data/lib/sws/Core/components/String/String.rb +28 -0
  135. data/lib/sws/Core/components/SubmitButton/SubmitButton.api +6 -0
  136. data/lib/sws/Core/components/SubmitButton/SubmitButton.rb +53 -0
  137. data/lib/sws/Core/components/TextArea/TextArea.api +9 -0
  138. data/lib/sws/Core/components/TextArea/TextArea.rb +28 -0
  139. data/lib/sws/Core/components/TextField/TextField.api +9 -0
  140. data/lib/sws/Core/components/TextField/TextField.rb +46 -0
  141. data/lib/sws/Core/framework.yaml +25 -0
  142. data/lib/sws/JSComponents/components/JSMenu/JSMenu.api +5 -0
  143. data/lib/sws/JSComponents/components/JSMenu/JSMenu.html +58 -0
  144. data/lib/sws/JSComponents/components/JSMenu/JSMenu.rb +34 -0
  145. data/lib/sws/JSComponents/components/JSMenu/JSMenu.sws +37 -0
  146. data/lib/sws/JSComponents/framework.yaml +3 -0
  147. data/lib/sws/adaptor.rb +334 -0
  148. data/lib/sws/application.rb +604 -0
  149. data/lib/sws/component.rb +656 -0
  150. data/lib/sws/cookie.rb +27 -0
  151. data/lib/sws/direct_action.rb +38 -0
  152. data/lib/sws/extensions.rb +49 -0
  153. data/lib/sws/parsers.rb +374 -0
  154. data/lib/sws/request.rb +308 -0
  155. data/lib/sws/response.rb +70 -0
  156. data/lib/sws/session.rb +195 -0
  157. data/lib/sws/slot.rb +198 -0
  158. metadata +263 -0
@@ -0,0 +1,656 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module SWS
4
+
5
+
6
+ # Represents a HTML page or its part. Probably most important class of SWS -
7
+ # it is used to build pages and to create reusable parts. It consists of a few
8
+ # files, which define the component object - Ruby class file (.rb), HTML
9
+ # template (.html), slot definition file (.api) and bindings file (.sws).
10
+ class Component
11
+
12
+ # Request object the receiver is handling
13
+ attr_reader :request
14
+
15
+ # The Component up the hierarchy, eg. for SubmitButton it can be Form. For
16
+ # the topmost component see #page
17
+ attr_reader :parent
18
+
19
+ # Array of components contained within the receiver. Opposite to #parent
20
+ attr_reader :subcomponents
21
+
22
+ # Array of Slot objects defined for the receiver
23
+ attr_accessor :slots
24
+
25
+ # Method of the receiver that will be called during #perform_action phase
26
+ attr_accessor :method_to_call
27
+
28
+ # Component that defines bindings for the receiver. Usually parent, but not
29
+ # always.
30
+ attr_accessor :definition_component
31
+
32
+ # Name of the receiver
33
+ attr_accessor :name
34
+
35
+ # The Request (HTTP) parameters that conform to the receiver
36
+ attr_accessor :parameters
37
+
38
+ # HTML tag attributes
39
+ attr_accessor :html_attrs
40
+
41
+ # Action subcomponents for page
42
+ attr_reader :action_components
43
+
44
+ # Number of requests processed by this component - necessary when handling
45
+ # backtracking
46
+ attr_reader :request_number
47
+
48
+ # Character encoding - defaults to Application.default_encoding
49
+ attr_accessor :encoding
50
+
51
+ attr_accessor :tokens
52
+ # Cache for component filenames
53
+ @@component_infos = Hash.new do |hash,component|
54
+ hash[component] = Application.instance.get_component( component )
55
+ end
56
+
57
+ #List of slots synchronized with instance variables
58
+ @@synchronized_slots = Hash.new do |hash,component|
59
+ hash[component] = Array.new
60
+ end
61
+
62
+ # Create new Component. If you want to create new page (toplevel
63
+ # component), the only argument is a Request object. If you want to create
64
+ # another component (which you shouldn't need to), the arguments are:
65
+ # named of the component, Request object, parent component and slots Hash
66
+ def initialize ( request, name, parent, slots )
67
+
68
+ #use this if you are creating new page (no slots, parent or name)...
69
+ # TODO: create child class Page
70
+ if ( parent == nil)
71
+ initialize_page( request )
72
+ #...and this when creating new component (but in such case Component.create is probably better)
73
+ else
74
+ initialize_component( request, name, parent, slots )
75
+ end
76
+ #common initialization for both "constructors"
77
+ initialize_common()
78
+
79
+ end
80
+
81
+
82
+ protected
83
+ def initialize_common ()
84
+
85
+ @parameters = Hash.new
86
+ #TODO: reconsider if necessary - is initialized in remove_subcomponents anyway
87
+ #(which is always called when parsing component definition)
88
+ @subcomponents = Array.new
89
+
90
+ end
91
+
92
+
93
+ def initialize_page ( request = nil )
94
+
95
+ @request = request
96
+ @request_number = 0
97
+ @action_components = Hash.new
98
+ @request_subcomponents = Array.new
99
+ @encoding = app().default_encoding
100
+
101
+ end
102
+
103
+
104
+ def initialize_component ( request, name, parent, slots )
105
+
106
+ @request = request
107
+ @slots = slots
108
+ @parent = parent
109
+ @name = name
110
+
111
+ end
112
+
113
+
114
+ public
115
+
116
+ # Defines a slot (symbol or name) that should be synchronized with instance
117
+ # variable of the same name
118
+ def Component.synchronize_slot ( *args )
119
+ args.each { |slot_name| @@synchronized_slots[self.to_s] << slot_name.to_s }
120
+ end
121
+
122
+ # Returns the name of the instance variable the slot should be synchronized
123
+ # with or nil if it should not be synchronized
124
+ def synchronize_slot? ( slot_name )
125
+ return @@synchronized_slots[self.class.to_s].include?( slot_name.to_s )
126
+ end
127
+
128
+ # Synchronizes variable with slots
129
+ def synchronize_slots ()
130
+
131
+ @@synchronized_slots[self.class.to_s].each do |slot_name|
132
+ slot = @slots[slot_name]
133
+ unless slot
134
+ raise NameError.new( "Synchronized slot '#{slot_name}' not found in #{self}" )
135
+ else
136
+ # Slots are synchronized in Slot.value, so it's enough to call it
137
+ slot.value
138
+ end
139
+ end
140
+
141
+ end
142
+
143
+ # Returns filename for HTML template of this component
144
+ def template_filename ()
145
+ return @@component_infos[self.class.to_s].template
146
+ end
147
+
148
+
149
+ # Returns filename for component definition (.sws file)
150
+ def definition_filename ()
151
+ return @@component_infos[self.class.to_s].definition
152
+ end
153
+
154
+
155
+ # Return filename for slot definition (.api file)
156
+ def api_filename ()
157
+ return @@component_infos[self.class.to_s].api
158
+ end
159
+
160
+
161
+ # Shortcut for Application instance
162
+ def app ()
163
+ return Application.instance
164
+ end
165
+
166
+
167
+ # Session object accessor
168
+ def session ()
169
+ return @request.session
170
+ end
171
+
172
+
173
+ # Returns most top-level component
174
+ def page ()
175
+
176
+ return self if ( @parent == nil )
177
+
178
+ component = @parent
179
+ while ( component.parent != nil )
180
+ component = component.parent
181
+ end
182
+ return component
183
+
184
+ end
185
+
186
+
187
+ def set_request_subcomponents ()
188
+ $log_sws_component.debug( "#{self.class.name} #{object_id}: Storing subcomponents for request #{@request_number}" )
189
+ @request_subcomponents [@request_number] = @subcomponents
190
+ end
191
+
192
+ # A string to be added to URL to indicate this component will handle the
193
+ # request
194
+ def url_string
195
+ return "#{object_id}.#{@request_number}"
196
+ end
197
+
198
+ # Return new component of that name for given request. Can be used instead
199
+ # of constructor if you have class_name for the component as string and
200
+ # not the Class object itself - this method is used for components in order
201
+ # to require them dynamically.
202
+ def Component.create ( class_name, request, name = nil, parent = nil, slots = Hash.new )
203
+
204
+ klass = @@component_infos[class_name].component_class
205
+
206
+ # TODO: why was this here?
207
+ #name = class_name if ( name == nil )
208
+ component = klass.new( request, name, parent, slots )
209
+ return component
210
+
211
+ end
212
+
213
+
214
+ # Clears subcomponents array
215
+ def remove_subcomponents ()
216
+ @subcomponents.clear()
217
+ end
218
+
219
+ # Main component procedure - performs all phases of request handling.
220
+ # Returns component object which will process next request
221
+ def process_request ( request, request_number = nil )
222
+
223
+ # TODO: check here if backtracked and omit some initial phases if so
224
+ @request = request
225
+ $log_sws_component.debug( "Request number in process_request: #{request_number}" )
226
+ request_number ||= @request_number
227
+ $log_sws_component.debug( "#{self.class.name} #{object_id}: Subcomponents for request #{request_number}" )
228
+ @subcomponents = @request_subcomponents[request_number] || Array.new
229
+ unless request_number == @request_number
230
+ $log_sws_component.debug( "---------BACKTRACK from #{@request_number} to #{request_number}" )
231
+ end
232
+ @request_number += 1
233
+ $log_sws_component.debug( "#{self.class.name} #{object_id}: request number is now #{@request_number}" )
234
+ awake()
235
+ process_parameters()
236
+ new_component = perform_action() || self
237
+
238
+ # If below is true variable new_component is used as response.
239
+ # It replaces standard return [SWS::Component, SWS::Response]
240
+ # with [SWS::Response, nil].
241
+ if( new_component.instance_of?( SWS::Response ) )
242
+ return new_component, nil
243
+ end
244
+
245
+ response = Response.new( request )
246
+ @subcomponents = Array.new
247
+ new_component.create_component_tree()
248
+ new_component.set_request_subcomponents()
249
+ new_component.append_to_response( response )
250
+
251
+ response.cookies << @request.session.to_cookie
252
+ response.headers["Content-type"] = "text/html;charset=#{@encoding}"
253
+ #response.headers["Pragma"] = "no-cache"
254
+ #response.headers["Expires"] = "0"
255
+ #response.headers["Cache-control"] = "private, no-cache, no-store, must-revalidate, max-age = 0"
256
+
257
+ sleep()
258
+ return new_component,response
259
+
260
+ end
261
+
262
+
263
+ # A generic method used for per-request initialization of the component.
264
+ # Called at the very begining of response process handling . Default
265
+ # implementation does recursively nothing :) - that is, it calls the
266
+ # awake() method for all subcomponents and their subcomponents and
267
+ # their...
268
+ def awake ()
269
+ @subcomponents.each { |com| com.awake() }
270
+ end
271
+
272
+
273
+ # A generic method used for per-request deinitialization of the component.
274
+ # Called at the very end of handling response process. Default
275
+ # implementation does recursively nothing :) - that is, it calls the
276
+ # sleep() method for all subcomponents and their subcomponents and
277
+ # their...
278
+ def sleep ()
279
+ @subcomponents.each { |com| com.sleep() }
280
+ end
281
+
282
+
283
+ # Takes values from request (HTTP parameters) and binds them to slots.
284
+ # Component to bind to is determined by splitting the parameter name. Each
285
+ # parameter is passed to #tokenize_binding( key, value ) method. After
286
+ # processing the parameters #process_bindings() is called.
287
+ def process_parameters ()
288
+
289
+ @request.params.each do |key,value|
290
+ stripped_key = key.sub( /^[^.]*?\./,"" )
291
+ $log_sws_component.debug( "Received key #{key}, stripped #{stripped_key} with value #{value}" )
292
+ tokenize_binding( stripped_key, value )
293
+ end
294
+ process_bindings()
295
+
296
+ end
297
+
298
+
299
+ # Takes parameters and binds them to slots - recursively for
300
+ # subcomponents. Default implementation (overriden in several Component
301
+ # subclasses) just calls #update_binding( key,value )
302
+ def process_bindings ()
303
+
304
+ @subcomponents.each do |subcomponent|
305
+ subcomponent.process_bindings()
306
+ end
307
+ @parameters.each { |key,value| update_binding( key,value ) }
308
+
309
+ end
310
+
311
+
312
+ # Updates @parameters Hash or calls the same method recursively on proper
313
+ # subcomponent (the name of which is determined by the parameter name to
314
+ # the first dot).
315
+ def tokenize_binding ( key,value )
316
+
317
+ if ( md = /^([^.]*?)\.(.*)$/.match( key ) ) #contains dot - extract component name and rest of binding
318
+ #TODO: what if the same component is present twice in template?
319
+ #but probably using form elements twice is not a good idea :)
320
+ component = subcomponent_for_name( md[1] )
321
+ if ( component == nil )
322
+ $log_sws_component.debug( "Ignoring key #{key}, component #{self}" )
323
+ else
324
+ component.tokenize_binding( md[2],value )
325
+ end
326
+ else #no dot - only slot name left - parameter belongs to self
327
+ @parameters[key] = value
328
+ end
329
+
330
+ end
331
+
332
+
333
+ # Updates binding described by key to value. Default implementation
334
+ # (often overriden in specific Component subclasses to perform custom
335
+ # initialization) just updates proper Slot value
336
+ def update_binding ( key, value )
337
+
338
+ slot = @slots[key]
339
+ $log_sws_component.debug( "slot #{key}: binding #{slot}, component: #{self}" )
340
+ if slot
341
+ slot.value = value
342
+ else
343
+ # This happens in cases like submitting INPUT type=image in Firefox,
344
+ # which (besides usual name.x and name.y parameters) additionaly submits
345
+ # "name" parameter (without any suffix) :(. So in order to avoid
346
+ # exception we need to ignore the parameter. TODO: refactor the whole
347
+ # parameter processing logic, as this workaround introduces some
348
+ # overhead.
349
+ subcomponent = subcomponent_for_name( key )
350
+ if subcomponent
351
+ $log_sws_component.debug( "Ignoring parameter #{key} with value #{value} as it refers directly to subcomponent of component #{self}" )
352
+ else
353
+ $log_sws_component.debug( "Ignoring bogus parameter #{key} with value #{value} for component #{self}" )
354
+ end
355
+ end
356
+
357
+ end
358
+
359
+
360
+ # Returns subcomponent of the receiver for given name, or nil if not found
361
+ def subcomponent_for_name ( name )
362
+ return @subcomponents.find { |subcomponent| subcomponent.name == name }
363
+ end
364
+
365
+
366
+ # Performs the selected action recursively on all subcomponents. This is
367
+ # the phase of response handling process where the topmost component can
368
+ # change - it is changed to the return value of first action that returns
369
+ # other value than nil
370
+ def perform_action ()
371
+
372
+ next_page = nil
373
+ @subcomponents.each do |subcomponent|
374
+ next_page = subcomponent.perform_action()
375
+ $log_sws_component.debug( "PERFORM: #{next_page} for component #{subcomponent}" )
376
+ return next_page if ( next_page )
377
+ end
378
+ return next_page
379
+
380
+ end
381
+
382
+
383
+ # Recursively generates HTML code for all subcomponents and adds it to the
384
+ # Response object
385
+ def append_to_response ( response )
386
+
387
+ @subcomponents.each do |child|
388
+ child.append_to_response( response )
389
+ end
390
+
391
+ end
392
+
393
+
394
+ # Parses template file for the receiver and uses generated token tree to
395
+ # create component tree. Is called even if the same component is still used
396
+ # after #perform_action() (but in this case it has old subcomponents removed
397
+ # before)
398
+ def create_component_tree ()
399
+
400
+ $log_sws_component.debug( "Creating component tree for #{self}" )
401
+
402
+ if ( content? ) # If the receiver is Content, expand content tokens
403
+ definition_component.tokens.each { |token| @parent.expand_token( token ) }
404
+ else
405
+ template_parser = TemplateParser.new( template_filename() )
406
+ root_tokens = template_parser.parse()
407
+ root_tokens.each { |token| expand_token( token ) }
408
+ end
409
+
410
+ end
411
+
412
+
413
+ protected
414
+
415
+ # Strongly coupled with #create_component_tree - expands a token: determines
416
+ # its type and creates coresponding component object
417
+ def expand_token ( token )
418
+
419
+ $log_sws_component.debug( "expanding #{token}, #{token.text?}, #{token.data.nil?}" )
420
+
421
+ if ( token.comment? )
422
+
423
+ $log_sws_component.debug( "Comment: #{token.data}" )
424
+ # TODO: comments should be appended conditionally
425
+ append_text_component( token.data )
426
+
427
+ elsif ( token.text? )
428
+
429
+ $log_sws_component.debug( "Text: #{token.data}" )
430
+ append_text_component( token.data )
431
+
432
+ elsif ( token.tag? )
433
+
434
+ if ( token.standalone? )
435
+ append_text_component( token.data )
436
+ else
437
+ $log_sws_component.debug( "Tag - container" )
438
+ append_text_component( token.data ) # opening tag
439
+ token.children.each { |child| expand_token( child ) }
440
+ append_text_component( "</#{token.name}>" ) # closing tag
441
+ end
442
+
443
+ elsif ( token.sws? )
444
+
445
+ child_component = DefinitionParser.instance.component_for_token_with_parent( token,self )
446
+ $log_sws_component.debug( "Added child #{child_component.name} for component #{self}" )
447
+ if ( child_component.container? )
448
+ $log_sws_component.debug( "found container component: #{token.children}" )
449
+ child_component.tokens = token.children
450
+ child_component.create_component_tree()
451
+ elsif ( child_component.content? )
452
+ $log_sws_component.debug( "found content component" )
453
+ child_component.create_component_tree()
454
+ end
455
+ @subcomponents << child_component
456
+
457
+ end
458
+
459
+ $log_sws_component.debug( "closing #{token}, #{token.text?}" )
460
+
461
+ end
462
+
463
+
464
+ # Appends a TextComponent to the component tree
465
+ def append_text_component ( data )
466
+
467
+ unless ( data )
468
+ raise( "NULL text component!!!!!!" )
469
+ end
470
+
471
+ child_component = TextComponent.new( @request,nil,self,nil )
472
+ child_component.content = data
473
+ @subcomponents << child_component
474
+
475
+ end
476
+
477
+
478
+ # Returns "other_tag_string" and html_attrs in form of tag attributes
479
+ def generic_attr_string ()
480
+
481
+ result = ""
482
+ other_tag_slot = @slots["other_tag_string"]
483
+ if ( other_tag_slot && other_tag_slot.bound_string )
484
+ result << " #{other_tag_slot.value()}"
485
+ end
486
+ if ( @html_attrs )
487
+ @html_attrs.each_pair { |name,value| result << " #{name}=\"#{value}\"" }
488
+ end
489
+ return result
490
+
491
+ end
492
+
493
+
494
+ # Returns HTML tag attribute string for those parameters, which are the
495
+ # names of bound slots
496
+ def bound_slot_string ( *args )
497
+
498
+ bound_slots = args.find_all { |slot| slot_bound?( slot ) }
499
+ return bound_slots.empty? ? "" : " " + bound_slots.collect { |slot| @slots[slot].to_tag_attribute }.join( " " )
500
+
501
+ end
502
+
503
+ public
504
+
505
+ # Returns true if Slot for given name is bound.
506
+ def slot_bound? ( slot_name )
507
+ return @slots[slot_name].bound_string != nil
508
+ end
509
+
510
+
511
+ # Returns true if the receiver is a container component - only containers
512
+ # can contain a content.
513
+ def container? ()
514
+ return true
515
+ end
516
+
517
+
518
+ # Returns true if the component is content - it will be replaced by the
519
+ # content of the parent component. Currently only SWS::Component::Content
520
+ # return true here.
521
+ def content? ()
522
+ return false
523
+ end
524
+
525
+ end
526
+
527
+
528
+ # Represents pure text - component representation of text token. Used to
529
+ # append text tokens from parsed tree to the Response object
530
+ class TextComponent < Component
531
+
532
+ # HTML content
533
+ attr_accessor :content
534
+
535
+
536
+ # Appends HTML content to the Response
537
+ def append_to_response ( response )
538
+ response << @content
539
+ end
540
+
541
+ end
542
+
543
+
544
+ # Base class for all components being the part of a form
545
+ class FormElement < Component
546
+
547
+
548
+ # Returns base name attribute of HTML tag created from names of all
549
+ # parents separated by '.'. Used for creating HTTP parameters containing
550
+ # values entered by user.
551
+ def element_name ()
552
+
553
+ name = @name
554
+ component = @parent
555
+ while ( component != nil )
556
+ name = "#{component.name}.#{name}"
557
+ component = component.parent
558
+ end
559
+ return name
560
+
561
+ end
562
+
563
+
564
+ # All form parts are not container components - they do not contains other
565
+ # components or HTML content
566
+ def container? ()
567
+ return false
568
+ end
569
+
570
+ end
571
+
572
+
573
+ # Base class for RadioButtonList and MultipleSelectionList
574
+ class ListElement < FormElement
575
+
576
+
577
+ # Returns value of "list" slot
578
+ def list ()
579
+ return @slots["list"].value()
580
+ end
581
+
582
+
583
+ # Returns value of "item" slot
584
+ def item ()
585
+ return @slots["item"].value()
586
+ end
587
+
588
+
589
+ def append_to_response ( response )
590
+ response << generate_html()
591
+ end
592
+
593
+ end
594
+
595
+
596
+ # Base class for lists with multiple selections allowed: CheckBoxList and Select
597
+ class MultipleSelectionList < ListElement
598
+
599
+
600
+ # Returns value of "selections" slot or empty Array instead of nil
601
+ def selections ()
602
+ @selections ||= (@slots["selections"].value() || Array.new )
603
+ return @selections
604
+ end
605
+
606
+
607
+ # Sets the value of the "selections" slot
608
+ def selections= ( selections )
609
+
610
+ if ( selections == nil )
611
+
612
+ @slots["selections"].value = []
613
+
614
+ elsif ( selections.is_a?( Array ) )
615
+
616
+ #in the selections array we have indices of elements in "list" array
617
+ selected = selections.collect { |sel| list[sel.to_i] }
618
+ @slots["selections"].value = selected
619
+
620
+ else #single value - create an Array containing it
621
+
622
+ if ( selections == "__empty_string" )
623
+ @slots["selections"].value = nil
624
+ else
625
+ @slots["selections"].value = [ list[selections.to_i] ]
626
+ end
627
+
628
+ end
629
+
630
+ end
631
+
632
+
633
+ # #if ( selections == nil ) #nothing is selected - return empty array
634
+ # #@slots["selections"].value = Array.new()
635
+ # #else
636
+ # #WARNING: selections will be an Array if multiple and a single object if not
637
+ # selected = Array.new()
638
+ # #if we have single selection, make it an Array
639
+ # selections = [selections] unless ( selections.is_a? (Array) )
640
+ # #in the selections array we have indices of elements in "list" array
641
+ # selections.each { |sel| selected << list[sel.to_i] }
642
+ # @slots["selections"].value = selected
643
+ # end
644
+ # end
645
+ def process_bindings ()
646
+ if ( @parameters["selections"] )
647
+ self.selections = @parameters["selections"]
648
+ else
649
+ self.selections = nil
650
+ end
651
+ end
652
+
653
+ end
654
+
655
+
656
+ end