Adding a Login Screen to a Sencha Touch Application, Part 2

In this second part of my tutorial on How to add a Login Screen to Sencha Touch application, you will continue building a small Sencha Touch app that demonstrates how to implement a very basic login feature.

You created the app’s Login view with its main components in the first part of the tutorial, and gave the Login view the ability to fire the signInCommand application event when a user taps the Login button.

Creating the MainMenu View

Now you will create the app’s MainMenu view. You will take users to this view after a successful authentication.

You need to place the view’s code in the view/MainMenu.js file. Here is the skeleton of the view:

Ext.define('Sample.view.MainMenu', {
    extend: 'Ext.Panel',
    requires: ['Ext.TitleBar'],
    alias: 'widget.mainmenuview',
    config: {
        layout: {
            type: 'fit'
        },
	 items:[]
    }
});

To keep this tutorial as simple as possible, the MainMenu view will contain just a TitleBar, which in turn will host the LogOff button. You need to add these components in the view’s items array:

items: [{
    xtype: 'titlebar',
    title: 'Main Menu',
    docked: 'top',
    items: [
        {
            xtype: 'button',
            text: 'Log Off',
            itemId: 'logOffButton',
            align: 'right'
        }
    ]
}]

The LogOff button needs a tap listener, which you will define in the listeners config:

listeners: [{
    delegate: '#logOffButton',
    event: 'tap',
    fn: 'onLogOffButtonTap'
}]

Inside this listener, you will trigger an application event called signOffCommand:

onLogOffButtonTap: function () {
    this.fireEvent('signOffCommand');
}

The controller will capture this event and perform the log off logic.

This is all you will do in the MainMenu view (In a real-world app, you will need to add more components to this view). Now you need to add the view in the app.js file:

Ext.application({
    name: 'Sample',
    views: ['Login','MainMenu'],
    controllers:['Login'],
    launch: function () {

        Ext.Viewport.add([
            { xtype: 'loginview' },
            { xtype: 'mainmenuview' }
        ]);
    }
});

Creating the Controller

The next application module you will create is the Login controller. Inside the controller, you will handle the signInCommand event, and create the logic to authenticate the user and grant her access to the application’s main menu Screen.

The controller will also handle the signOffCommand event, triggered by the MainMenu view when the user has requested to log off.

Let’s start with an empty controller in the controller/Login.js file:

Ext.define('Sample.controller.Login', {
    extend: 'Ext.app.Controller',
    config: {

    }
});

Since you want to listen to events generated in the Login and MainMenu views, you need to create their respective references in the controller. You will create the loginView and mainMenuView references using the controller’s refs config:

Ext.define('Sample.controller.Login', {
    extend: 'Ext.app.Controller',
    config: {
        refs: {
            loginView: 'loginview',
            mainMenuView: 'mainmenuview'
        },
        control: {
            loginView: {
                signInCommand: 'onSignInCommand'
            }
        }
    }
});

The control config is where you define the events you want your controller to listen to. In this case, you are saying that the signInCommand generated by the Login view ref will be handled by the onSignInCommand method.

Implementing the Sign-in Logic

The onSignInCommand method is in charge of performing the authentication logic:

onSignInCommand: function (view, username, password) {

    console.log('Username: ' + username + '\n' + 'Password: ' + password);

    var me = this,
        loginView = me.getLoginView();

    if (username.length === 0 || password.length === 0) {

        loginView.showSignInFailedMessage('Please enter your username and password.');
        return;
    }

    loginView.setMasked({
        xtype: 'loadmask',
        message: 'Signing In...'
    });

    Ext.Ajax.request({
        url: '../../services/login.ashx',
        method: 'post',
        params: {
            user: username,
            pwd: password
        },
        success: function (response) {

            var loginResponse = Ext.JSON.decode(response.responseText);

            if (loginResponse.success === "true") {
                // The server will send a token that can be used throughout the app to confirm that the user is authenticated.
                me.sessionToken = loginResponse.sessionToken;
                me.signInSuccess();     //Just simulating success.
            } else {
                me.signInFailure(loginResponse.message);
            }
        },
        failure: function (response) {
            me.sessionToken = null;
            me.signInFailure('Login failed. Please try again later.');
        }
    });
}

Inside onSignInCommand, you first perform a very simple validation of the username and password values passed by the Login view:

var me = this,
    loginView = me.getLoginView();

if (username.length === 0 || password.length === 0) {

    loginView.showSignInFailedMessage('Please enter your username and password.');
    return;
}

If the validation fails, you will invoke the Login view’s showSignInFailedMessage method. This method does not exist yet. Let’s jump to the view/Login.js file and add it like so:

showSignInFailedMessage: function (message) {
    var label = this.down('#signInFailedLabel');
    label.setHtml(message);
    label.show();
}

In showSignInFailedMessage, you acquire a reference to the label component you will use to display the failure message, and set its html to the value passed from the controller. Invoking this method when the validation fails will change the look of the view as depicted below:

login-view-11

Back in the controller/Login.js file, your next step inside the onSignInCommand method consists of activating the Login view’s load mask. You can accomplish this using the setMasked method:

loginView.setMasked({
    xtype: 'loadmask',
    message: 'Signing In...'
});

Next, you will send the username and password values to the server for authentication:

Ext.Ajax.request({
    url: '../../services/login.ashx',
    method: 'post',
    params: {
        user: username,
        pwd: password
    },
    success: function (response) {

        var loginResponse = Ext.JSON.decode(response.responseText);

        if (loginResponse.success === "true") {
            // The server will send a token that can be used throughout the app to confirm that the user is authenticated.
            me.sessionToken = loginResponse.sessionToken;
            me.signInSuccess();     //Just simulating success.
        } else {
            me.signInFailure(loginResponse.message);
        }
    },
    failure: function (response) {
        me.sessionToken = null;
        me.signInFailure('Login failed. Please try again later.');
    }
});

You are making an Ajax request, sending the username and password to the server through the params config. You are free to use any server-side technology. Just make sure to change the url config so it references the endpoint that will perform the authentication in your app.

The success callback evaluates the server’s response and sets the controller’s sessionToken property. You can use this property throughout the app to confirm that the user is authenticated. After setting the session token, the callback invokes either the signInSuccess or signInFailure methods in the controller.

The failure callback invokes the controller’s signInFailure method after cancelling the session token.

Let’s implement the signInSuccess method first:

signInSuccess: function () {
    console.log('Signed in.');
    var loginView = this.getLoginView();
    mainMenuView = this.getMainMenuView();
    loginView.setMasked(false);

    Ext.Viewport.animateActiveItem(mainMenuView, this.getSlideLeftTransition());
}

Inside signInSuccess, you need to turn off the Login view’s mask, and make the MainMenu view active. Here you will take advantage of the getLoginView and getMainMenuView methods, which the framework creates for you automatically when you add the views’ references in the ref config of the controller.

When making the MainMenu view active, you will use of the getSlideLeftTransition method. This method returns a slide left transition object that you can use in the controller:

getSlideLeftTransition: function () {
    return { type: 'slide', direction: 'left' };
}

The MainMenu view should look like this:

main-menu-view1

Now you can go ahead and create the signInFailure method. This method will let the user know that the sign in operation failed. Let’s take a look at the code:

signInFailure: function (message) {
    var loginView = this.getLoginView();
    loginView.showSignInFailedMessage(message);
    loginView.setMasked(false);
}

This short method invokes a couple of routines in the Login view: showSignInFaileMessage and setMasked. You have used both methods previously in this tutorial.

This is all you need to handle the success and failure cases in the sign in logic. Now you can focus on the sign off logic.

Implementing the Sign-off Logic

The first step would be to handle the signOffCommand fired from the MainMenu view. In the control section of the controller’s config, you will add an entry for the MainMenu view:

config: {
    refs: {
        loginView: 'loginview',
        mainMenuView: 'mainmenuview'
    },
    control: {
        loginView: {
            signInCommand: 'onSignInCommand'
        },
        mainMenuView: {
            signOffCommand: 'onSignOffCommand'
        }
    }
}

The entry maps the signOffCommand event to a controller method named onSignOffCommand. You can add the onSignOffCommand method at the end of the controller:

onSignOffCommand: function () {

    var me = this;

    Ext.Ajax.request({
        url: '../../services/logoff.ashx',
        method: 'post',
        params: {
            sessionToken: me.sessionToken
        },
        success: function (response) {

            // TODO: Implementation.
        },
        failure: function (response) {

            // TODO: Implementation.
        }
    });

    Ext.Viewport.animateActiveItem(this.getLoginView(), this.getSlideRightTransition());
}

You need to expire the user’s session on both the server and the client. This is why you are first executing an Ajax request to the server-side endpoint responsible for expiring the user’s token on the server, and then cancelling the token in the app.

After these steps, you will make the Login view active again. Here you will use the getSlideRightTransition method, which returns a slide right transition as its name indicates:

getSlideRightTransition: function () {
    return { type: 'slide', direction: 'right' };
}

Conclusion

In this tutorial you learned how to add a Login view to a Sencha Touch application. You also learned how to connect this view to a controller that performs authentication logic on behalf of the application.

Authentication is only one part of the security design of an app. After authentication takes place, you need to decide which application features the user will be able to access based on her rights. In future tutorials I will touch on this subject, commonly known as authorization, and how you can implement it in a Sencha Touch app.

Stay tuned! :-)

Source Code

Here’s the source code of the app.

App.js:

Ext.application({
    name: 'Sample',
    views: ['Login','MainMenu'],
    controllers:['Login'],
    launch: function () {

        Ext.Viewport.add([
            { xtype: 'loginview' },
            { xtype: 'mainmenuview' }
        ]);
    }
});

view/Login.js:

Ext.define('Sample.view.Login', {
    extend: 'Ext.form.Panel',
    alias: "widget.loginview",
    requires: ['Ext.form.FieldSet', 'Ext.form.Password', 'Ext.Label', 'Ext.Img', 'Ext.util.DelayedTask'],
    config: {
        title: 'Login',
        items: [
            {
                xtype: 'image',
                src: Ext.Viewport.getOrientation() == 'portrait' ? '../../../img/login.png' : '../../../img/login-small.png',
                style: Ext.Viewport.getOrientation() == 'portrait' ? 'width:80px;height:80px;margin:auto' : 'width:40px;height:40px;margin:auto'
            },
            {
                xtype: 'label',
                html: 'Login failed. Please enter the correct credentials.',
                itemId: 'signInFailedLabel',
                hidden: true,
                hideAnimation: 'fadeOut',
                showAnimation: 'fadeIn',
                style: 'color:#990000;margin:5px 0px;'
            },
            {
                xtype: 'fieldset',
                title: 'Login Example',
                items: [
                    {
                        xtype: 'textfield',
                        placeHolder: 'Username',
                        itemId: 'userNameTextField',
                        name: 'userNameTextField',
                        required: true
                    },
                    {
                        xtype: 'passwordfield',
                        placeHolder: 'Password',
                        itemId: 'passwordTextField',
                        name: 'passwordTextField',
                        required: true
                    }
                ]
            },
            {
                xtype: 'button',
                itemId: 'logInButton',
                ui: 'action',
                padding: '10px',
                text: 'Log In'
            }
         ],
        listeners: [{
            delegate: '#logInButton',
            event: 'tap',
            fn: 'onLogInButtonTap'
        }]
    },
    onLogInButtonTap: function () {

        var me = this,
            usernameField = me.down('#userNameTextField'),
            passwordField = me.down('#passwordTextField'),
            label = me.down('#signInFailedLabel'),
            username = usernameField.getValue(),
            password = passwordField.getValue();

        label.hide();

        // Using a delayed task in order to give the hide animation above
        // time to finish before executing the next steps.
        var task = Ext.create('Ext.util.DelayedTask', function () {

            label.setHtml('');

            me.fireEvent('signInCommand', me, username, password);

            usernameField.setValue('');
            passwordField.setValue('');
        });

        task.delay(500);

    },
    showSignInFailedMessage: function (message) {
        var label = this.down('#signInFailedLabel');
        label.setHtml(message);
        label.show();
    }
});

view/MainMenu.js:

Ext.define('Sample.view.MainMenu', {
    extend: 'Ext.Panel',
    requires: ['Ext.TitleBar'],
    alias: 'widget.mainmenuview',
    config: {
        layout: {
            type: 'fit'
        },
        items: [{
            xtype: 'titlebar',
            title: 'Main Menu',
            docked: 'top',
            items: [
                {
                    xtype: 'button',
                    text: 'Log Off',
                    itemId: 'logOffButton',
                    align: 'right'
                }
            ]
        }],
        listeners: [{
            delegate: '#logOffButton',
            event: 'tap',
            fn: 'onLogOffButtonTap'
        }]
    },
    onLogOffButtonTap: function () {
        this.fireEvent('onSignOffCommand');
    }
});

controller/Login.js:

Ext.define('Sample.controller.Login', {
    extend: 'Ext.app.Controller',
    config: {
        refs: {
            loginView: 'loginview',
            mainMenuView: 'mainmenuview'
        },
        control: {
            loginView: {
                signInCommand: 'onSignInCommand'
            },
            mainMenuView: {
                onSignOffCommand: 'onSignOffCommand'
            }
        }
    },

    // Session token

    sessionToken: null,

    // Transitions
    getSlideLeftTransition: function () {
        return { type: 'slide', direction: 'left' };
    },

    getSlideRightTransition: function () {
        return { type: 'slide', direction: 'right' };
    },

    onSignInCommand: function (view, username, password) {

        console.log('Username: ' + username + '\n' + 'Password: ' + password);

        var me = this,
            loginView = me.getLoginView();

        if (username.length === 0 || password.length === 0) {

            loginView.showSignInFailedMessage('Please enter your username and password.');
            return;
        }

        loginView.setMasked({
            xtype: 'loadmask',
            message: 'Signing In...'
        });

        Ext.Ajax.request({
            url: '../../services/login.ashx',
            method: 'post',
            params: {
                user: username,
                pwd: password
            },
            success: function (response) {

                var loginResponse = Ext.JSON.decode(response.responseText);

                if (loginResponse.success === "true") {
                    // The server will send a token that can be used throughout the app to confirm that the user is authenticated.
                    me.sessionToken = loginResponse.sessionToken;
                    me.signInSuccess();     //Just simulating success.
                } else {
                    me.signInFailure(loginResponse.message);
                }
            },
            failure: function (response) {
                me.sessionToken = null;
                me.signInFailure('Login failed. Please try again later.');
            }
        });
    },

    signInSuccess: function () {
        console.log('Signed in.');
        var loginView = this.getLoginView();
        mainMenuView = this.getMainMenuView();
        loginView.setMasked(false);

        Ext.Viewport.animateActiveItem(mainMenuView, this.getSlideLeftTransition());
    },

    singInFailure: function (message) {
        var loginView = this.getLoginView();
        loginView.showSignInFailedMessage(message);
        loginView.setMasked(false);
    },

    onSignOffCommand: function () {

        var me = this;

        Ext.Ajax.request({
            url: '../../services/logoff.ashx',
            method: 'post',
            params: {
                sessionToken: me.sessionToken
            },
            success: function (response) {

                // TODO: You need to handle this condition.
            },
            failure: function (response) {

                // TODO: You need to handle this condition.
            }
        });

        Ext.Viewport.animateActiveItem(this.getLoginView(), this.getSlideRightTransition());
    }
});

Comments

  1. Bhaskar says

    Thanks for tutorial , i need help in resolving this issue “Getting XMLHttpRequest cannot load file: data.json.Origin null is not allowed by Access-Control-Allow-Origin” , i have tried this “C:\Users\YOUR_USER\AppData\Local\Google\Chrome\Application\chrome.exe –allow-file-access-from-files –disable-web-security” to allow access but still the issue , what might be the problem, thanks in advance.