In the previous post we talked about customizing the generated page to include references to the Bootstrap project.
This is the first lession where we start working on a complete example, a projecs-notes app usable from desktop and mobile web browsers using the twitter bootstrap framework.
Now we will start working on a login page.
To build a new page with a form I will start making a component that
will be built from the application controller named login
. The
controller will be named PnProjectLogin
.
Let’s start building this new component creating a class whose
superclass is ILWidget
:
ILWidget subclass: #PnProjectLogin
instanceVariableNames: 'username password loginError'
classVariableNames: ''
poolDictionaries: ''
category: 'LeonardoBlog'
remember that widgets are stateful: the instance variables will be populated by the application and by the user.
username
will be the text entered by the user in the login form;password
as previous;loginError
will be the string of the error message of the login ornil
if there is no error message.
I won’t show the accessor methods because they can be automatically generated by the Pharo browser but I will show the initializer method:
!PnProjectLogin methodsFor: 'initialization'!
initialize
super initialize.
loginError := nil.
username := ''.
password := ''.! !
Let’s start by creating a method to build the username field and the relative label:
!PnProjectLogin methodsFor: 'building'!
buildLoginRow: aForm
aForm div class: (loginError ifNotNil: [ 'form-group has-error' ] ifNil: [ 'form-group' ]);
build: [ : row |
row label text:'Login:'.
row input class:'form-control'; action: [ :text | username := text ]; value:username ]! !
See how interesting it the build:
method: it takes a block and
evaluate it passing, as argument, the receiver. Before executing the
passed block the method opens the element and closes it when the block
finished. In the previous example the aForm div
execution results in
a DIV
element that get automatically closed when the block has
terminated its execution.
Another interesting thing is the class:
method invocation on the
aForm div
element: to comply with the bootstrap rules we need to
insert the has-error
class when this element of the form is
wrong. To do this we check the loginError
contents.
In the input
element, the action block takes the element text
content.
This method is somewhat similiar:
!PnProjectLogin methodsFor: 'building'!
buildPasswordRow: aForm
aForm div class:(loginError ifNotNil: [ 'form-group has-error' ] ifNil: [ 'form-group' ]);
build: [ : row |
row label text:'Password'.
row input class:'form-control'; type:'password'; action: [ :text | password := text ] ]! !
As you see, while the login field get constructed with a value (or the previous one), the password is always empty.
Ok. Now we will construct the error message row of the form:
!PnProjectLogin methodsFor: 'building'!
buildErrorMessageRow: aForm
loginError ifNotNil: [
(aForm div class: 'alert alert-warning') text: loginError ]! !
Obviously we have to build the errors row only if there are errors! Well. Now we are missing the “register” link.
!PnProjectLogin methodsFor: 'building'!
buildRegisterRow: aForm
(aForm div class: 'form-group') a
href: 'register';
text: 'Want to register?'! !
Let’s bind all together in the contents
method:
!PnProjectLogin methodsFor: 'building' stamp: 'LeonardoCecchi 2/28/2014 23:03'!
contents
^ [ :e |
e h1 text: 'Project Notes - Login'.
e p
text:
'Project Notes is a note taking app that you
can use for your project. It will store memos for
you and for your team.'.
e p text: 'Believe me, you really need this app!!'.
e form
build: [ :form |
self buildLoginRow: form.
self buildPasswordRow: form.
self buildErrorMessageRow: form.
self buildRegisterRow: form.
form button
class: 'btn btn-default';
text: 'Login!!';
action: [ self loginAction ] ] ]! !
In the loginAction
method, which is called when the user press on
the “Login” button we execute the loginAction
method of this class,
which is defined like this:
!PnProjectLogin methodsFor: 'actions'!
loginAction
self markDirty .
username ifEmpty: [ ^ loginError := 'Please enter the user name' ].
password ifEmpty: [ ^ loginError := 'Please enter the password' ].
loginError := nil.
"self redirectToLocal: 'browseProject'."
In this method, which for now is only a stub, we check for empty data and then we will call the main page.
Now we need to integrate this widget in our application,
LcBlogProjectNotes
. We must add an instance variable named loginWidget
.
ILApplication subclass: #LcBlogProjectNotes
instanceVariableNames: 'loginWidget
classVariableNames: ''
poolDictionaries: ''
category: 'LeonardoBlog'
As we discussed in the relative article we need an accessor method that lazily creates the widget:
!LcBlogProjectNotes methodsFor: 'accessing'!
loginWidget
^ loginWidget ifNil: [ loginWidget := PnProjectLogin new ]! !
Now we can create the controller method:
!LcBlogProjectNotes methodsFor: 'controllers'!
login
^ [ :e | e div class:'container'; build: self loginWidget ]! !
If you go to http://localhost:7070/ProjectNotes/login you should see the following result:
Yeah! We succesfuly build our first form, even with error checking.
When the loginError
is not empty the form looks like this:
The next post is here.