sws 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/DOC.otl +34 -0
- data/doc/Makefile +13 -0
- data/doc/architecture.dia +0 -0
- data/doc/docbook/architecture.png +0 -0
- data/doc/docbook/concepts.docbook +474 -0
- data/doc/docbook/installation.docbook +57 -0
- data/doc/docbook/introduction.docbook +130 -0
- data/doc/docbook/sws_manual.docbook +35 -0
- data/doc/docbook/todo.docbook +38 -0
- data/doc/docbook/tutorial.docbook +594 -0
- data/examples/README +1 -0
- data/examples/addressbook/CardBrowse/CardBrowse.html +43 -0
- data/examples/addressbook/CardBrowse/CardBrowse.rb +65 -0
- data/examples/addressbook/CardBrowse/CardBrowse.sws +92 -0
- data/examples/addressbook/Login/LoginPage.html +12 -0
- data/examples/addressbook/Login/LoginPage.rb +19 -0
- data/examples/addressbook/Login/LoginPage.sws +15 -0
- data/examples/addressbook/README +1 -0
- data/examples/addressbook/addressbook.rb +70 -0
- data/examples/addressbook/application.yaml +8 -0
- data/examples/addressbook/db.yaml +7 -0
- data/examples/component_demo/CheckBoxDemo/CheckBoxDemo.html +11 -0
- data/examples/component_demo/CheckBoxDemo/CheckBoxDemo.rb +21 -0
- data/examples/component_demo/CheckBoxDemo/CheckBoxDemo.sws +25 -0
- data/examples/component_demo/ComponentDemo.rb +21 -0
- data/examples/component_demo/ConditionalDemo/ConditionalDemo.html +18 -0
- data/examples/component_demo/ConditionalDemo/ConditionalDemo.rb +2 -0
- data/examples/component_demo/ConditionalDemo/ConditionalDemo.sws +22 -0
- data/examples/component_demo/FileUploadDemo/FileUploadDemo.html +10 -0
- data/examples/component_demo/FileUploadDemo/FileUploadDemo.rb +9 -0
- data/examples/component_demo/FileUploadDemo/FileUploadDemo.sws +33 -0
- data/examples/component_demo/FormFieldsDemo/FormFieldsDemo.html +12 -0
- data/examples/component_demo/FormFieldsDemo/FormFieldsDemo.rb +21 -0
- data/examples/component_demo/FormFieldsDemo/FormFieldsDemo.sws +40 -0
- data/examples/component_demo/FormListsDemo/FormListsDemo.html +11 -0
- data/examples/component_demo/FormListsDemo/FormListsDemo.rb +37 -0
- data/examples/component_demo/FormListsDemo/FormListsDemo.sws +47 -0
- data/examples/component_demo/GenericDemo/GenericDemo.html +4 -0
- data/examples/component_demo/GenericDemo/GenericDemo.rb +2 -0
- data/examples/component_demo/GenericDemo/GenericDemo.sws +10 -0
- data/examples/component_demo/HyperlinkDemo/HyperlinkDemo.html +8 -0
- data/examples/component_demo/HyperlinkDemo/HyperlinkDemo.rb +20 -0
- data/examples/component_demo/HyperlinkDemo/HyperlinkDemo.sws +19 -0
- data/examples/component_demo/ImageLinkDemo/ImageLinkDemo.html +11 -0
- data/examples/component_demo/ImageLinkDemo/ImageLinkDemo.rb +2 -0
- data/examples/component_demo/ImageLinkDemo/ImageLinkDemo.sws +14 -0
- data/examples/component_demo/PageWrapper/PageWrapper.html +23 -0
- data/examples/component_demo/PageWrapper/PageWrapper.rb +2 -0
- data/examples/component_demo/PageWrapper/PageWrapper.sws +42 -0
- data/examples/component_demo/README +1 -0
- data/examples/component_demo/RepetitionDemo/RepetitionDemo.html +13 -0
- data/examples/component_demo/RepetitionDemo/RepetitionDemo.rb +19 -0
- data/examples/component_demo/RepetitionDemo/RepetitionDemo.sws +20 -0
- data/examples/component_demo/StringDemo/StringDemo.html +5 -0
- data/examples/component_demo/StringDemo/StringDemo.rb +16 -0
- data/examples/component_demo/StringDemo/StringDemo.sws +14 -0
- data/examples/component_demo/application.yaml +28 -0
- data/examples/component_demo/poweredby.jpg +0 -0
- data/examples/component_demo/style.css +1 -0
- data/examples/movies/Menu/Menu.html +3 -0
- data/examples/movies/Menu/Menu.rb +7 -0
- data/examples/movies/Menu/Menu.sws +7 -0
- data/examples/movies/MovieBrowse/MovieBrowse.html +68 -0
- data/examples/movies/MovieBrowse/MovieBrowse.rb +178 -0
- data/examples/movies/MovieBrowse/MovieBrowse.sws +127 -0
- data/examples/movies/README +1 -0
- data/examples/movies/UserBrowse/UserBrowse.html +50 -0
- data/examples/movies/UserBrowse/UserBrowse.rb +69 -0
- data/examples/movies/UserBrowse/UserBrowse.sws +49 -0
- data/examples/movies/application.yaml +11 -0
- data/examples/movies/da.rb +36 -0
- data/examples/movies/dbmovies.rb +44 -0
- data/examples/movies/frameworks/TestFramework/framework.yaml +4 -0
- data/examples/movies/frameworks/TestFramework/resources/im1.jpg +0 -0
- data/examples/movies/images/pbr1b.jpg +0 -0
- data/examples/movies/movies.rb +28 -0
- data/examples/movies/movies.sds +119 -0
- data/examples/movies/movies.sqlite +0 -0
- data/examples/movies/movies_mysql.sql +28 -0
- data/examples/movies/movies_postgres.sql +27 -0
- data/examples/movies/movies_sqlite.sql +28 -0
- data/lib/sws.rb +89 -0
- data/lib/sws/Core/components/CheckBox/CheckBox.api +5 -0
- data/lib/sws/Core/components/CheckBox/CheckBox.rb +45 -0
- data/lib/sws/Core/components/CheckBoxList/CheckBoxList.api +13 -0
- data/lib/sws/Core/components/CheckBoxList/CheckBoxList.rb +54 -0
- data/lib/sws/Core/components/Conditional/Conditional.api +3 -0
- data/lib/sws/Core/components/Conditional/Conditional.html +1 -0
- data/lib/sws/Core/components/Conditional/Conditional.rb +39 -0
- data/lib/sws/Core/components/Conditional/Conditional.sws +2 -0
- data/lib/sws/Core/components/Content/Content.rb +18 -0
- data/lib/sws/Core/components/ExceptionPage/ExceptionPage.html +13 -0
- data/lib/sws/Core/components/ExceptionPage/ExceptionPage.rb +18 -0
- data/lib/sws/Core/components/ExceptionPage/ExceptionPage.sws +16 -0
- data/lib/sws/Core/components/FileUpload/FileUpload.api +16 -0
- data/lib/sws/Core/components/FileUpload/FileUpload.rb +62 -0
- data/lib/sws/Core/components/Form/Form.api +9 -0
- data/lib/sws/Core/components/Form/Form.html +3 -0
- data/lib/sws/Core/components/Form/Form.rb +55 -0
- data/lib/sws/Core/components/Form/Form.sws +12 -0
- data/lib/sws/Core/components/GenericContainer/GenericContainer.api +10 -0
- data/lib/sws/Core/components/GenericContainer/GenericContainer.html +1 -0
- data/lib/sws/Core/components/GenericContainer/GenericContainer.rb +39 -0
- data/lib/sws/Core/components/GenericContainer/GenericContainer.sws +12 -0
- data/lib/sws/Core/components/GenericElement/GenericElement.api +10 -0
- data/lib/sws/Core/components/GenericElement/GenericElement.rb +34 -0
- data/lib/sws/Core/components/HiddenField/HiddenField.api +7 -0
- data/lib/sws/Core/components/HiddenField/HiddenField.rb +37 -0
- data/lib/sws/Core/components/Hyperlink/Hyperlink.api +13 -0
- data/lib/sws/Core/components/Hyperlink/Hyperlink.html +1 -0
- data/lib/sws/Core/components/Hyperlink/Hyperlink.rb +102 -0
- data/lib/sws/Core/components/Hyperlink/Hyperlink.sws +12 -0
- data/lib/sws/Core/components/Image/Image.api +11 -0
- data/lib/sws/Core/components/Image/Image.rb +49 -0
- data/lib/sws/Core/components/ImageButton/ImageButton.api +16 -0
- data/lib/sws/Core/components/ImageButton/ImageButton.rb +76 -0
- data/lib/sws/Core/components/Link/Link.api +11 -0
- data/lib/sws/Core/components/Link/Link.rb +39 -0
- data/lib/sws/Core/components/PasswordField/PasswordField.api +7 -0
- data/lib/sws/Core/components/PasswordField/PasswordField.rb +41 -0
- data/lib/sws/Core/components/RadioButton/RadioButton.api +7 -0
- data/lib/sws/Core/components/RadioButton/RadioButton.rb +44 -0
- data/lib/sws/Core/components/RadioButtonList/RadioButtonList.api +20 -0
- data/lib/sws/Core/components/RadioButtonList/RadioButtonList.rb +76 -0
- data/lib/sws/Core/components/Repetition/Repetition.api +10 -0
- data/lib/sws/Core/components/Repetition/Repetition.html +1 -0
- data/lib/sws/Core/components/Repetition/Repetition.rb +137 -0
- data/lib/sws/Core/components/Repetition/Repetition.sws +2 -0
- data/lib/sws/Core/components/ResetButton/ResetButton.api +5 -0
- data/lib/sws/Core/components/ResetButton/ResetButton.rb +28 -0
- data/lib/sws/Core/components/Select/Select.api +24 -0
- data/lib/sws/Core/components/Select/Select.rb +57 -0
- data/lib/sws/Core/components/String/String.api +5 -0
- data/lib/sws/Core/components/String/String.rb +28 -0
- data/lib/sws/Core/components/SubmitButton/SubmitButton.api +6 -0
- data/lib/sws/Core/components/SubmitButton/SubmitButton.rb +53 -0
- data/lib/sws/Core/components/TextArea/TextArea.api +9 -0
- data/lib/sws/Core/components/TextArea/TextArea.rb +28 -0
- data/lib/sws/Core/components/TextField/TextField.api +9 -0
- data/lib/sws/Core/components/TextField/TextField.rb +46 -0
- data/lib/sws/Core/framework.yaml +25 -0
- data/lib/sws/JSComponents/components/JSMenu/JSMenu.api +5 -0
- data/lib/sws/JSComponents/components/JSMenu/JSMenu.html +58 -0
- data/lib/sws/JSComponents/components/JSMenu/JSMenu.rb +34 -0
- data/lib/sws/JSComponents/components/JSMenu/JSMenu.sws +37 -0
- data/lib/sws/JSComponents/framework.yaml +3 -0
- data/lib/sws/adaptor.rb +334 -0
- data/lib/sws/application.rb +604 -0
- data/lib/sws/component.rb +656 -0
- data/lib/sws/cookie.rb +27 -0
- data/lib/sws/direct_action.rb +38 -0
- data/lib/sws/extensions.rb +49 -0
- data/lib/sws/parsers.rb +374 -0
- data/lib/sws/request.rb +308 -0
- data/lib/sws/response.rb +70 -0
- data/lib/sws/session.rb +195 -0
- data/lib/sws/slot.rb +198 -0
- 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
|