Class: SWS::Component
- Inherits:
-
Object
- Object
- SWS::Component
- Defined in:
- lib/sws/component.rb
Overview
Represents a HTML page or its part. Probably most important class of SWS - it is used to build pages and to create reusable parts. It consists of a few files, which define the component object - Ruby class file (.rb), HTML template (.html), slot definition file (.api) and bindings file (.sws).
Direct Known Subclasses
JSComponents::JSMenu, Conditional, Content, ExceptionPage, Form, FormElement, GenericContainer, GenericElement, Hyperlink, Image, Link, Repetition, String, TextComponent
Constant Summary collapse
- @@component_infos =
Cache for component filenames
Hash.new do |hash,component| hash[component] = Application.instance.get_component( component ) end
- @@synchronized_slots =
List of slots synchronized with instance variables
Hash.new do |hash,component| hash[component] = Array.new end
Instance Attribute Summary collapse
-
#action_components ⇒ Object
readonly
Action subcomponents for page.
-
#definition_component ⇒ Object
Component that defines bindings for the receiver.
-
#encoding ⇒ Object
Character encoding - defaults to Application.default_encoding.
-
#html_attrs ⇒ Object
HTML tag attributes.
-
#method_to_call ⇒ Object
Method of the receiver that will be called during #perform_action phase.
-
#name ⇒ Object
Name of the receiver.
-
#parameters ⇒ Object
The Request (HTTP) parameters that conform to the receiver.
-
#parent ⇒ Object
readonly
The Component up the hierarchy, eg.
-
#request ⇒ Object
readonly
Request object the receiver is handling.
-
#request_number ⇒ Object
readonly
Number of requests processed by this component - necessary when handling backtracking.
-
#slots ⇒ Object
Array of Slot objects defined for the receiver.
-
#subcomponents ⇒ Object
readonly
Array of components contained within the receiver.
-
#tokens ⇒ Object
Returns the value of attribute tokens.
Class Method Summary collapse
-
.create(class_name, request, name = nil, parent = nil, slots = Hash.new) ⇒ Object
Return new component of that name for given request.
-
.synchronize_slot(*args) ⇒ Object
Defines a slot (symbol or name) that should be synchronized with instance variable of the same name.
Instance Method Summary collapse
-
#api_filename ⇒ Object
Return filename for slot definition (.api file).
-
#app ⇒ Object
Shortcut for Application instance.
-
#append_to_response(response) ⇒ Object
Recursively generates HTML code for all subcomponents and adds it to the Response object.
-
#awake ⇒ Object
A generic method used for per-request initialization of the component.
-
#container? ⇒ Boolean
Returns true if the receiver is a container component - only containers can contain a content.
-
#content? ⇒ Boolean
Returns true if the component is content - it will be replaced by the content of the parent component.
-
#create_component_tree ⇒ Object
Parses template file for the receiver and uses generated token tree to create component tree.
-
#definition_filename ⇒ Object
Returns filename for component definition (.sws file).
-
#initialize(request, name, parent, slots) ⇒ Component
constructor
Create new Component.
-
#page ⇒ Object
Returns most top-level component.
-
#perform_action ⇒ Object
Performs the selected action recursively on all subcomponents.
-
#process_bindings ⇒ Object
Takes parameters and binds them to slots - recursively for subcomponents.
-
#process_parameters ⇒ Object
Takes values from request (HTTP parameters) and binds them to slots.
-
#process_request(request, request_number = nil) ⇒ Object
Main component procedure - performs all phases of request handling.
-
#remove_subcomponents ⇒ Object
Clears subcomponents array.
-
#session ⇒ Object
Session object accessor.
- #set_request_subcomponents ⇒ Object
-
#sleep ⇒ Object
A generic method used for per-request deinitialization of the component.
-
#slot_bound?(slot_name) ⇒ Boolean
Returns true if Slot for given name is bound.
-
#subcomponent_for_name(name) ⇒ Object
Returns subcomponent of the receiver for given name, or nil if not found.
-
#synchronize_slot?(slot_name) ⇒ Boolean
Returns the name of the instance variable the slot should be synchronized with or nil if it should not be synchronized.
-
#synchronize_slots ⇒ Object
Synchronizes variable with slots.
-
#template_filename ⇒ Object
Returns filename for HTML template of this component.
-
#tokenize_binding(key, value) ⇒ Object
Updates @parameters Hash or calls the same method recursively on proper subcomponent (the name of which is determined by the parameter name to the first dot).
-
#update_binding(key, value) ⇒ Object
Updates binding described by key to value.
-
#url_string ⇒ Object
A string to be added to URL to indicate this component will handle the request.
Constructor Details
#initialize(request, name, parent, slots) ⇒ Component
Create new Component. If you want to create new page (toplevel component), the only argument is a Request object. If you want to create another component (which you shouldn’t need to), the arguments are: named of the component, Request object, parent component and slots Hash
66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/sws/component.rb', line 66 def initialize ( request, name, parent, slots ) #use this if you are creating new page (no slots, parent or name)... # TODO: create child class Page if ( parent == nil) initialize_page( request ) #...and this when creating new component (but in such case Component.create is probably better) else initialize_component( request, name, parent, slots ) end #common initialization for both "constructors" initialize_common() end |
Instance Attribute Details
#action_components ⇒ Object (readonly)
Action subcomponents for page
42 43 44 |
# File 'lib/sws/component.rb', line 42 def action_components @action_components end |
#definition_component ⇒ Object
Component that defines bindings for the receiver. Usually parent, but not always.
30 31 32 |
# File 'lib/sws/component.rb', line 30 def definition_component @definition_component end |
#encoding ⇒ Object
Character encoding - defaults to Application.default_encoding
49 50 51 |
# File 'lib/sws/component.rb', line 49 def encoding @encoding end |
#html_attrs ⇒ Object
HTML tag attributes
39 40 41 |
# File 'lib/sws/component.rb', line 39 def html_attrs @html_attrs end |
#method_to_call ⇒ Object
Method of the receiver that will be called during #perform_action phase
26 27 28 |
# File 'lib/sws/component.rb', line 26 def method_to_call @method_to_call end |
#name ⇒ Object
Name of the receiver
33 34 35 |
# File 'lib/sws/component.rb', line 33 def name @name end |
#parameters ⇒ Object
The Request (HTTP) parameters that conform to the receiver
36 37 38 |
# File 'lib/sws/component.rb', line 36 def parameters @parameters end |
#parent ⇒ Object (readonly)
The Component up the hierarchy, eg. for SubmitButton it can be Form. For the topmost component see #page
17 18 19 |
# File 'lib/sws/component.rb', line 17 def parent @parent end |
#request ⇒ Object (readonly)
Request object the receiver is handling
13 14 15 |
# File 'lib/sws/component.rb', line 13 def request @request end |
#request_number ⇒ Object (readonly)
Number of requests processed by this component - necessary when handling backtracking
46 47 48 |
# File 'lib/sws/component.rb', line 46 def request_number @request_number end |
#slots ⇒ Object
Array of Slot objects defined for the receiver
23 24 25 |
# File 'lib/sws/component.rb', line 23 def slots @slots end |
#subcomponents ⇒ Object (readonly)
Array of components contained within the receiver. Opposite to #parent
20 21 22 |
# File 'lib/sws/component.rb', line 20 def subcomponents @subcomponents end |
#tokens ⇒ Object
Returns the value of attribute tokens.
51 52 53 |
# File 'lib/sws/component.rb', line 51 def tokens @tokens end |
Class Method Details
.create(class_name, request, name = nil, parent = nil, slots = Hash.new) ⇒ Object
Return new component of that name for given request. Can be used instead of constructor if you have class_name for the component as string and not the Class object itself - this method is used for components in order to require them dynamically.
202 203 204 205 206 207 208 209 210 211 |
# File 'lib/sws/component.rb', line 202 def Component.create ( class_name, request, name = nil, parent = nil, slots = Hash.new ) klass = @@component_infos[class_name].component_class # TODO: why was this here? #name = class_name if ( name == nil ) component = klass.new( request, name, parent, slots ) return component end |
.synchronize_slot(*args) ⇒ Object
Defines a slot (symbol or name) that should be synchronized with instance variable of the same name
118 119 120 |
# File 'lib/sws/component.rb', line 118 def Component.synchronize_slot ( *args ) args.each { |slot_name| @@synchronized_slots[self.to_s] << slot_name.to_s } end |
Instance Method Details
#api_filename ⇒ Object
Return filename for slot definition (.api file)
156 157 158 |
# File 'lib/sws/component.rb', line 156 def api_filename () return @@component_infos[self.class.to_s].api end |
#app ⇒ Object
Shortcut for Application instance
162 163 164 |
# File 'lib/sws/component.rb', line 162 def app () return Application.instance end |
#append_to_response(response) ⇒ Object
Recursively generates HTML code for all subcomponents and adds it to the Response object
385 386 387 388 389 390 391 |
# File 'lib/sws/component.rb', line 385 def append_to_response ( response ) @subcomponents.each do |child| child.append_to_response( response ) end end |
#awake ⇒ Object
A generic method used for per-request initialization of the component. Called at the very begining of response process handling . Default implementation does recursively nothing :) - that is, it calls the awake() method for all subcomponents and their subcomponents and their…
268 269 270 |
# File 'lib/sws/component.rb', line 268 def awake () @subcomponents.each { |com| com.awake() } end |
#container? ⇒ Boolean
Returns true if the receiver is a container component - only containers can contain a content.
513 514 515 |
# File 'lib/sws/component.rb', line 513 def container? () return true end |
#content? ⇒ Boolean
Returns true if the component is content - it will be replaced by the content of the parent component. Currently only SWS::Component::Content return true here.
521 522 523 |
# File 'lib/sws/component.rb', line 521 def content? () return false end |
#create_component_tree ⇒ Object
Parses template file for the receiver and uses generated token tree to create component tree. Is called even if the same component is still used after #perform_action() (but in this case it has old subcomponents removed before)
398 399 400 401 402 403 404 405 406 407 408 409 410 |
# File 'lib/sws/component.rb', line 398 def create_component_tree () $log_sws_component.debug( "Creating component tree for #{self}" ) if ( content? ) # If the receiver is Content, expand content tokens definition_component.tokens.each { |token| @parent.( token ) } else template_parser = TemplateParser.new( template_filename() ) root_tokens = template_parser.parse() root_tokens.each { |token| ( token ) } end end |
#definition_filename ⇒ Object
Returns filename for component definition (.sws file)
150 151 152 |
# File 'lib/sws/component.rb', line 150 def definition_filename () return @@component_infos[self.class.to_s].definition end |
#page ⇒ Object
Returns most top-level component
174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/sws/component.rb', line 174 def page () return self if ( @parent == nil ) component = @parent while ( component.parent != nil ) component = component.parent end return component end |
#perform_action ⇒ Object
Performs the selected action recursively on all subcomponents. This is the phase of response handling process where the topmost component can change - it is changed to the return value of first action that returns other value than nil
370 371 372 373 374 375 376 377 378 379 380 |
# File 'lib/sws/component.rb', line 370 def perform_action () next_page = nil @subcomponents.each do |subcomponent| next_page = subcomponent.perform_action() $log_sws_component.debug( "PERFORM: #{next_page} for component #{subcomponent}" ) return next_page if ( next_page ) end return next_page end |
#process_bindings ⇒ Object
Takes parameters and binds them to slots - recursively for subcomponents. Default implementation (overriden in several Component subclasses) just calls #update_binding( key,value )
302 303 304 305 306 307 308 309 |
# File 'lib/sws/component.rb', line 302 def process_bindings () @subcomponents.each do |subcomponent| subcomponent.process_bindings() end @parameters.each { |key,value| update_binding( key,value ) } end |
#process_parameters ⇒ Object
Takes values from request (HTTP parameters) and binds them to slots. Component to bind to is determined by splitting the parameter name. Each parameter is passed to #tokenize_binding( key, value ) method. After processing the parameters #process_bindings() is called.
287 288 289 290 291 292 293 294 295 296 |
# File 'lib/sws/component.rb', line 287 def process_parameters () @request.params.each do |key,value| stripped_key = key.sub( /^[^.]*?\./,"" ) $log_sws_component.debug( "Received key #{key}, stripped #{stripped_key} with value #{value}" ) tokenize_binding( stripped_key, value ) end process_bindings() end |
#process_request(request, request_number = nil) ⇒ Object
Main component procedure - performs all phases of request handling. Returns component object which will process next request
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/sws/component.rb', line 221 def process_request ( request, request_number = nil ) # TODO: check here if backtracked and omit some initial phases if so @request = request $log_sws_component.debug( "Request number in process_request: #{request_number}" ) request_number ||= @request_number $log_sws_component.debug( "#{self.class.name} #{object_id}: Subcomponents for request #{request_number}" ) @subcomponents = @request_subcomponents[request_number] || Array.new unless request_number == @request_number $log_sws_component.debug( "---------BACKTRACK from #{@request_number} to #{request_number}" ) end @request_number += 1 $log_sws_component.debug( "#{self.class.name} #{object_id}: request number is now #{@request_number}" ) awake() process_parameters() new_component = perform_action() || self # If below is true variable new_component is used as response. # It replaces standard return [SWS::Component, SWS::Response] # with [SWS::Response, nil]. if( new_component.instance_of?( SWS::Response ) ) return new_component, nil end response = Response.new( request ) @subcomponents = Array.new new_component.create_component_tree() new_component.set_request_subcomponents() new_component.append_to_response( response ) response. << @request.session. response.headers["Content-type"] = "text/html;charset=#{@encoding}" #response.headers["Pragma"] = "no-cache" #response.headers["Expires"] = "0" #response.headers["Cache-control"] = "private, no-cache, no-store, must-revalidate, max-age = 0" sleep() return new_component,response end |
#remove_subcomponents ⇒ Object
Clears subcomponents array
215 216 217 |
# File 'lib/sws/component.rb', line 215 def remove_subcomponents () @subcomponents.clear() end |
#session ⇒ Object
Session object accessor
168 169 170 |
# File 'lib/sws/component.rb', line 168 def session () return @request.session end |
#set_request_subcomponents ⇒ Object
187 188 189 190 |
# File 'lib/sws/component.rb', line 187 def set_request_subcomponents () $log_sws_component.debug( "#{self.class.name} #{object_id}: Storing subcomponents for request #{@request_number}" ) @request_subcomponents [@request_number] = @subcomponents end |
#sleep ⇒ Object
A generic method used for per-request deinitialization of the component. Called at the very end of handling response process. Default implementation does recursively nothing :) - that is, it calls the sleep() method for all subcomponents and their subcomponents and their…
278 279 280 |
# File 'lib/sws/component.rb', line 278 def sleep () @subcomponents.each { |com| com.sleep() } end |
#slot_bound?(slot_name) ⇒ Boolean
Returns true if Slot for given name is bound.
506 507 508 |
# File 'lib/sws/component.rb', line 506 def slot_bound? ( slot_name ) return @slots[slot_name].bound_string != nil end |
#subcomponent_for_name(name) ⇒ Object
Returns subcomponent of the receiver for given name, or nil if not found
361 362 363 |
# File 'lib/sws/component.rb', line 361 def subcomponent_for_name ( name ) return @subcomponents.find { |subcomponent| subcomponent.name == name } end |
#synchronize_slot?(slot_name) ⇒ Boolean
Returns the name of the instance variable the slot should be synchronized with or nil if it should not be synchronized
124 125 126 |
# File 'lib/sws/component.rb', line 124 def synchronize_slot? ( slot_name ) return @@synchronized_slots[self.class.to_s].include?( slot_name.to_s ) end |
#synchronize_slots ⇒ Object
Synchronizes variable with slots
129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/sws/component.rb', line 129 def synchronize_slots () @@synchronized_slots[self.class.to_s].each do |slot_name| slot = @slots[slot_name] unless slot raise NameError.new( "Synchronized slot '#{slot_name}' not found in #{self}" ) else # Slots are synchronized in Slot.value, so it's enough to call it slot.value end end end |
#template_filename ⇒ Object
Returns filename for HTML template of this component
144 145 146 |
# File 'lib/sws/component.rb', line 144 def template_filename () return @@component_infos[self.class.to_s].template end |
#tokenize_binding(key, value) ⇒ Object
Updates @parameters Hash or calls the same method recursively on proper subcomponent (the name of which is determined by the parameter name to the first dot).
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/sws/component.rb', line 315 def tokenize_binding ( key,value ) if ( md = /^([^.]*?)\.(.*)$/.match( key ) ) #contains dot - extract component name and rest of binding #TODO: what if the same component is present twice in template? #but probably using form elements twice is not a good idea :) component = subcomponent_for_name( md[1] ) if ( component == nil ) $log_sws_component.debug( "Ignoring key #{key}, component #{self}" ) else component.tokenize_binding( md[2],value ) end else #no dot - only slot name left - parameter belongs to self @parameters[key] = value end end |
#update_binding(key, value) ⇒ Object
Updates binding described by key to value. Default implementation (often overriden in specific Component subclasses to perform custom initialization) just updates proper Slot value
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 |
# File 'lib/sws/component.rb', line 336 def update_binding ( key, value ) slot = @slots[key] $log_sws_component.debug( "slot #{key}: binding #{slot}, component: #{self}" ) if slot slot.value = value else # This happens in cases like submitting INPUT type=image in Firefox, # which (besides usual name.x and name.y parameters) additionaly submits # "name" parameter (without any suffix) :(. So in order to avoid # exception we need to ignore the parameter. TODO: refactor the whole # parameter processing logic, as this workaround introduces some # overhead. subcomponent = subcomponent_for_name( key ) if subcomponent $log_sws_component.debug( "Ignoring parameter #{key} with value #{value} as it refers directly to subcomponent of component #{self}" ) else $log_sws_component.debug( "Ignoring bogus parameter #{key} with value #{value} for component #{self}" ) end end end |
#url_string ⇒ Object
A string to be added to URL to indicate this component will handle the request
194 195 196 |
# File 'lib/sws/component.rb', line 194 def url_string return "#{object_id}.#{@request_number}" end |