Iliad Framework, the login form

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:

<span class="nc">ILWidget</span> <span class="nf">subclass:</span> <span class="ss">#PnProjectLogin</span>
    <span class="nf">instanceVariableNames:</span> <span class="s">'username password loginError'</span>
    <span class="nf">classVariableNames:</span> <span class="s">''</span>
    <span class="nf">poolDictionaries:</span> <span class="s">''</span>
    <span class="nf">category:</span> <span class="s">'LeonardoBlog'</span>

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 or nil 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:

<span class="k">!</span><span class="nc">PnProjectLogin</span><span class="k"> methodsFor: </span><span class="s">'initialization'</span><span class="k">!</span>
<span class="nf">initialize</span>
    <span class="bp">super</span> <span class="nf">initialize</span><span class="p">.</span>
    <span class="nv">loginError</span> <span class="o">:=</span> <span class="bp">nil</span><span class="p">.</span>
    <span class="nv">username</span> <span class="o">:=</span> <span class="s">''</span><span class="p">.</span>
    <span class="nv">password</span> <span class="o">:=</span> <span class="s">''</span><span class="p">.</span><span class="k">! !</span>

Let’s start by creating a method to build the username field and the relative label:

<span class="k">!</span><span class="nc">PnProjectLogin</span><span class="k"> methodsFor: </span><span class="s">'building'</span><span class="k">!</span>
<span class="nf">buildLoginRow:</span> <span class="nv">aForm</span>
    <span class="nv">aForm</span> <span class="nf">div</span> <span class="nf">class:</span> (<span class="nv">loginError</span> <span class="nf">ifNotNil:</span> [ <span class="s">'form-group has-error'</span> ] <span class="nf">ifNil:</span>  [ <span class="s">'form-group'</span> ])<span class="p">;</span>
            <span class="nf">build:</span> [ <span class="o">:</span> <span class="nv">row</span> <span class="o">|</span>
                <span class="nv">row</span> <span class="nf">label</span> <span class="nf">text:</span><span class="s">'Login:'</span><span class="p">.</span>
                <span class="nv">row</span> <span class="nf">input</span> <span class="nf">class:</span><span class="s">'form-control'</span><span class="p">;</span> <span class="nf">action:</span> [ <span class="o">:</span><span class="nv">text</span> <span class="o">|</span> <span class="nv">username</span> <span class="o">:=</span> <span class="nv">text</span> ]<span class="p">;</span> <span class="nf">value:</span><span class="nv">username</span> ]<span class="k">! !</span>

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:

<span class="k">!</span><span class="nc">PnProjectLogin</span><span class="k"> methodsFor: </span><span class="s">'building'</span><span class="k">!</span>
<span class="nf">buildPasswordRow:</span> <span class="nv">aForm</span>
    <span class="nv">aForm</span> <span class="nf">div</span> <span class="nf">class:</span>(<span class="nv">loginError</span> <span class="nf">ifNotNil:</span> [ <span class="s">'form-group has-error'</span> ] <span class="nf">ifNil:</span>  [ <span class="s">'form-group'</span> ])<span class="p">;</span>
            <span class="nf">build:</span> [ <span class="o">:</span> <span class="nv">row</span> <span class="o">|</span>
                <span class="nv">row</span> <span class="nf">label</span> <span class="nf">text:</span><span class="s">'Password'</span><span class="p">.</span>
                <span class="nv">row</span> <span class="nf">input</span> <span class="nf">class:</span><span class="s">'form-control'</span><span class="p">;</span> <span class="nf">type:</span><span class="s">'password'</span><span class="p">;</span> <span class="nf">action:</span> [ <span class="o">:</span><span class="nv">text</span> <span class="o">|</span> <span class="nv">password</span> <span class="o">:=</span> <span class="nv">text</span> ] ]<span class="k">! !</span>

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:

<span class="k">!</span><span class="nc">PnProjectLogin</span><span class="k"> methodsFor: </span><span class="s">'building'</span><span class="k">!</span>
<span class="nf">buildErrorMessageRow:</span> <span class="nv">aForm</span>
    <span class="nv">loginError</span> <span class="nf">ifNotNil:</span> [
    (<span class="nv">aForm</span> <span class="nf">div</span> <span class="nf">class:</span> <span class="s">'alert alert-warning'</span>) <span class="nf">text:</span> <span class="nv">loginError</span> ]<span class="k">! !</span>

Obviously we have to build the errors row only if there are errors! Well. Now we are missing the “register” link.

<span class="k">!</span><span class="nc">PnProjectLogin</span><span class="k"> methodsFor: </span><span class="s">'building'</span><span class="k">!</span>
<span class="nf">buildRegisterRow:</span> <span class="nv">aForm</span>
    (<span class="nv">aForm</span> <span class="nf">div</span> <span class="nf">class:</span> <span class="s">'form-group'</span>) <span class="nf">a</span>
        <span class="nf">href:</span> <span class="s">'register'</span><span class="p">;</span>
        <span class="nf">text:</span> <span class="s">'Want to register?'</span><span class="k">! !</span>

Let’s bind all together in the contents method:

<span class="k">!</span><span class="nc">PnProjectLogin</span><span class="k"> methodsFor: </span><span class="s">'building'</span><span class="k"> stamp: 'LeonardoCecchi 2/28/2014 23:03'!</span>
<span class="nf">contents</span>
    <span class="o">^</span> [ <span class="o">:</span><span class="nv">e</span> <span class="o">|</span>
    <span class="nv">e</span> <span class="nf">h1</span> <span class="nf">text:</span> <span class="s">'Project Notes - Login'</span><span class="p">.</span>
    <span class="nv">e</span> <span class="nf">p</span>
        <span class="nf">text:</span>
            <span class="s">'Project Notes is a note taking app that you</span>
<span class="s">            can use for your project. It will store memos for</span>
<span class="s">            you and for your team.'</span><span class="p">.</span>
    <span class="nv">e</span> <span class="nf">p</span> <span class="nf">text:</span> <span class="s">'Believe me, you really need this app!!'</span><span class="p">.</span>
    <span class="nv">e</span> <span class="nf">form</span>
        <span class="nf">build:</span> [ <span class="o">:</span><span class="nv">form</span> <span class="o">|</span>
            <span class="bp">self</span> <span class="nf">buildLoginRow:</span> <span class="nv">form</span><span class="p">.</span>
            <span class="bp">self</span> <span class="nf">buildPasswordRow:</span> <span class="nv">form</span><span class="p">.</span>
            <span class="bp">self</span> <span class="nf">buildErrorMessageRow:</span> <span class="nv">form</span><span class="p">.</span>
            <span class="bp">self</span> <span class="nf">buildRegisterRow:</span> <span class="nv">form</span><span class="p">.</span>
            <span class="nv">form</span> <span class="nf">button</span>
                <span class="nf">class:</span> <span class="s">'btn btn-default'</span><span class="p">;</span>
                <span class="nf">text:</span> <span class="s">'Login!!'</span><span class="p">;</span>
                <span class="nf">action:</span> [ <span class="bp">self</span> <span class="nf">loginAction</span> ] ] ]<span class="k">! !</span>

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:

<span class="k">!</span><span class="nc">PnProjectLogin</span><span class="k"> methodsFor: </span><span class="s">'actions'</span><span class="k">!</span>
<span class="nf">loginAction</span>
    <span class="bp">self</span> <span class="nf">markDirty</span> <span class="p">.</span>
    <span class="nv">username</span> <span class="nf">ifEmpty:</span> [ <span class="o">^</span> <span class="nv">loginError</span> <span class="o">:=</span> <span class="s">'Please enter the user name'</span> ]<span class="p">.</span>
    <span class="nv">password</span> <span class="nf">ifEmpty:</span> [ <span class="o">^</span> <span class="nv">loginError</span> <span class="o">:=</span> <span class="s">'Please enter the password'</span> ]<span class="p">.</span>
    <span class="nv">loginError</span> <span class="o">:=</span> <span class="bp">nil</span><span class="p">.</span>
    <span class="c">"self redirectToLocal: 'browseProject'."</span>

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.

<span class="nc">ILApplication</span> <span class="nf">subclass:</span> <span class="ss">#LcBlogProjectNotes</span>
    <span class="nf">instanceVariableNames:</span> <span class="s">'loginWidget</span>
<span class="s">    classVariableNames: ''</span>
<span class="s">    poolDictionaries: ''</span>
<span class="s">    category: '</span><span class="nf">LeonardoBlog</span><span class="err">'</span>

As we discussed in the relative article we need an accessor method that lazily creates the widget:

<span class="k">!</span><span class="nc">LcBlogProjectNotes</span><span class="k"> methodsFor: </span><span class="s">'accessing'</span><span class="k">!</span>
<span class="nf">loginWidget</span>
    <span class="o">^</span> <span class="nv">loginWidget</span> <span class="nf">ifNil:</span> [ <span class="nv">loginWidget</span> <span class="o">:=</span> <span class="nc">PnProjectLogin</span> <span class="nb">new</span> ]<span class="k">! !</span>

Now we can create the controller method:

<span class="k">!</span><span class="nc">LcBlogProjectNotes</span><span class="k"> methodsFor: </span><span class="s">'controllers'</span><span class="k">!</span>
<span class="nf">login</span>
    <span class="o">^</span> [ <span class="o">:</span><span class="nv">e</span> <span class="o">|</span> <span class="nv">e</span> <span class="nf">div</span> <span class="nf">class:</span><span class="s">'container'</span><span class="p">;</span> <span class="nf">build:</span> <span class="bp">self</span> <span class="nf">loginWidget</span> ]<span class="k">! !</span>

If you go to http://localhost:7070/ProjectNotes/login you should see the following result:

Project notes login form

Yeah! We succesfuly build our first form, even with error checking. When the loginError is not empty the form looks like this:

Project notes login form error