Friday, February 17, 2012

Simple example of a Sencha ExtJS 4 login form using MVC

Hey guys.

For my senior design project I've been working with Sencha's ExtJS 4 to develop a web application. Now, I wouldn't normally try and write a beginner tutorial for something like this because I believe there are plenty of badly written beginner's tutorials all over the place that you could find much easier. But I'm going to post what I've been doing with this framework for two reasons:
  • Most tutorials I see seem contrived and don't employ any kind of separation. I'm using an MVC architecture and had trouble finding examples. 
  • Some references in Sencha's documentation seem to be wrong. Apparently their API has gone through several major revisions. 
And I'd like to go ahead and state that this is how I got it working, it might not be the best way to do so. In fact, if you know a better way to do something, feel free to let me know.

The goal here is simple: A user should be able to log in, see some indication that they've been logged in, and log out. Here's what that looks like:
Not the most amazing login system...
Pretty crude, but the important part is getting it functional. Here you see that the user can log in our out. There's also an entry below the form that displays the currently logged in user. Normally if they're logged in the "login" button doesn't show, but this is from an earlier version.

So, to the right I've given you the directory structure so you can see what it looks like -- as you can see it's a pretty straight forward MVC layout.

A short disclaimer: I've put together this example by ripping off various examples in Sencha's documentation, so if you see some dangling reference to them that's why.

Alright, let's start with the store and model. First, here's the model:

Ext.define('AM.model.User', {
    extend: 'Ext.data.Model',
    fields: ['username', 'isAdmin', 'authenticated', 'loggedOut']
});


Nothing too exciting, this represents the current user. Here's the corresponding store:

Ext.define('AM.store.Users', {
    extend: 'Ext.data.Store',
    model: 'AM.model.User',

autoLoad: true,

proxy: {
    type: 'ajax',
    url: 'login?view=sencha&json=true',
    method: 'GET',
    reader: {
        type: 'json',
        root: 'model',
        successProperty: 'model.success'
    }
}

});



As you can see, the store is configured to ask the server for the user data.

Ok, now the views. The first view is the actual form:

Ext.define('AM.view.login.Form' ,{
    extend: 'Ext.form.FormPanel',
    alias : 'widget.loginform',

    name: 'loginform',
    frame: true,
    title: 'Password Verification',
    bodyPadding: '5px 5px 0',
    width: 350,
    height: 150,
    fieldDefaults: {
        labelWidth: 125,
        msgTarget: 'side',
        autoFitErrors: false
    },
    defaults: {
        width: 300,
        inputType: 'password'
    },
    defaultType: 'textfield',
   
    initComponent: function() {
        this.buttons = [
        {
        name: 'loginButton',
            text: 'Login',
            action: 'login'
        },
        {
        name: 'logoutButton',
        text: 'Logout',
        action: 'logout',
        visible: false
        }
        ];
       
        this.items = [
        {
            fieldLabel: 'Username',
            name: 'username',
            id: 'username',
            inputType: 'text'
        },
        {
            fieldLabel: 'Password',
            name: 'password'
        }
        ];
       
        this.callParent(arguments);
    }
});



By the way, I ran across a tutorial in the documentation referencing Ext.FormPanel, but Ext.form.FormPanel seems to be correct.

Here's the other view that just lists the currently logged in user:

Ext.define('AM.view.login.Display' ,{
    extend: 'Ext.grid.Panel',
    alias : 'widget.logindisplay',

    title : 'Users',
   
    initComponent: function() {
        this.store = 'Users',

        this.columns = [
            {header: 'username',  dataIndex: 'username',  flex: 1},
            {header: 'authenticated',  dataIndex: 'authenticated',  flex: 1}
        ];
  
        this.callParent(arguments);
    }
});



Alright, now the fun part: the controller. First, here it is:


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

    models: ['User'],
    stores: ['Users'],
    views: [ 'login.Form', 'login.Display'],
   
    refs: [
           {
               ref: 'loginForm',
               selector: 'form'
           },
           {
           ref: 'loginButton',
           selector: 'loginform button[action=login]'
           },
           {
           ref: 'logoutButton',
           selector: 'loginform button[action=logout]'
           }
       ],
   
    init: function() {
        this.control({
        'loginform button[action=logout]': {
        click: function(button)
        {
           var store = this.getUsersStore();
           var logoutButton = button;
           var loginButton = this.getLoginButton();
        
           this.getLoginForm().form.submit({
                        waitMsg:'Loading...',
                        url: 'login',
                        method: 'POST',
                        success: function(form,action) {                        
                           Ext.MessageBox.alert('Logged out', 'You have been logged out');
                           logoutButton.setVisible(false);
                           store.load();
                           store.sync();
                           loginButton.setVisible(true);
                        },
                        params:
                        {
                           view: 'sencha',
                           json: true,
                           logout: true
                        }
              });
            }
        },
        
        'loginform button[action=login]': {
            click: function(button)
            {
            var store = this.getUsersStore();             
            var loginButton = button;
            var logoutButton = this.getLogoutButton();
            
            this.getLoginForm().form.submit({
                        waitMsg:'Loading...',
                        url: 'login',
                        method: 'POST',
                        success: function(form,action) {                        
                            store.load();
                            store.sync();
                            loginButton.setVisible(false);
                            logoutButton.setVisible(true);
                        },
                        failure: function(form,action){
                            Ext.MessageBox.alert('Error', "Invalid username/password");
                        },
                        params:
                        {
                           view: 'sencha',
                           json: true
                        }
               });
            }
            }
        });
    }
});


I didn't have much success on finding information about referencing views from the controller. Most examples mix the logic with the view so it's not an issue. In the end I ended up using refs to obtain the form and its buttons.

There's not much going on here actually. When the user clicks 'Login,' we pass the information to the server and then synchronize the store. If the user is logged in, the server will pass back the user's information. To log out, the parameter is passed to log out and the server clears the user. When the store is synchronized, the user will no longer be in it.

Oh, and here's app.js: 



Ext.Loader.setConfig({enabled:true});

Ext.application
({
    name: 'AM',

    appFolder: 'js/sencha/app',  /* this appears to NOT be a relative path */    

    controllers: ['Login'],   
   
    launch: function() {    
    console.log("init");
    
        Ext.create('Ext.Panel', {
            layout: 'fit',
            renderTo: 'login',
            items: [
                {
                    xtype:'loginform'                 
                },
                {
                    xtype:'logindisplay'
                }
            ]
        });
    }
});

So that's pretty much it. Again, I'm sticking this up here because I had problems finding examples that suited my needs. Most of them mixed the view/controller logic which made referencing components a lot easier. For this project however, we're using an MVC architecture so that didn't help.

See you next time. 

3 comments:

  1. Hi i tried same thing but i get this error.
    Can you help me on this?
    ERROR:
    Uncaught Error: The following classes are not declared even if their files have been loaded: 'Ext.grid.Panel'. Please check the source code of their corresponding files for possible typos: 'sdk/src/grid/Panel.js

    ReplyDelete
  2. Wonder if you have your sample code in one place for us to download and try right away.

    ReplyDelete
  3. Can you give a link to download your code ?

    ReplyDelete