WAComponent


Seaside-Component

Comment:

I am a class representing a graphical element of a seaside application. A component has state (instance variables, that might be backtracked using #registerObjectForBacktracking:), behavior decorations, children and an appearance that is specified in #renderContentOn:. A component might chose to display another component with #call:.

Child Components:
It is common for a component to display instances of other components while rendering itself.  It does this by passing them into the #render: method of WAHtmlRenderer.  For example, this #renderContentOn: method simply renders a heading and then displays a counter component 
immediately below it:

	renderContentOn: html
		html heading: 'My Counter' level: 3.
		html render: myCounter.

It's important that you use #render:, rather than directly calling the #renderContentOn: method of the subcomponent. The following is *not* correct:

	renderContentOn: html
		html heading: 'My Counter' level: 3.
		myCounter renderContentOn: html.   "DON'T DO THIS".

These subcomponents are usually instance variables of the component that is "embedding" them.  They are commonly created as part of the components #initialize method:

	initialize
		myCounter := WACounter new.

They may also be stored in a collection. One fairly common pattern is to keep a lazily initialized dictionary of subcomponents that match a collection of model items. For example, if you wanted a BudgetItemRow subcomponent for each member of budgetItems, you might do something like this:

	initialize
		budgetRows := Dictionary new.

	rowForItem: anItem
		^budgetRows at: anItem ifAbsentPut: [ BudgetItemRow item: anItem ].

	renderContentOn: html
		self budgetItems
			do: [ :each | html render: (self rowForItem: each) ]
			separatedBy: [ html horizontalLine ].

Each parent component *must* implement a #children method that returns a collection of all of the subcomponents that it might display on the next render. For the above two examples, #children might look like this:

	children
		^Array with: myCounter

or this:

	children
		^self budgetItems collect: [ :each | self rowForItem: each ].
		
Call/Answer:
If a subcomponent makes a #call: to another component, that component will appear in place of the subcomponent.  In the first example, if myCounter made a #call: to DateSelector, that DateSelector would appear in the context of the counter's parent, with the 'My Counter' heading 
above it.

Since a subcomponent has not been #call:'d, in general #answer: is a no-op.  However, the parent may attach an #onAnswer: block to the subcomponent to be notified if it sends #answer:. This allows one component to be used both from #call: and through embedding. For example:

	initialize
		dateSelector := WADateSelector new 
			onAnswer: [ :date | self dateChosen: date ].

Hierarchy:

ProtoObject
Object
WAPresenter
WAComponent

Summary:

instance variables:

decoration

methods:

instance class
as yet unclassified call/answer convenience rendering as yet unclassified

Detail:

instance variables:

decoration

instance methods:

as yet unclassified
addDecoration: newDecoration

	| prev dec |
	prev _ nil.
	dec _ self decoration.
	[dec ~~ self and: [self decoration: dec shouldWrap: newDecoration]] whileTrue:
		[prev _ dec.
		dec _ dec owner].
	newDecoration owner: dec.
	prev
		ifNil: [self decoration: newDecoration]
		ifNotNil: [prev owner: newDecoration].
	^ newDecoration
allDecorationsDo: aBlock

	| ea |
	ea _ self decoration.
	[ea notNil and: [ea ~~ self]] whileTrue:
		[aBlock value: ea.
		ea _ ea owner]
authenticateWith: anAuthenticator during: aBlock

	^ self decorateWith: (WABasicAuthentication new authenticator: anAuthenticator) during: aBlock
children

	"This method is really important. It should return a collection of all subcomponents of the current component that will be rendered in #renderContentOn:. Components that are displayed using #call: are *not* children."
	
	^ #()
childrenDo: aBlock

	self children do: [:ea | ea ifNotNil: [aBlock value: ea]]
decorateWith: aDecoration during: aBlock

	| val |
	self addDecoration: aDecoration.
	val _ aBlock value.
	self removeDecoration: aDecoration.
	^ val
decoration

	decoration ifNil: [decoration _ WAStateHolder new contents: self].
	^ decoration contents
decoration: aDecoration

	decoration contents: aDecoration
decoration: oldDecoration shouldWrap: newDecoration

	^ (oldDecoration isGlobal and: [newDecoration isGlobal not])
		or: [oldDecoration isDelegation and: [newDecoration isLocal]]
decorationChainDo: aBlock

	aBlock value: self decoration
delegations

	| delegations |
	delegations _ OrderedCollection new.
	self allDecorationsDo: [:ea | ea isDelegation ifTrue: [delegations add: ea]].
	^ delegations
home

	self delegations do: [:ea | self removeDecoration: ea]
initializeDecoration

	decoration _ WAStateHolder new contents: self
nextPresentersDo: aBlock

	self childrenDo: [:ea | ea decorationChainDo: aBlock]
removeDecoration: aDecoration

	| dec |
	dec _ self decoration.
	dec = aDecoration ifTrue:
		[self decoration: dec owner.
		^ self].
	
	[dec notNil] whileTrue:
		[dec owner = aDecoration
			ifTrue: [dec owner: aDecoration owner.  ^ self]
			ifFalse: [dec _ dec owner]]
renderOn: aRenderer

	"Do not override this methods on your component, override instead #renderContentOn: and in addition to not invoke directly renderContentOn: on subcomponent in such method but use instead html render: subcomponent."

	self decorationChainDo: [ :each | each renderWithContext: aRenderer context ].
visiblePresentersDo: aBlock

	self decorationChainDo: [:ea | ea withNextPresentersDo: aBlock]

call/answer
answer

	self answer: self
answer: anObject

	self decorationChainDo: [:ea | ea handleAnswer: anObject]
call: aComponent

	^ AnswerContinuation currentDo: [:cc | self show: aComponent onAnswer: cc]
handleAnswer: anObject

	^ false
onAnswer: aBlock

	^ self addDecoration: (WAAnswerHandler new block: aBlock)
show: aComponent

	self show: aComponent onAnswer: [:v]
show: aComponent onAnswer: aBlock

	| delegation event |
	delegation _ WADelegation new delegate: aComponent.
	event _ nil.
	event _ aComponent onAnswer: [:v | delegation remove. event remove. aBlock value: v].
	self addDecoration: delegation.
	WARenderNotification raiseSignal

convenience
activeComponent

	self visiblePresentersDo: [:ea | ea isDecoration ifFalse: [^ ea]]
addMessage: aString

	self addDecoration: (WAMessageDecoration new message: aString)
confirm: aString

	^ self call: (WAYesOrNoDialog new addMessage: aString)
inform: aString

	self call: (WAFormDialog new addMessage: aString)
isolate: aBlock

	|txn val |
	txn _ WATransaction new.
	val _ self decorateWith: txn during: aBlock.
	txn close.
	^ val
request: aString

	^ self request: aString default: ''
request: aString default: initialString

	^ self request: aString label: nil default: initialString
request: requestString label: labelString

	^ self request: requestString label: labelString default: ''
request: requestString label: labelString default: initialString

	^ self call:
		((WAInputDialog new
			label: labelString;
			default: initialString)
				addMessage: requestString)
validateWith: aBlock

	self addDecoration: (WAValidationDecoration new validateWith: aBlock)

rendering
script

	^ nil
style

	^ nil

class methods:

as yet unclassified
applicationWithPath: aString

	| app |
	app _ WAApplication path: aString.
	app configuration addAncestor: WARenderLoopConfiguration localConfiguration.
	app preferenceAt: #rootComponent put: self.
	^ app
canBeRoot

	"When returning true, the component can be registered as a standalone application from the config interface."
	
	^ false
new

	^ self basicNew initializeDecoration initialize
registerAsApplication: appName

	|app|
	app _ self applicationWithPath: appName.
	WADispatcher default registerEntryPoint: app at: appName.
	^ app
registerAsAuthenticatedApplication: appName

	| app user password |
	app _ self registerAsApplication: appName.
	app configuration addAncestor: WAAuthConfiguration localConfiguration.
	
	[user _ FillInTheBlank
		request: ('Please choose an administrator\username for the application ''', appName asString , '''') withCRs.
	user = ''] whileTrue.
		
	[password _ FillInTheBlank
		request: 'Please enter a password for ', user printString.
	password = ''] whileTrue.
		
	app preferenceAt: #login put: user.
	app preferenceAt: #password put: password.
	
	^ app
	

^top


- made by Dandelion -