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. 

Sunday, February 12, 2012

Running Emma with jdk7 produces "expecting a stackmap frame at branch ..."

Hey everyone.

Today while integrating adding a code coverage build step to integrate into Jenkins (using Emma), a test failed with this error:

Expecting a stackmap frame at branch target 11 in method model.bean.Car.setId(Ljava/lang/Long;)V at offset 4

... Right. And it only failed on the build server (not my machine) What on earth does that mean? Honestly, I have no idea. This post on stackoverflow suggested it was something to do with our build server running jdk7 that the binary distribution of emma did not like (2.0.5312). So, we reverted back to 1.6. That fixed the problem.

I'll probably explore that more in detail sometime, but in the meantime that's the solution.

Saturday, February 11, 2012

jqPlot: crash/hang/infinite loop in the DateAxisRenderer

Hi everyone,

This seems oddly specific. If you've tried using the DateAxisRenderer in jqPlot with a single data point, or multiple data points with the same y value, you might have found yourself stuck with a browser crash.

For example, see this question on stackoverflow or the issue tracker.

Well, the problem has a partial fix which is now in the codebase. I believe there are still some cases where there might be a problem so it's being worked on.

Until its released, you can incorporate the changes into dateAxisRenderer.js by copying them from here. 

Wednesday, February 8, 2012

Local Mercurial repositories on top of other SCM systems

Hi everyone,

Big fan of Mercurial here. Enough that going to other SCM systems is seen as an unfortunate experience. For example, where I worked as an intern we use TFS. Now TFS is much more than source control but I personally don't like its approach to the source control aspect (also: I'm starting to dislike source control plugins to the IDE, but that's another day).

One of the policies is that every commit must have a separate code reviewer. That's fine, but for me made commits a hassle, especially since I started working outside normal hours (and was given limited commit access in the beginning as an intern).

Anyway, the solution is simple: I create a local Hg repository in the directory the project's checked out in and commit to that instead. Also be sure to clone it to a not-your-desktop-which-could-randomly-explode location.

The good news is that this is a very simple process. The bad news is that you of course don't gain the advantage of keeping the Hg commit history like you would with a more complex tool for this like hg-svn or something. Of course in my situation that would get the same problem of requiring reviewers.

The main use for this was to be able to commit frequently and have snapshots of the progress instead of having to, for example, shelve it in TFS. As long as you make sure to clone it somewhere that's not your local machine as well, you should be good to go.

Another time this makes sense is where the rest of your team is used to SVN and the learning curve of another source control system is too much of a pain right now.

Friday, February 3, 2012

Don't forget your server side validation

Hey everyone,

So: the project we're maintaining is filled with JavaScript on the client. One of the things I make sure happens is that everything gets validated server-side even with those fancy shiny JavaScript validators, and we incorporate that requirement into our automated tests. This was missing from some parts when we first took over the project.

Anyway, some projects don't keep that in mind. Today a friend of mine tried to submit something past the specified date, which is enforced by the web application. Said web application enforces this client-side with a crusty snippet of JavaScript code and a hidden form element. Well, surely they would do a check on the server as well, instead of rely on this fairly crude method? No, and with tools like FireBug and friends it's trivial to work around.

You just can't trust the client. I love JavaScript as well, but you still better be validating it from a trusted spot.


See you next time!