Class: SWS::Component

Inherits:
Object
  • Object
show all
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).

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

Class Method Summary collapse

Instance Method Summary collapse

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_componentsObject (readonly)

Action subcomponents for page



42
43
44
# File 'lib/sws/component.rb', line 42

def action_components
  @action_components
end

#definition_componentObject

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

#encodingObject

Character encoding - defaults to Application.default_encoding



49
50
51
# File 'lib/sws/component.rb', line 49

def encoding
  @encoding
end

#html_attrsObject

HTML tag attributes



39
40
41
# File 'lib/sws/component.rb', line 39

def html_attrs
  @html_attrs
end

#method_to_callObject

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

#nameObject

Name of the receiver



33
34
35
# File 'lib/sws/component.rb', line 33

def name
  @name
end

#parametersObject

The Request (HTTP) parameters that conform to the receiver



36
37
38
# File 'lib/sws/component.rb', line 36

def parameters
  @parameters
end

#parentObject (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

#requestObject (readonly)

Request object the receiver is handling



13
14
15
# File 'lib/sws/component.rb', line 13

def request
  @request
end

#request_numberObject (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

#slotsObject

Array of Slot objects defined for the receiver



23
24
25
# File 'lib/sws/component.rb', line 23

def slots
  @slots
end

#subcomponentsObject (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

#tokensObject

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_filenameObject

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

#appObject

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

#awakeObject

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.

Returns:

  • (Boolean)


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.

Returns:

  • (Boolean)


521
522
523
# File 'lib/sws/component.rb', line 521

def content? ()
	return false
end

#create_component_treeObject

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.expand_token( token ) }
	else
		template_parser = TemplateParser.new( template_filename() )
		root_tokens = template_parser.parse()
		root_tokens.each { |token| expand_token( token ) }
	end

end

#definition_filenameObject

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

#pageObject

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_actionObject

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_bindingsObject

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_parametersObject

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.cookies << @request.session.to_cookie
	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_subcomponentsObject

Clears subcomponents array



215
216
217
# File 'lib/sws/component.rb', line 215

def remove_subcomponents ()	
	@subcomponents.clear()			
end

#sessionObject

Session object accessor



168
169
170
# File 'lib/sws/component.rb', line 168

def session ()
	return @request.session
end

#set_request_subcomponentsObject



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

#sleepObject

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.

Returns:

  • (Boolean)


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

Returns:

  • (Boolean)


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_slotsObject

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_filenameObject

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_stringObject

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