28 min read

In this article by Loiane Groner, author of the book Mastering Ext JS, Second Edition, we will start implementing the application’s core features, starting with static data management. What exactly is this? Every application has information that is not directly related to the core business, but this information is used by the core business logic somehow.

There are two types of data in every application: static data and dynamic data. For example, the types of categories, languages, cities, and countries can exist independently of the core business and can be used by the core business information as well; this is what we call static data because it does not change very often. And there is the dynamic data, which is the information that changes in the application, what we call core business data. Clients, orders, and sales would be examples of dynamic or core business data.

We can treat this static information as though they are independent MySQL tables (since we are using MySQL as the database server), and we can perform all the actions we can do on a MySQL table.

(For more resources related to this topic, see here.)

Creating a Model

As usual, we are going to start by creating the Models. First, let’s list the tables we will be working with and their columns:

  • Actor: actor_id, first_name, last_name, last_update
  • Category: category_id, name, last_update
  • Language: language_id, name, last_update
  • City: city_id, city, country_id, last_update
  • Country: country_id, country, last_update

We could create one Model for each of these entities with no problem at all; however, we want to reuse as much code as possible. Take another look at the list of tables and their columns. Notice that all tables have one column in common—the last_update column.

All the previous tables have the last_update column in common. That being said, we can create a super model that contains this field. When we implement the actor and category models, we can extend the super Model, in which case we do not need to declare the column. Don’t you think?

Abstract Model

In OOP, there is a concept called inheritance, which is a way to reuse the code of existing objects. Ext JS uses an OOP approach, so we can apply the same concept in Ext JS applications. If you take a look back at the code we already implemented, you will notice that we are already applying inheritance in most of our classes (with the exception of the util package), but we are creating classes that inherit from Ext JS classes. Now, we will start creating our own super classes.

As all the models that we will be working with have the last_update column in common (if you take a look, all the Sakila tables have this column), we can create a super Model with this field. So, we will create a new file under app/model/staticData named Base.js:

Ext.define('Packt.model.staticData.Base', {
    extend: 'Packt.model.Base', //#1
 
    fields: [
        {
            name: 'last_update',
            type: 'date',
            dateFormat: 'Y-m-j H:i:s'
        }
    ]
});

This Model has only one column, that is, last_update. On the tables, the last_update column has the type timestamp, so the type of the field needs to be date, and we will also apply date format: Y-m-j H:i:s‘, which is years, months, days, hours, minutes, and seconds, following the same format as we have in the database (2006-02-15 04:34:33).

When we can create each Model representing the tables, we will not need to declare the last_update field again.

Look again at the code at line #1. We are not extending the default Ext.data.Model class, but another Base class (security.Base).

Adapting the Base Model schema

Create a file named Base.js inside the app/model folder with the following content in it:

Ext.define('Packt.model.Base', {
    extend: 'Ext.data.Model',
 
    requires: [
        'Packt.util.Util'
    ],
 
    schema: {
        namespace: 'Packt.model', //#1
        urlPrefix: 'php',
        proxy: {
            type: 'ajax',
            api :{
                read : '{prefix}/{entityName:lowercase}/list.php',
                create:
                    '{prefix}/{entityName:lowercase}/create.php',
                update:
                    '{prefix}/{entityName:lowercase}/update.php',
                destroy:
                    '{prefix}/{entityName:lowercase}/destroy.php'
            },
            reader: {
                type: 'json',
                rootProperty: 'data'
            },
            writer: {
                type: 'json',
                writeAllFields: true,
                encode: true,
                rootProperty: 'data',
                allowSingle: false
            },
            listeners: {
                exception: function(proxy, response, operation){
              Packt.util.Util.showErrorMsg(response.responseText);
                }
            }
        }
    }
});

Instead of using Packt.model.security, we are going to use only Packt.model.

The Packt.model.security.Base class will look simpler now as follows:

Ext.define('Packt.model.security.Base', {
    extend: 'Packt.model.Base',
 
    idProperty: 'id',
 
    fields: [
        { name: 'id', type: 'int' }
    ]
});

It is very similar to the staticData.Base Model we are creating for this article. The difference is in the field that is common for the staticData package (last_update) and security package (id).

Having a single schema for the application now means entityName of the Models will be created based on their name after ‘Packt.model’. This means that the User and Group models we created will have entityName security.User, and security.Group respectively. However, we do not want to break the code we have implemented already, and for this reason we want the User and Group Model classes to have the entity name as User and Group. We can do this by adding entityName: ‘User’ to the User Model and entityName: ‘Group’ to the Group Model. We will do the same for the specific models we will be creating next.

Having a super Base Model for all models within the application means our models will follow a pattern. The proxy template is also common for all models, and this means our server-side code will also follow a pattern. This is good to organize the application and for future maintenance.

Specific models

Now we can create all the models representing each table. Let’s start with the Actor Model. We will create a new class named Packt.model.staticData.Actor; therefore, we need to create a new file name Actor.js under app/model/staticData, as follows:

Ext.define('Packt.model.staticData.Actor', {
    extend: 'Packt.model.staticData.Base', //#1
 
    entityName: 'Actor', //#2
 
    idProperty: 'actor_id', //#3
 
    fields: [
        { name: 'actor_id' },
        { name: 'first_name'},
        { name: 'last_name'}
    ]
});

There are three important things we need to note in the preceding code:

  • This Model is extending (#1) from the Packt.model.staticData.Base class, which extends from the Packt.model.Base class, which in turn extends from the Ext.data.Model class. This means this Model inherits all the attributes and behavior from the classes Packt.model.staticData.Base, Packt.model.Base, and Ext.data.Model.
  • As we created a super Model with the schema Packt.model, the default entityName created for this Model would be staticData.Actor. We are using entityName to help the proxy compile the url template with entityName. To make our life easier we are going to overwrite entityName as well (#2).
  • The third point is idProperty (#3). By default, idProperty has the value “id“. This means that when we declare a Model with a field named “id“, Ext JS already knows that this is the unique field of this Model. When it is different from “id“, we need to specify it using the idProperty configuration. As all Sakila tables do not have a unique field called “id“—it is always the name of the entity + “_id“—we will need to declare this configuration in all models.

Now we can do the same for the other models. We need to create four more classes:

  • Packt.model.staticData.Category
  • Packt.model.staticData.Language
  • Packt.model.staticData.City
  • Packt.model.staticData.Country

At the end, we will have six Model classes (one super Model and five specific models) created inside the app/model/staticData package. If we create a UML-class diagram for the Model classes, we will have the following diagram:

The Actor, Category, Language, City, and Country Models extend the Packt.model.staticData Base Model, which extends from Packt.model.Base, which in turn extends the Ext.data.Model class.

Creating a Store

The next step is to create the storesfor each Model. As we did with the Model, we will try to create a generic Storeas well (in this article, will create a generic code for all screens, so creating a super Model, Store, and View is part of the capability). Although the common configurations are not in the Store, but in the Proxy(which we declared inside the schema in the Packt.model.Base class), having a super Store class can help us to listen to events that are common for all the static data stores.

We will create a super Store named Packt.store.staticData.Base.

As we need a Store for each Model, we will create the following stores:

  • Packt.store.staticData.Actors
  • Packt.store.staticData.Categories
  • Packt.store.staticData.Languages
  • Packt.store.staticData.Cities
  • Packt.store.staticData.Countries

At the end of this topic, we will have created all the previous classes. If we create a UML diagram for them, we will have something like the following diagram:

All the Store classes extend from the Base Store.

Now that we know what we need to create, let’s get our hands dirty!

Abstract Store

The first class we need to create is the Packt.store.staticData.Base class. Inside this class, we will only declare autoLoad as true so that all the subclasses of this Store can be loaded when the application launches:

Ext.define('Packt.store.staticData.Base', {
    extend: 'Ext.data.Store',
 
    autoLoad: true
});

All the specific stores that we will create will extend this Store. Creating a super Store like this can feel pointless; however, we do not know that during future maintenance, we will need to add some common Store configuration.

As we will use MVC for this module, another reason is that inside the Controller, we can also listen to Store events (available since Ext JS 4.2). If we want to listen to the same event of a set of stores and we execute exactly the same method, having a super Store will save us some lines of code.

Specific Store

Our next step is to implement the Actors, Categories, Languages, Cities, and Countries stores.

So let’s start with the Actors Store:

Ext.define('Packt.store.staticData.Actors', {
    extend: 'Packt.store.staticData.Base', //#1
 
    model: 'Packt.model.staticData.Actor' //#2
});

After the definition of the Store, we need to extend from the Ext JS Store class. As we are using a super Store, we can extend directly from the super Store (#1), which means extending from the Packt.store.staticData.Base class.

Next, we need to declare the fields or the model that this Store is going to represent. In our case, we always declare the Model (#2).

Using a model inside the Store is good for reuse purposes. The fields configuration is recommended just in case we need to create a very specific Store with specific data that we are not planning to reuse throughout the application, as in a chart or a report.

For the other stores, the only thing that is going to be different is the name of the Store and the Model.

However, if you need the code to compare with yours or simply want to get the complete source code, you can download the code bundle from this book or get it at https://github.com/loiane/masteringextjs.

Creating an abstract GridPanel for reuse

Now is the time to implement the views. We have to implement five views: one to perform the CRUD operations for Actor, one for Category, one for Language, one for City, and one for Country.

The following screenshot represents the final result we want to achieve after implementing the Actors screen:

And the following screenshot represents the final result we want to achieve after implementing the Categories screen:

Did you notice anything similar between these two screens? Let’s take a look again:

The top toolbar is the same (1); there is a Live Search capability (2); there is a filter plugin (4), and the Last Update and widget columns are also common (3). Going a little bit further, both GridPanels can be edited using a cell editor(similar to MS Excel capabilities, where you can edit a single cell by clicking on it). The only things different between these two screens are the columns that are specific to each screen (5). Does this mean we can reuse a good part of the code if we use inheritance by creating a super GridPanel with all these common capabilities? Yes!

So this is what we are going to do. So let’s create a new class named Packt.view.staticData.BaseGrid, as follows:

Ext.define('Packt.view.staticData.BaseGrid', {
    extend: 'Ext.ux.LiveSearchGridPanel', //#1
    xtype: 'staticdatagrid',
 
    requires: [
        'Packt.util.Glyphs' //#2
    ],
 
    columnLines: true,    //#3
    viewConfig: {
        stripeRows: true //#4
    },
 
    //more code here
});   

We will extend the Ext.ux.LiveSearchGridPanel class instead of Ext.grid.Panel. The Ext.ux.LiveSearchGridPanel class already extends the Ext.grid.Panel class and also adds the Live Search toolbar (2). The LiveSearchGridPanel class is a plugin that is distributed with the Ext JS SDK. So, we do not need to worry about adding it manually to our project (you will learn how to add third-party plugins to the project later in this book).

As we will also add a toolbar with the Add, Save Changes, Cancel Changes buttons, we need to require the util.Glyphs class we created (#2).

The configurations #3 and #4 show the border of each cell of the grid and to alternate between a white background and a light gray background.

Likewise, any other component that is responsible for displaying information in Ext JS, such as the “Panel” piece is only the shell. The View is responsible for displaying the columns in a GridPanel. We can customize it using the viewConfig (#4).

The next step is to create an initComponent method.

To initComponent or not?

While browsing other developers’ code, we might see some using the initComponent when declaring an Ext JS class and some who do not (as we have done until now). So what is the difference between using it and not using it?

When declaring an Ext JS class, we usually configure it according to the application needs. They might become either a parent class for other classes or not. If they become a parent class, some of the configurations will be overridden, while some will not. Usually, we declare the ones that we expect to override in the class as configurations. We declare inside the initComponent method the ones we do not want to be overridden.

As there are a few configurations we do not want to be overridden, we will declare them inside the initComponent, as follows:

initComponent: function() {
    var me = this;
 
    me.selModel = {
        selType: 'cellmodel' //#5
    };
 
    me.plugins = [
        {
            ptype: 'cellediting',  //#6
            clicksToEdit: 1,
            pluginId: 'cellplugin'
        },
        {
            ptype: 'gridfilters'  //#7
        }
    ];
 
    //docked items
 
    //columns
 
    me.callParent(arguments); //#8
}

We can define how the user can select information from the GridPanel: the default configuration is the Selection RowModel class. As we want the user to be able to edit cell by cell, we will use the Selection CellModel class (#5) and also the CellEditing plugin (#6), which is part of the Ext JS SDK. For the CellEditing plugin, we configure the cell to be available to edit when the user clicks on the cell (if we need the user to double-click, we can change to clicksToEdit: 2). To help us later in the Controller, we also assign an ID to this plugin.

To be able to filter the information (the Live Search will only highlight the matching records), we will use the Filters plugin (#7). The Filters plugin is also part of the Ext JS SDK.

The callParent method (#8) will call initConfig from the superclass Ext.ux.LiveSearchGridPanel passing the arguments we defined.

It is a common mistake to forget to include the callParent call when overriding the initComponent method. If the component does not work, make sure you are calling the callParent method!

Next, we are going to declare dockedItems. As all GridPanels will have the same toolbar, we can declare dockedItems in the super class we are creating, as follows:

me.dockedItems = [
    {
        xtype: 'toolbar',
        dock: 'top',
        itemId: 'topToolbar', //#9
        items: [
            {
                xtype: 'button',
                itemId: 'add', //#10
                text: 'Add',
                glyph: Packt.util.Glyphs.getGlyph('add')
            },
            {
                xtype: 'tbseparator'
            },
            {
                xtype: 'button',
                itemId: 'save',
                text: 'Save Changes',
                glyph: Packt.util.Glyphs.getGlyph('saveAll')
            },
            {
                xtype: 'button',
                itemId: 'cancel',
                text: 'Cancel Changes',
                glyph: Packt.util.Glyphs.getGlyph('cancel')
            },
            {
                xtype: 'tbseparator'
            },
            {
                xtype: 'button',
                itemId: 'clearFilter',
                text: 'Clear Filters',
                glyph: Packt.util.Glyphs.getGlyph('clearFilter')
            }
        ]
    }
];

We will have Add, Save Changes, Cancel Changes, and Clear Filters buttons. Note that the toolbar (#9) and each of the buttons (#10) has itemId declared. As we are going to use the MVC approach in this example, we will declare a Controller. The itemId configuration has a responsibility similar to the reference that we declare when working with a ViewController. We will discuss the importance of itemId more when we declare the Controller.

When declaring buttons inside a toolbar, we can omit the xtype: ‘button’ configuration since the button is the default component for toolbars.

Inside the Glyphs class, we need to add the following attributes inside its config:

saveAll: 'xf0c7',
clearFilter: 'xf0b0'

And finally, we will add the two columns that are common for all the screens (Last Update column and Widget Column delete (#13)) along with the columns already declared in each specific GridPanel:

me.columns = Ext.Array.merge( //#11
    me.columns,               //#12
    [{
        xtype    : 'datecolumn',
        text     : 'Last Update',
        width    : 150,
        dataIndex: 'last_update',
        format: 'Y-m-j H:i:s',
        filter: true
    },
    {
        xtype: 'widgetcolumn', //#13
        width: 45,
        sortable: false,       //#14
        menuDisabled: true,    //#15
        itemId: 'delete',
        widget: {
            xtype: 'button',   //#16
            glyph: Packt.util.Glyphs.getGlyph('destroy'),
            tooltip: 'Delete',
            scope: me,                //#17
            handler: function(btn) {  //#18
                me.fireEvent('widgetclick', me, btn);
            }
        }
    }]
);

In the preceding code we merge (#11) me.columns (#12) with two other columns and assign this value to me.columns again. We want all child grids to have these two columns plus the specific columns for each child grid. If the columns configuration from the BaseGrid class were outside initConfig, then when a child class declared its own columns configuration the value would be overridden. If we declare the columns configuration inside initComponent, a child class would not be able to add its own columns configuration, so we need to merge these two configurations (the columns from the child class #12 with the two columns we want each child class to have).

For the delete button, we are going to use a Widget Column (#13) (introduced in Ext JS 5). Until Ext JS 4, the only way to have a button inside a Grid Column was using an Action Column. We are going to use a button (#16) to represent a Widget Column. Because it is a Widget Column, there is no reason to make this column sortable (#14), and we can also disable its menu (#15).

Specific GridPanels for each table

Our last stop before we implement the Controller is the specific GridPanels. We have already created the super GridPanel that contains most of the capabilities that we need. Now we just need to declare the specific configurations for each GridPanel.

We will create five GridPanels that will extend from the Packt.view.staticData.BaseGrid class, as follows:

  • Packt.view.staticData.Actors
  • Packt.view.staticData.Categories
  • Packt.view.staticData.Languages
  • Packt.view.staticData.Cities
  • Packt.view.staticData.Countries

Let’s start with the Actors GridPanel, as follows:

Ext.define('Packt.view.staticData.Actors', {
    extend: 'Packt.view.staticData.BaseGrid',
    xtype: 'actorsgrid',        //#1
 
    store: 'staticData.Actors', //#2
 
    columns: [
        {
            text: 'Actor Id',
            width: 100,
            dataIndex: 'actor_id',
            filter: {
                type: 'numeric'   //#3
            }
        },
        {
            text: 'First Name',
            flex: 1,
            dataIndex: 'first_name',
            editor: {
                allowBlank: false, //#4
                maxLength: 45      //#5
            },
            filter: {
                type: 'string'     //#6
            }
        },
        {
            text: 'Last Name',
            width: 200,
            dataIndex: 'last_name',
            editor: {
                allowBlank: false, //#7
                maxLength: 45      //#8
            },
            filter: {
                type: 'string'     //#9
            }
        }
    ]
});

Each specific class has its own xtype (#1). We also need to execute an UPDATE query in the database to update the menu table with the new xtypes we are creating:

UPDATE `sakila`.`menu` SET `className`='actorsgrid' WHERE `id`='5';
UPDATE `sakila`.`menu` SET `className`='categoriesgrid' WHERE `id`='6';
UPDATE `sakila`.`menu` SET `className`='languagesgrid' WHERE `id`='7';
UPDATE `sakila`.`menu` SET `className`='citiesgrid' WHERE `id`='8';
UPDATE `sakila`.`menu` SET `className`='countriesgrid' WHERE `id`='9';

The first declaration that is specific to the Actors GridPanel is the Store (#2). We are going to use the Actors Store. Because the Actors Store is inside the staticData folder (store/staticData), we also need to pass the name of the subfolder; otherwise, Ext JS will think that this Store file is inside the app/store folder, which is not true.

Then we need to declare the columns specific to the Actors GridPanel (we do not need to declare the Last Update and the Delete Action Column because they are already in the super GridPanel).

What you need to pay attention to now are the editor and filter configurations for each column. The editor is for editing (cellediting plugin). We will only apply this configuration to the columns we want the user to be able to edit, and the filter (filters plugin) is the configuration that we will apply to the columns we want the user to be able to filter information from.

For example, for the id column, we do not want the user to be able to edit it as it is a sequence provided by the MySQL database auto increment, so we will not apply the editor configuration to it. However, the user can filter the information based on the ID, so we will apply the filter configuration (#3).

We want the user to be able to edit the other two columns: first_name and last_name, so we will add the editor configuration. We can perform client validations as we can do on a field of a form too. For example, we want both fields to be mandatory (#4 and #7) and the maximum number of characters the user can enter is 45 (#5 and #8).

And at last, as both columns are rendering text values (string), we will also apply filter (#6 and #9).

For other filter types, please refer to the Ext JS documentation as shown in the following screenshot. The documentation provides an example and more configuration options that we can use:

And that is it! The super GridPanel will provide all the other capabilities.

Summary

In this article, we covered how to implement screens that look very similar to the MySQL Table Editor. The most important concept we covered in this article is implementing abstract classes, using the inheritance concept from OOP. We are used to using these concepts on server-side languages, such as PHP, Java, .NET, and so on. This article demonstrated that it is also important to use these concepts on the Ext JS side; this way, we can reuse a lot of code and also implement generic code that provides the same capability for more than one screen.

We created a Base Model and Store. We used GridPanel and Live Search grid and filter plugin for the GridPanel as well. You learned how to perform CRUD operations using the Store capabilities.

Resources for Article:


Further resources on this subject:


Packt

Share
Published by
Packt

Recent Posts

Harnessing Tech for Good to Drive Environmental Impact

At Packt, we are always on the lookout for innovative startups that are not only…

2 months ago

Top life hacks for prepping for your IT certification exam

I remember deciding to pursue my first IT certification, the CompTIA A+. I had signed…

3 years ago

Learn Transformers for Natural Language Processing with Denis Rothman

Key takeaways The transformer architecture has proved to be revolutionary in outperforming the classical RNN…

3 years ago

Learning Essential Linux Commands for Navigating the Shell Effectively

Once we learn how to deploy an Ubuntu server, how to manage users, and how…

3 years ago

Clean Coding in Python with Mariano Anaya

Key-takeaways:   Clean code isn’t just a nice thing to have or a luxury in software projects; it's a necessity. If we…

3 years ago

Exploring Forms in Angular – types, benefits and differences   

While developing a web application, or setting dynamic pages and meta tags we need to deal with…

3 years ago