Building ExtJS Grid Panel User Interface with Spring MVC Rest and MongoDB Backend

Building ExtJS Grid Panel User Interface

In the last tutorial, “Spring MVC RESTful Web Service Example with Spring Data for MongoDB and ExtJS GUI” we covered the configuration and setup of Spring MVC for use as a RESTful API using MongoDB for our datastore. In this post, we will cover ExtJS exclusively.

Getting Started using Spring Data

The primary goal of Spring Data is to make it easy to access both legacy relational databases in addition to new data technologies like NoSQL databases, map reduce frameworks and cloud based solutions. Spring Data for MongoDB is an umbrella project which aim to keep the consistent and familiar manner of the Spring based programming paradigm for new datastores.

In order to run this tutorial yourself, you will need the following:

RESTful Web Service End Points

#URIMethodDescription
1/tutorial/rest/issuersGETReturns a list of all the issuers available in MongoDB
2/tutorial/rest/issuer/{ticker}GETReturn the issuer based on the Ticker in MongoDB
3/tutorial/rest/issuer/delete/{ticker}DELETEDelete the issuer in the MongoDB datastore based on the Ticker
4/tutorial/rest/issuer/update/{ticker}PUTUpdates the issuer in the MongoDB datastore based on the Ticker
5/tutorial/rest/issuer/createPOSTInserts the issuer into the MongoDB datastore based on the contents of the form

Our ExtJS Application

extjs grid populated

Downloading ExtJS

There are several flavors of ExtJS — Commercial Software License, Commercial OEM license and Open Source License (GPLv3).

For this and any other project available on my blog I am using the open source version of their software. I have been using version 2.2 for the longest time and honestly have not downloaded their latest versions. But I am planning on doing that and trying it out as it looks like Sencha has added quite a bit of enhancements to their product.

Sencha is an avid supporter of open source and provide older versions of their products available here. If you want to get their latest version, at the time of this writing it is Sencha ExtJS 5.01

Installing ExtJS

Once you have downloaded ExtJS from Sencha, extract the javascript from the zip file and drop it into your project’s include folder.

extjs project sts

Configuring ExtJS

In order to use ExtJS you need to add the necessary javascript libraries and cascading stylesheets to our html page. In our case, we are using index.jsp page that is HTML5 compliant. Below you will see the sample skeleton structure of what our page would look like.

<!DOCTYPE html>
<html>
  <head>
    <title>Database Driven Grid</title>
    <link rel="stylesheet" href="include/extjs/resources/css/ext-all.css" />
    <link rel="stylesheet" href="include/styles.css">
    <script src="include/extjs/adapter/ext/ext-base.js"></script>
    <script src="include/extjs/ext-all-debug.js"></script>
  </head>
  <body>
     ....
  </body>  

ExtJS as MVC or MVVM

In our previous tutorial we looked at MVC (Model View Controller) from the Spring MVC viewpoint. In this tutorial, we concentrate on MVC from the UIs perspective. One could make a point that even version 2.2 of ExtJS contained this MVC pattern albeit, in much more rudimentary form than it does today. Version 4.0 of ExtJS formally introduced MVC patterns in 2011. Now ExtJS 5.0 has introduced MVVM architectural pattern which takes it to the next level by maintaining the abstraction of a View (the ViewModel) which manages the changes between a Model’s data and the View’s representation of that data and its bindings.

The Model

The model is used to represent the data of the records stored on the Grid Panel. We actually use this model after we add an issuer to the GridPanel. From this simple model you can see that we are only interested in four fields for each issuer: ticker, issuerName, issuerType and country. The actual documents in the MongoDB collection actually contained many more fields, but for the sake of simplicity I decided only to have these four fields.

var ds_model = Ext.data.Record.create([
  'ticker',
  'issuerName',
  'issuerType',
  'country'
]);

The View

The view skeleton code gives you an indication of what will be contained in the final product. I have condensed it here for illustration purposes only. The final code will also contain handlers for the “Add Issuer” and “Remove Issuer” buttons as well as listeners for afteredit events on the Grid itself and code to make the REST API calls and get back results. For now, let just focus on the framework of the view.

var grid = new Ext.grid.EditorGridPanel({
  id:'button-grid',
  store: store,
  cm: new Ext.grid.ColumnModel([
    new Ext.grid.RowNumberer(),
      {header: "Ticker", dataIndex: 'ticker', sortable: true},
      {id: 'name', header: "Issuer Name", dataIndex: 'issuerName', sortable: true, editor: name_edit},
      {header: "Issuer Type", dataIndex: 'issuerType', sortable: true, width: 75, editor: type_edit},
      {header: "Country", dataIndex: 'country', sortable: true, width: 75, editor: country_edit}
    ]),

    selModel: new Ext.grid.RowSelectionModel({
    singleSelect: false
  }),

  viewConfig: {
    forceFit:true
  },

    // inline toolbars
    tbar:[{
      text:'Add Issuer',
      tooltip:'Add a new Issuer',
      icon: 'images/addIssuer16.png',
      cls: 'x-btn-text-icon'
    },{
      text:'Remove Issuer',
      tooltip:'Remove the selected issuer',
      icon: 'images/removeIssuer16.png',
      cls: 'x-btn-text-icon'
    }],

    width: 600,
    height: 350,
    collapsible: true,
    frame: true,
    clicksToEdit: 2,
    animCollapse: false,
    title:'Issuer Grid Panel for MongoDB Access',
    iconCls:'icon-grid',
    renderTo: document.body
   });
});

The Controller

Like I said earlier I am using version 2.2 of ExtJS so I am sort of pushing the limits on MVC design pattern using this version. Until versions 4.0 and greater which formally supports it, this version has the view and controller code sort of intermingled. The controller’s main function is to listen for events and take the appropriate actions. In our example, that means creating any dialog boxes for “Add Issuer” and having a popup dialog box when deleting an issuer to confirm the issuer you are about to delete. In addition, this application supports in-place editing of the record for three out of the four fields. The ticker field is not editable as we don’t want one of our key fields to change in the system once it has been assigned. Think of this key as being used for referential integrity as a foreign key in other tables if we were using a standard relational databases.

Adding an Issuer

handler: function() {
  var form = new Ext.form.FormPanel({
    baseCls: 'x-plain',
    labelWidth: 75,
    name: 'MyForm',
    url: '/tutorial/rest/issuer/addIssuer',
    defaultType: 'textfield',

    items: [{
      fieldLabel: 'Ticker',
      id: 'ticker',
      name: 'ticker',
      xtype: 'textfield',
      maxLength: 10,
      allowBlank:false,
      width: 100,
      listeners: {
        afterrender: function(field) {
          field.focus(false, 200);
        }
      }
    },{
      fieldLabel: 'Issuer Name',
      id: 'issuerName',
      name: 'issuerName',
      allowBlank:false,
      anchor: '100%'  // anchor width by percentage
    }, {
      fieldLabel: 'Issuer Type',
      id: 'issuerType',
      name: 'issuerType',
      maxLength: 10,
      width: 90
    }, {
      fieldLabel: 'Country',
      id: 'country',
      name: 'country',
      maxLength: 20,
      width: 150
    }]
  });

  var window = new Ext.Window({
    title: 'Add New Issuer',
    width: 350,
    height:180,
    minWidth: 350,
    minHeight: 180,
    layout: 'fit',
    plain:true,
    bodyStyle:'padding:5px;',
    buttonAlign:'center',
    resizable: false,
    items: form,

    buttons: [{
        text: 'Save Issuer',
        handler: function () {
          var formTicker = Ext.get('ticker').getValue();
          var formName = Ext.get('issuerName').getValue();
          var formType = Ext.get('issuerType').getValue();
          var formCountry = Ext.get('country').getValue();

          if (form.getForm().isValid()) {
            form.getForm().submit({
              method: 'POST',
              url: '/tutorial/rest/issuer/addIssuer',
              success: function(a, response) {
                grid.getStore().insert(
                  0,
                  new ds_model({
                    ticker: formTicker,
                    issuerName: formName,
                    issuerType: formType,
                    country: formCountry
                  })
               );
               window.close();
            },
            failure: function(a, response) {
              Ext.Msg.alert("Failed", response.result.message);
            }
          });
        }
      }
    },{
        text: 'Cancel',
        handler: function () {
          if (window) {
            window.close();
          }
        }
    }]
  });

  window.show();
}
extjs grid add issuer

The snippet seen above sets up the handler for the Add Issuer functionality. We define a FormPanel containing the four fields in our issuer model and set up the form field characteristics like allow blank fields or not, maximum length and width of the text fields. This Add New Issuer form also contains two buttons, one labeled “Save Issuer” and the other one labeled “Cancel”. After the user fills in the form and hits the Save Issuer button a validation takes place to ensure the form is valid and the mandatory fields have been provided. If they are, the form is submitted using POST as our method to the URI of /tutorial/rest/issuer/addIssuer. Once we get the response asynchronously from the server we will insert the row if it was successful, or display an alert popup with message from the server if it was not.

Updating an Issuer

listeners: {
  afteredit: function(e) {
    var _ticker = e.record.data.ticker;
    var _issuerName = (e.field == 'issuerName') ? e.value : e.record.data.issuerName;
    var _issuerType = (e.field == 'issuerType') ? e.value : e.record.data.issuerType;
    var _country = (e.field == 'country') ? e.value : e.record.data.country;

    var restURL = '/tutorial/rest/issuer/update/' + _ticker;
    var conn = new Ext.data.Connection();
      conn.request({
        url: restURL,
        method: 'PUT',
        params: {
          ticker: _ticker,
          issuerName: _issuerName,
          issuerType: _issuerType,
          country: _country
        },
        success: function(a, response) {
          e.record.commit();
        },
        failure: function(a, response) {
          Ext.Msg.alert("Failed", response.result.message);
          e.record.reject();
        }
      });
    }
  }

This javascript snippet above sets up a listener to capture afteredit events in the gridpanel. The user is allowed to make changes to the issuerName, issuerType and country. Double-clicking on the field will allow for in-place editing and any changes to the fields are immediately sent to our REST api at the /tutorial/rest/issuer/update/{ticker} URI. We send the request asynchronously to the backend and process responses when they arrive. Successful responses will commit the changes to the grid while failures will display an alert popup with the message from the server and reject the changes.

extjs grid update issuer

Deleting an Issuer

In the following javascript code will handle the “Remove Issuer” functionality. Before we can remove an issuer we must ensure that the sure has selected a row on the GridPanel by highlighting it. Once we grab the selected row from the GridPanel we will use the issuerName in the message to alert the user about the impending request to remove the current selected row from the backend data source and the Gridpanel itself. If the user confirms by hitting the ‘Yes’ button, a asynchronous call is made the the URI /tutorial/rest/issuer/delete/{ticker} for processing. If the asynchronous call returns successfully then the row is removed from the grid otherwise an alert popup is displayed with the message “Unable to delete issuer”.

handler: function() {
  var sm = grid.getSelectionModel();
  var sel = sm.getSelected();
  if (sm.hasSelection()) {
    Ext.Msg.show({
      title: 'Remove Issuer',
      buttons: Ext.MessageBox.YESNOCANCEL,
      msg: 'Remove ' + sel.data.issuerName + '?',
      fn: function(btn) {
        if (btn == 'yes') {
          var conn = new Ext.data.Connection();
          var restURL = '/tutorial/rest/issuer/delete/' + sel.data.ticker;
          conn.request({
            method: 'DELETE',
            url: restURL,
            success: function(resp,opt) {
              grid.getStore().remove(sel);
            },
            failure: function(resp,opt) {
              Ext.Msg.alert('Error', 'Unable to delete issuer');
            }
          });
        }
      }
    });
  };
}
extjs grid del issuer

ExtJS 2.2 GUI – Putting It All Together

GUI Web Page (index.jsp)

<!DOCTYPE html>
<html>
<head>
<title>Database Driven Grid</title>
  <link rel="stylesheet" href="include/extjs/resources/css/ext-all.css" />
  <link rel="stylesheet" href="include/styles.css">
  <script src="include/extjs/adapter/ext/ext-base.js"></script>
  <script src="include/extjs/ext-all-debug.js"></script>
  <script>
    Ext.onReady(function() {
      //add data store here
      var store = new Ext.data.Store({
        url: '/tutorial/rest/issuers',
        reader: new Ext.data.JsonReader({
          root: 'issuers',
          id: 'ticker'
        }, [
          'ticker',
          'issuerName',
          'issuerType',
          'country' 
        ])
      });
      store.load();
      
      

      var ds_model = Ext.data.Record.create([
        'ticker',
        'issuerName',
        'issuerType',
        'country'
      ]);
      

      var ticker_edit = new Ext.form.TextField();
      var name_edit = new Ext.form.TextField();
      var type_edit = new Ext.form.TextField();
      var country_edit = new Ext.form.TextField();
      

      var sm2 = new Ext.grid.CheckboxSelectionModel();
      var grid = new Ext.grid.EditorGridPanel({
        id:'button-grid',
        store: store,
        cm: new Ext.grid.ColumnModel([
        new Ext.grid.RowNumberer(),
          {header: "Ticker", dataIndex: 'ticker', sortable: true},
          {id: 'name', header: "Issuer Name", dataIndex: 'issuerName', sortable: true, editor: name_edit},
          {header: "Issuer Type", dataIndex: 'issuerType', sortable: true, width: 75, editor: type_edit},
          {header: "Country", dataIndex: 'country', sortable: true, width: 75, editor: country_edit}
        ]),
            


        selModel: new Ext.grid.RowSelectionModel({
          singleSelect: false
        }),
            


        listeners: {
          afteredit: function(e) {
            var _ticker = e.record.data.ticker;
            var _issuerName = (e.field == 'issuerName') ? e.value : e.record.data.issuerName;
            var _issuerType = (e.field == 'issuerType') ? e.value : e.record.data.issuerType;
            var _country = (e.field == 'country') ? e.value : e.record.data.country;
                

            var restURL = '/tutorial/rest/issuer/update/' + _ticker;
            var conn = new Ext.data.Connection();
              conn.request({
                url: restURL,
                method: 'PUT',
                params: {
                  ticker: _ticker,
                  issuerName: _issuerName,
                  issuerType: _issuerType,
                  country: _country
                },

                success: function(a, response) {
                  e.record.commit();
                },
								
                failure: function(a, response) {
                  Ext.Msg.alert("Failed", response.result.message);
                  e.record.reject();
                }
            });
          }
        },

				viewConfig: {
          forceFit:true
        },

        // inline toolbars
        tbar:[{
            text:'Add Issuer',
                tooltip:'Add a new Issuer',
                icon: 'images/addIssuer16.png',
                cls: 'x-btn-text-icon',
                handler: function() {
                  var form = new Ext.form.FormPanel({
                    baseCls: 'x-plain',
                    labelWidth: 75,
                    name: 'MyForm',
                    url: '/tutorial/rest/issuer/addIssuer',
                    defaultType: 'textfield',

                    items: [{
                        fieldLabel: 'Ticker',
                        id: 'ticker', 

                        name: 'ticker',
                        xtype: 'textfield',
                        maxLength: 10,
                        allowBlank:false,
                        width: 100,
												listeners: {
                          afterrender: function(field) {
                          field.focus(false, 200);
                        }
                      }
                    },{
                        fieldLabel: 'Issuer Name',
                        id: 'issuerName',
                        name: 'issuerName',
                        allowBlank:false,
                        anchor: '100%'  // anchor width by percentage
                    }, {
                      fieldLabel: 'Issuer Type',
                      id: 'issuerType',
                        name: 'issuerType',
                        maxLength: 10,
                        width: 90

                    }, {
                      fieldLabel: 'Country',
                      id: 'country',
                        name: 'country',
                        maxLength: 20,
                        width: 150

                    }]
                });
              

              var window = new Ext.Window({
                    title: 'Add New Issuer',
                    width: 350,
                    height:180,
                    minWidth: 350,
                    minHeight: 180,
                    layout: 'fit',
                    plain:true,
                    bodyStyle:'padding:5px;',
                    buttonAlign:'center',
                    resizable: false,
                    items: form,

                    buttons: [{
                        text: 'Save Issuer',
                        handler: function () {
                          var formTicker = Ext.get('ticker').getValue();
                          var formName = Ext.get('issuerName').getValue();
                          var formType = Ext.get('issuerType').getValue();
                          var formCountry = Ext.get('country').getValue();
                          

                          if (form.getForm().isValid()) {
                            form.getForm().submit({
                            method: 'POST',
                            url: '/tutorial/rest/issuer/addIssuer',

                            success: function(a, response) {
                              grid.getStore().insert(
                              0,
                              new ds_model({
                                ticker: formTicker,
                                issuerName: formName,
                                issuerType: formType,
                                country: formCountry
                              })
                             );
                             window.close();
                            },

                          failure: function(a, response) {
                            Ext.Msg.alert("Failed", response.result.message);
                          }
                        });
                      }
                    }
                  },{
                    text: 'Cancel',
                    handler: function () {
                      if (window) {
                        window.close();
                      }
                    }
                  }]
                });
                window.show();
              }
            },'-',{
                text:'Remove Issuer',
                tooltip:'Remove the selected issuer',
                icon: 'images/removeIssuer16.png',
                cls: 'x-btn-text-icon',
                handler: function() {
                  var sm = grid.getSelectionModel();
                  var sel = sm.getSelected();
                  if (sm.hasSelection()) {
                    Ext.Msg.show({
                      title: 'Remove Issuer',
                      buttons: Ext.MessageBox.YESNOCANCEL,
                      msg: 'Remove ' + sel.data.issuerName + '?',
                      fn: function(btn) {
                        if (btn == 'yes') {
                          var conn = new Ext.data.Connection();
                          var restURL = '/tutorial/rest/issuer/delete/' + sel.data.ticker;
                          conn.request({
                            method: 'DELETE',
                            url: restURL,
 
														success: function(resp,opt) {
                              grid.getStore().remove(sel);
                            },
                            
														failure: function(resp,opt) {
                              Ext.Msg.alert('Error', 'Unable to delete issuer');
                            }
                          });
                        }
                      }
                    });
                  };
                }
            }],

            width: 600,
            height: 350,
            collapsible: true,
            frame: true,
            clicksToEdit: 2,
            animCollapse: false,
            title:'Issuer Grid Panel for MongoDB Access',
            iconCls:'icon-grid',
            renderTo: document.body
        });
    });
  </script>
</head>
<body>
  <h1>Spring RESTful Web Service using mongoDB and extJS Example</h1>
  <br>
  <p>This example uses a REST Web Service that will query the database and generate appropriate JSON for the Grid to load.</p>
  <br>
  <div id="mygrid"></div>
</body>
</html>

Download the Code

That’s It!

I hope you enjoyed this tutorial. It was certainly a lot of fun putting it together and testing it out. Please continue to share the love and like us so that we can continue bringing you quality tutorials. Happy Coding!!!

extjs user interface

Please Share Us on Social Media

Facebooktwittergoogle_plusredditpinterestlinkedinmail

Leave a Reply

Your email address will not be published. Required fields are marked *