sws 0.4

Sign up to get free protection for your applications and to get access to all the features.
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