RESTful Spring MVC and ExtJs (Episode 2, The ExtJs Stuff)

In the last post we talked about configuring Spring MVC for use as a RESTful API with the intention of using ExtJs for the UI work. In this post, I’ll review the ExtJs configuration work and highlight some of the important aspects of the project. I’ve included an example interface that will work with our previously configured back end. This app will be called ‘Gizmo Manager’. It will exercise the ExtJs MVC components and their RESTful proxies. Again, all of the source for this is on Github, for you to follow along with. On with it!

Installing ExtJs
There are two flavors of ExtJs – the open source version and the commercial version. For this project, I’m using the open source version (obviously)- 4.1.1 – you’ll notice I don’t have it checked in. The download(s) are available from Sencha, and although I haven’t used it, there is a copy on the Sencha CDN here. For this example, I’ve downloaded it and dropped it into my web project’s ‘js’ folder.
ExtJs install

Configuring ExtJs
To get the foundation for our app created, we’ll want to update our index.html file to reference both the ExtJs distribution, and our app (which will soon be created). Here’s what mine looks like when I’m done:

<html>
  <head>
    <title>Gizmo Manager</title>
    <link rel="stylesheet" type="text/css" href="js/extjs/resources/css/ext-neptune-debug.css">
    <script type="text/javascript" src="js/extjs/ext-all-debug.js"></script>
    <script type="text/javascript" src="js/app.js"></script>
  </head>
  <body></body>
</html>

You’ll notice we’ve added two items related to our home page – a path to ExtJs itself (ext-all-debug.js), and a stylesheet reference. There are already-minified versions of ExtJs available in the distro, but for this example, I’m using the debug version as it is easier to troubleshoot configuration problems using it. As for the stylesheet, Neptune is one of the few themes that ExtJs comes packed with. They each have drawbacks, but I think Neptune has the most modern look and feel.

Next we’ll create our application’s folder structure. You’ll notice in the screenshot above a folder named ‘app’ and a file named ‘app.js’. These are both located in the /web/js folder, right alongside my extjs install. Our app.js file contains our application’s bootstrap information. This is how it looks:

Ext.application({
    name: 'gizmoManager',
    appFolder: 'js/app',
    controllers: ['GizmoController'],
    launch: function() {
        Ext.create('gizmoManager.view.MainViewport');
    }
});

You’ll notice some important items in this file. This is where we name our app and let ExtJs know where the source is located. We also give ExtJs a list of controllers to set up, as well as a view to create on launch. In this example, we’re using the MVC capabilities of ExtJs, so if you’re familiar with any other MVC framework, this should be easy to follow along with, at least structurally.

Our app directory has four folders in it – one each for controllers, models, views, and stores – all layers of the ExtJs flavor of MVC.
folders

Creating the Application
Let’s follow the ExtJs stack from front to back. Remember the default view we added to our app.js file? This is where our applications view components will be displayed. If we had multiple views in our app, our default view might be a card layout or a tab panel, where multiple views could be managed. In our app, we’ll configure a single view – more on that shortly. Here is our default view, the MainViewport.js, with it’s reference to our app’s single view component.

Ext.define('gizmoManager.view.MainViewport', {
    extend : 'Ext.container.Viewport',
    alias : 'widget.mainViewport',
    requires: [
        'gizmoManager.view.MainContainer'
    ],
    items : [{
        xtype : 'mainContainer'
    }]
});

In this file, you’ll notice two references to our app’s single view component – once in a parameter called ‘requires’ and once in a parameter called ‘items’. Think of items listed in the ‘requires’ parameter to ‘imported’ (in java terms) in to this file. Items in the ‘items’ parameter are displayed items, in the order they appear in the ‘items’ array.

The View
Since our ‘MainContainer’ is our application’s single view file, and our goal is exercise all of the RESTful API we created in our last post, you may think that it’s going to be very complicated. It’s really not! Adapting the gridpanel example from ExtJs docs, we’ll have an editable grid, which we can use to add, remove, update ‘Gizmos’. This file is called MainContainer.js, and is located in the root of the ‘view’ directory, along with MainViewPort.js. Our MainContainer.js file looks like this:

Ext.define('gizmoManager.view.MainContainer', {
    extend: 'Ext.tab.Panel',
    alias: 'widget.mainContainer',
    title: 'Gizmo Manager',
    items:[
        {
            xtype: 'gridpanel',
            title: 'Gizmos',
            store: 'Gizmo',
            name: 'editableGrid',
            columns: [
                {text: 'ID', dataIndex: 'id', width: 300},
                {text: 'DESCRIPTION', dataIndex: 'description', width: 300,
                    editor: {
                        xtype: 'textfield',
                        allowBlank: false
                    }}
            ],
            columnLines: true,
            selModel: 'rowmodel',
            plugins: [
                Ext.create('Ext.grid.plugin.RowEditing', {
                    clicksToEdit: 1
                })
            ],
            dockedItems: [{
                xtype: 'toolbar',
                items: [{
                    action: 'add',
                    text:'Add Something'
                },'-',{
                    action: 'remove',
                    text:'Remove Something',
                    disabled: true
                }]
            }],

            width: 600,
            height: 300
        }
    ]
});

You can see we create our grid and add columns to display Gizmo data, we have added the row editing plugin for our grid panel (more info in the ExtJs docs on those topics), and we have also added a couple of buttons to the grid’s toolbar – one to remove items and another to add them. The buttons, the grid itself, and the rest of this don’t do much without the rest of the stack.

The Controller
The controller wires the stack together, serving as the middle man between the view and store/model, just as it would in any other MVC framework. We’ll named it GizmoController.js, and put it in our ExtJs app’s ‘controller’ folder. The controller’s main function in this example is listening to view events and interacting with our store/model. Overall, this is a pretty basic example, but again, exercises our whole API.

Ext.define('gizmoManager.controller.GizmoController', {
    extend: 'Ext.app.Controller',
    stores: ['Gizmo'],
    views: ['MainContainer'],

    refs:[
        {
            ref: 'mainContainer',
            selector: 'mainContainer'
        },
        {
            ref: 'editableGrid',
            selector: 'panel > gridpanel[name=editableGrid]'
        }
    ],

    init: function () {
        this.control({
            'mainContainer > gridpanel[name=editableGrid]' : {
                edit: function(editor, object) {
                    object.store.save();
                    object.store.commitChanges();
                },
                selectionchange: function(current, selections) {
                    this.getEditableGrid().down('button[action=remove]').setDisabled(selections.length == 0);
                }
            },
            'mainContainer > gridpanel > toolbar > button[action=add]' : {
                click: function(button) {
                    this.getGizmoStore().add(Ext.create('gizmoManager.model.Gizmo'));
                }
            },
            'mainContainer > gridpanel > toolbar > button[action=remove]' : {
                click: function(button) {
                    var selection = this.getEditableGrid().getSelectionModel(),
                        me = this;
                    Ext.each(selection.selected.items, function(gizmo) {
                        me.getGizmoStore().remove(gizmo);
                    });
                    me.getGizmoStore().sync();
                }
            }
        });
        this.getGizmoStore().load();
    }
});

In the ‘init’ property of the controller, we’ll see the four methods of our API as they exist in code. For the parameter-less GET (or ‘index’ in Grails/Rails terms), we use our GizmoStore’s load method. Our controller has a reference to the GizmoStore in it’s store array. This gives us an accessor to the store for free.

this.getGizmoStore().load();

For deletions, you can see the listener on the remove button’s click method:

'mainContainer > gridpanel > toolbar > button[action=remove]' : {
                click: function(button) {
                    var selection = this.getEditableGrid().getSelectionModel(),
                        me = this;
                    Ext.each(selection.selected.items, function(gizmo) {
                        me.getGizmoStore().remove(gizmo);
                    });
                    me.getGizmoStore().sync();
                }
            }

In this case, we’re using the control block, providing a path to the element we’d like to control (in this case, the delete button), and listening for the click method there. Again, you can see accessors available here – getting the editable grid – but this one is not free. In the controller, you’ll see the ‘refs’ parameter. The refs parameter allows us to define and name an array of objects for which we’d like accessor methods. In this case, we’ve got accessor for our view, and our view’s grid. Two other important things here: you can see the call to the GizmoStore’s remove button, followed by a call to the sync method.

The Model
The model (Gizmo.js) is stored in our app’s model folder. The model is used as a value object for the data going to and coming from our API. You’ll notice this is not a fat model like an ActiveRecord or a Model in Grails.

Ext.define('gizmoManager.model.Gizmo', {
    extend: 'Ext.data.Model',
    fields: ['id','description']
});

Not much more than the field config in our example, but more complex apps include validation information, proxy references, parent/child relationship configuration and much more. You can see the names of the fields in our model match with the ‘dataIndex’ parameters of the columns in our view. This is how our model is correctly bound to the grid.

The Store
The store is a singleton and maintains the client-side cache in ExtJs MVC applications. Our store contains a configured proxy through which the calls to our API are made. There are many convenient methods on the store for dealing with data in memory – comprehensions, finders, transformers, etc. In our example app, we’ll just be using the store for it’s proxy and synchronizing immediately after changes (see controller above).

Ext.define('gizmoManager.store.Gizmo', {
    extend: 'Ext.data.Store',
    model: 'gizmoManager.model.Gizmo',
    proxy: {
        type: 'rest',
        url : 'spring/gizmos',
        reader:{
            type: 'json',
            root: 'gizmos'
        }
    }
});

Above you can see our store’s configuration. The store contains a reference to the Gizmo model and a configured proxy. You’ll notice the proxy configuration points to our API, and is of the type ‘rest’. The ‘rest’ type proxy automatically makes calls to our API when we call methods like ‘load’ and ‘save’ and our store. Much more information in the ExtJs docs on that topic in the Ext.data.proxy.Rest class.

Run!
If you’ve been following along (or just skipped to downloading the project on Github), we should be able to run the app at this point. Here is what we should see:
Gizmo Manager
Click around and delete, edit, and add new Gizmos.

I hope this helped you get going faster than I did. Next up, design patterns in Scala!

Advertisements

5 comments

  1. This will not working i thing. When you add new record, “Ext.create(‘gizmoManager.model.Gizmo’)”, you will get requestbody like:{id:”gizmoManager.model.Gizmo-1″, description:”text”} and your server site throw exception, because “id” is not Integer.

  2. Could you Please help me in resolving this error.

    I created a project with your code.
    Uncaught Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. Missing required class: gizmoManager.controller.GizmoController

    Thanking You.

      1. I still the same issue : ext-all-debug.js:5788 Uncaught Error: Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. Missing required class: gizmoManager.controller.GizmoController. I am using extjs 4.1.1

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s