WAComponent subclass: #PersonalInformationView instanceVariableNames: 'name address gender sendEmailUpdates birthdate' classVariableNames: '' poolDictionaries: '' category: 'SCSeasideTutorial' name ^name name: aString name := aString address ^address address: aString address := aString gender ^gender gender: aString gender := aString sendEmailUpdates ^sendEmailUpdates sendEmailUpdates: aValue sendEmailUpdates := aValue birthdate ^birthdate birthdate: aDate birthdate := aDateWe will begin by prompting for the name (a text field) and address (a text area):
renderContentOn: html
html form:
[html spanClass: 'label' with: 'Name:'.
html spanClass: 'field'
with: [html textInputWithValue: self name
callback: [:v | self name: v]].
html spanClass: 'label' with: 'Address:'.
html spanClass: 'field'
with: [html textAreaWithValue: self address
callback: [:v | self address: v]].
html spanClass: 'button'
with: [html submitButtonWithAction: [self save] text: 'Save']]
save
self inform: self name , '--' , self address
style
^'
*.label {
clear: both;
float: left;
width: 100px;
}
*.field {
float: left;
width: 200px;
}
*.button {
clear: both;
width: 200px;
margin-left: 100px;
margin-top: 10px;
float: left;
}'
Notice that form submission buttons use the same callback mechanism as
anchors. Text inputs and areas also use callbacks. For each form
component you can specify a one argument callback block which will be
sent the contents of the input field. When a form is submittted
Seaside processes all input field callbacks before the submit
button callback. This is very important since it ensures that the
action in your submit button can count on your input field callbacks
to have done their job (quite often just validating and storing the
text that was supplied). So, the save
method can count on the input field callbacks having been called if
the user entered any data in those fields.
Since this is a new class we need to tell Seaside to use it as the root of an application (just as we did with HelloWorldComponent). Evaluate:
PersonalInformationView registerAsApplication: 'personal'Now point your web browser at http://localhost:9090/seaside/personal and you should see the form:
Try entering values and submitting the form. Start a new session with the component and submit the form (with empty fields). Notice that you are given a call stack trace for an error that occurred and the option (link) to debug it. Following the "Debug" link will raise the debugger dialog inside Squeak. Try it and find out why this error occurred. If you like you can correct it now or wait until later in this tutorial when we deal with validation.
self name: value in the code above).. also, the
component's initial value often comes from an accessor method (for
example self name in the code above). The renderer
provides some methods which do this automatically for you so the
following code operates exactly as the code above:
renderContentOn: html html form: [html spanClass: 'label' with: 'Name:'. html spanClass: 'field' with: [html textInputOn: #name of: self]. html spanClass: 'label' with: 'Address:'. html spanClass: 'field' with: [html textAreaOn: #address of: self]. html spanClass: 'button' with: [html submitButtonWithAction: [self save] text: 'Save']]The
textInputOn:of: and textAreaOn:of:
methods take the (symbol) name of the property to be displayed and the
object which holds the property (responds to the accessor and
mutator). Seaside generates the method names from the property names
using the usual Smalltalk accessor/mutator naming conventions. For
example a property called #name would use a method called
name as an accessor and a method called
name: as a mutator.
form: block above
to render a list for gender selection:
html spanClass: 'label' with: 'Gender:'.
html spanClass: 'field'
with: [html
selectFromList: #(Male Female)
selected: self gender
callback: [:v | self gender: v]].
Notice that the selected: argument allows us to specify
which item is selected by default (when the list is first displayed).
Seaside provides the usual shorthand for our simple case where the
callback is a mutator and the starting value comes from an accessor:
html spanClass: 'label' with: 'Gender:'. html spanClass: 'field' with: [html selectInputOn: #gender of: self list: #(Male Female)]Let's update
save to display the gender:
save self inform: name , '--' , address , '--' , genderTry the application now...you should see either a list box or a combo box (depending on your browser) to select the gender. There are several forms of the list generation methods which I encourage you to explore. Look in
WAHtmlRenderer for method names
beginning with "select" or, alternatively, the
optionWithLabel: methods.
html spanClass: 'field'
with: [|group|
group := html radioGroup.
html radioButtonInGroup: group
selected: self gender = #Male
callback: [self gender: #Male].
html text: 'Male'; break.
html radioButtonInGroup: group
selected: self gender = #Female
callback: [self gender: #Female].
html text: 'Female'].
Radio buttons in a group are mutually exclusive (only one can be
selected at a time). You first ask the renderer to create a new group
using radioGroup. This group is then supplied as the
first argument to the radio button creation methods. The
selected: argument determines if the browser will render
the page with that button selected. Notice in our example that we
select the button if it corresponds to the current value of the
gender variable. That way the form reflects the state of
our component. The callback: argument should be a zero
argument callback block which is executed when the page is submitted
with that radio button selected. Notice the callback block is not
called for options that were not selected.
sendEmailUpdates instance variable in our component. We
want to render a check box editing that field. Add the following code
inside the form: block:
html spanClass: 'label' with: 'Updates:'. html spanClass: 'field' with: [html checkboxOn: #sendEmailUpdates of: self].I went right to the shorthand version of this method. There is, of course, a method
checkboxWithValue:callback:. Update the
display method to show the value of this flag:
save self inform: name , '--' , address , '--' , gender , '--' , sendEmailUpdates printStringTry the application now. (Notice that the checkbox is selected even though the initial value of the instance variable is
nil.) Fill out and submit the form to see that the
checkbox is working.
dateInputWithValue:callback: method. Add the following
code to your form: block:
html spanClass: 'label' with: 'Birthdate:'. html spanClass: 'field' with: [html dateInputWithValue: self birthdate callback: [:v | self birthdate: v]].and update our display method:
save
self inform: name , '--' , address , '--' , gender , '--' , sendEmailUpdates printString ,
'--' , self birthdate printString
If you view this component in your browser you'll get an error
because, unlike the other components, the date input does not like
being given nil for its current value. We need to give a
reasonable initial value to the date. Modify the birthdate accessor to
lazy initialize the date:
birthdate ^birthdate ifNil: [birthdate := Date today]Now load the application in your web browser and everything should be fine. Your final version should look something like this: