Implementing Factory pattern in ExtJS

The factory method pattern is used to define an interface for creating an object, but let the subclasses decide which class to instantiate (Wikipedia). It is a basic method pattern which can be easy to implement in programming languages like Java, but could be difficult to accomplish in others like JavaScript.

This document shows how to built a GridPanel Factory through the implementation of the Factory method pattern in ExtJS (a JavaScript framework). The Panel Factory will be able to instantiate different types of Panels depending on our needs.

The following applications and libraries were used:

  • Ext JS 4
  • Windows 7
  • WAMP
  • Eclipse (with JavaScript Developer Tools)

1) Before creating the JavaScript project, let’s make sure that everything is set in place. Copy the ExtJS folder into WAMP’s www folder (In my case, it is located in C:\wamp\www) and go to the following URL http://localhost/ext401/  (change the ExtJS folder name)

image

2) Create a new JavaScript project in Eclipse and called it “FactoryPattern”. Make sure that your workspace is set to ExtJS’ “examples” folder (\wamp\www\ext401\examples\):

image

3) Let’s start creating the components that will be instantiated. For this example, the Grid Factory will be able to create two types of grids:

  1. Basic grid.- simple grid which lists a set of company names.
  2. Advanced grid.- similar to basic grid but when the row is selected, the company details will be displayed in another panel.

4) The following code defines the ‘basic grid’ component:

Ext.namespace('Ext.ux.Customgrids');

/**
 * SimpleGrid Class
 *
 * @version 1.0
 * @class Ext.ux.Customgrids.SimpleGrid
 * @extends Ext.grid.Panel
 * @constructor
 * @param {Object} config Configuration options
 */

Ext.define('Ext.ux.Customgrids.SimpleGrid',
        {
            extend: 'Ext.grid.Panel',
            alias: 'widget.simplegrid',
            constructor: function(config)
            {
                Ext.apply(this,
                        {
                    columns: [{
                            text     : 'Company',
                            flex     : 1,
                            sortable : false,
                            dataIndex: 'company'
                        },
                        {
                            text     : 'Price',
                            width    : 75,
                            sortable : true,
                            renderer : 'usMoney',
                            dataIndex: 'price'
                        },
                        {
                            text     : 'Change',
                            width    : 75,
                            sortable : true,
                            dataIndex: 'change'
                        }
                    ]
                 }
                );

                Ext.ux.Customgrids.SimpleGrid.superclass.constructor.apply(this, arguments);
            }
        }
);

5) Before we continue with the ‘advanced grid’ component, we need to define, in a different file, the Ext.Panel which will display de company details:

Ext.namespace('Ext.ux.Customgrids');

/**
 * DetailsPanel Class
 *
 * @version 1.0
 * Panel to display the company details.
 * @class Ext.ux.Customgrids.DetailsPanel
 * @alias widget.detailspanel
 * @extends Ext.grid.Panel
 * @constructor
 * @param {Object} config Configuration options
*/
Ext.define('Ext.ux.Customgrids.DetailsPanel',
{
    extend: 'Ext.form.Panel',
    alias: 'widget.detailspanel',
    bodyPadding: 7,
    items      : [
        Ext.create('Ext.form.field.Text', {
        fieldLabel : 'Company',
        name       : 'company'
        }),
        Ext.create('Ext.form.field.Number', {
            fieldLabel : 'Price',
            name       : 'price',
            allowBlank: false,
            allowDecimals: true,
            minValue: 0,
            maxValue: 150000
            }),
        Ext.create('Ext.form.field.Number', {
            fieldLabel : 'Change',
            name       : 'change',
            allowBlank: false,
            allowDecimals: true,
            minValue: 0,
            maxValue: 150000
            })
    ],
    initComponent: function()
    {
        Ext.ux.Customgrids.DetailsPanel.superclass.initComponent.call(this);
    }
});

6) The ‘advanced grid’ component is an Ext.Panel which contains 2 items:

  • DetailsPanel (previously defined)
  • SimpleGrid

The following code defines the component (based on ExtJs – Data binding example):

Ext.namespace('Ext.ux.Customgrids');

/**
 * AdvancedGrid Class
 * @version 1.0
 * @class Ext.ux.Customgrids.AdvancedGrid
 * @alias widget.advancedgrid
 * @extends Ext.Panel
 * @constructor
 * @param {Object} config Configuration options
 */

Ext.define('Ext.ux.Customgrids.AdvancedGrid',
        {
            extend: 'Ext.Panel',
            alias: 'widget.advancedgrid',
            frame: true,
            width: 500,
            height: 400,
            layout: 'border',
            constructor: function(config)
            {
                Ext.apply(this,
                {
                    items: [
                    {
                        xtype: 'simplegrid',
                        itemId: 'mainPanel',
                        region: 'center',
                        store: config.store,
                        split: true
                    },
                    {
                        xtype: 'detailspanel',
                        itemId: 'detailPanel',
                        region: 'north',
                        height: 140
                    }
                      ]
                }
                );
                Ext.ux.Customgrids.AdvancedGrid.superclass.constructor.apply(this, arguments);
            },
            initEvents: function() {
                // call the superclass's initEvents implementation
                Ext.ux.Customgrids.AdvancedGrid.superclass.initEvents.call(this);

                // now add application specific events
                // notice we use the selectionmodel's rowselect event rather
                // than a click event from the grid to provide key navigation
                // as well as mouse navigation
                var GridSm = this.getComponent('mainPanel').getSelectionModel();
                ('selectionchange', function(sm, rs) {
                if (rs.length) {
                    var detailPanel = Ext.getCmp('detailPanel');
                    detailPanel.getForm().loadRecord(rs[0]);
                }
            })
                GridSm.on('selectionchange', this.onRowSelect, this);
            },
            // add a method called onRowSelect
            // This matches the method signature as defined by the 'rowselect'
            // event defined in Ext.selection.RowModel
            onRowSelect: function(sm, rs) {
                // getComponent will retrieve itemId's or id's. Note that itemId's
                // are scoped locally to this instance of a component to avoid
                // conflicts with the ComponentManager
                if (rs.length) {
                    var detailPanel = this.getComponent('detailPanel');
                    detailPanel.getForm().loadRecord(rs[0]);
                }
            }
});

7) So far, we have all the components we need. And, if we want to instantiate one of these objects, we would need to code something like this:

    var simple = new Ext.ux.Customgrids.SimpleGrid({
        height: 350,
        width: 400,
        title: 'Simple Grid',
        store: store,
        renderTo: 'grid_div'
    });

    var advanced = new Ext.ux.Customgrids.AdvancedGrid({
        height: 350,
        width: 400,
        title: 'Advanced Grid',
        store: store,
        renderTo: 'advancedgrid_div'
    });

8 ) Here is where the factory pattern comes into the scene. As previously said, the factory pattern defines an interface to create objects. Unfortunately,ExtJS (and JavaScript) does not have a reserved ‘interface’ word as Java does. But we can accomplish something similar (an abstract class) through the following code:

/* Abstract class */
Ext.namespace('Ext.ux.factory');

/**
 * AbstractFactory Class
 * @version 1.0
 * Abstract class to build factories.
 * @class Ext.ux.factory.AbstractFactory
 */

Ext.define('Ext.ux.factory.AbstractFactory', {
    create : function(model, config){
                    throw "Unimplemented method.";
               }
});

9) This abstract class will be inherited by our GridFactory:

Ext.namespace('Ext.ux.factory');

/**
 * GridFactory Class (Singleton)
 * @version 1.0
 * Grid factory.
 * @class Ext.ux.factory.GridFactory
 * @extends Ext.ux.factory.AbstractFactory
 * @constructor
 * @param {Object} config Configuration options
 */

Ext.define('Ext.ux.factory.GridFactory', {
    extend: 'Ext.ux.factory.AbstractFactory',
    alias: 'widget.gridfactory',
    singleton: true,
    create : function(model, config){
        var grid;
        switch(model)
        {
            case 'simple':
                grid = new Ext.ux.Customgrids.SimpleGrid(config);
                break;
            case 'advanced':
                grid = new Ext.ux.Customgrids.AdvancedGrid(config);
                break;
        }

        return grid;
    }
});

As you will notice, this class is defined as a ‘singleton’. This is because we only need one factory to instantiate all the objects (grids) we need. The code is straightforward, the factory can built two types of objects: SimpleGrid and AdvancedGrid; and the returned object will depend on the input parameters.

10) Now that we have our GridFactory, we can instantiate the objects in the following way:

 var simple = Ext.ux.factory.GridFactory.create('simple', {
        height: 350,
        width: 400,
        title: 'Simple Grid',
        store: store,
        renderTo: 'grid_div'
    });

    var advanced = Ext.ux.factory.GridFactory.create('advanced', {
        height: 350,
        width: 400,
        title: 'Advanced Grid',
        store: store,
        renderTo: 'advancedgrid_div'
    });

11) You can test your code adding the following HTML & JavaScript files into your Eclipse project:

example.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
 <link rel="stylesheet" type="text/css" href="../../resources/css/ext-all.css" />
 <link rel="stylesheet" type="text/css" href="../shared/example.css" />
 <link rel="stylesheet" type="text/css" href="../ux/css/CheckHeader.css" />
 <script type="text/javascript" src="../../bootstrap.js"></script>

<script type="text/javascript" src="SimpleGrid.js"></script>
<script type="text/javascript" src="DetailsPanel.js"></script>
<script type="text/javascript" src="AdvancedGrid.js"></script>
<script type="text/javascript" src="AbstractFactory.js"></script>
<script type="text/javascript" src="GridFactory.js"></script>
<script type="text/javascript" src="example.js"></script>

<title>Implementing Factory Pattern in ExtJS</title>
</head>
<body>
<div id="main">
        <div id="grid_div" style="float:left;  padding: 15px;"></div>
        <div id="advancedgrid_div"></div>
        </div>
</body>
</html>

example.js

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

Ext.require([
    'Ext.grid.*'
]);

Ext.onReady(function(){

    //sample data
    var myData = [
                  ['3m Co',                               71.72, 0.02,  0.03],
                  ['Alcoa Inc',                           29.01, 0.42,  1.47],
                  ['Altria Group Inc',                    83.81, 0.28,  0.34],
                  ['American Express Company',            52.55, 0.01,  0.02],
                  ['American International Group, Inc.',  64.13, 0.31,  0.49],
                  ['AT&amp;T Inc.',                           31.61, -0.48, -1.54],
                  ['Boeing Co.',                          75.43, 0.53,  0.71],
                  ['Caterpillar Inc.',                    67.27, 0.92,  1.39],
                  ['Citigroup, Inc.',                     49.37, 0.02,  0.04],
                  ['E.I. du Pont de Nemours and Company', 40.48, 0.51,  1.28],
                  ['Exxon Mobil Corp',                    68.1,  -0.43, -0.64],
                  ['General Electric Company',            34.14, -0.08, -0.23],
                  ['General Motors Corporation',          30.27, 1.09,  3.74],
                  ['Hewlett-Packard Co.',                 36.53, -0.03, -0.08],
                  ['Honeywell Intl Inc',                  38.77, 0.05,  0.13],
                  ['Intel Corporation',                   19.88, 0.31,  1.58],
                  ['International Business Machines',     81.41, 0.44,  0.54],
                  ['Johnson &amp; Johnson',                   64.72, 0.06,  0.09],
                  ['JP Morgan &amp; Chase &amp; Co',              45.73, 0.07,  0.15],
                  ['McDonald\'s Corporation',             36.76, 0.86,  2.40],
                  ['Merck &amp; Co., Inc.',                   40.96, 0.41,  1.01],
                  ['Microsoft Corporation',               25.84, 0.14,  0.54],
                  ['Pfizer Inc',                          27.96, 0.4,   1.45],
                  ['The Coca-Cola Company',               45.07, 0.26,  0.58],
                  ['The Home Depot, Inc.',                34.64, 0.35,  1.02],
                  ['The Procter &amp; Gamble Company',        61.91, 0.01,  0.02],
                  ['United Technologies Corporation',     63.26, 0.55,  0.88],
                  ['Verizon Communications',              35.57, 0.39,  1.11],
                  ['Wal-Mart Stores, Inc.',               45.45, 0.73,  1.63]
              ];

    //data store
    var store = Ext.create('Ext.data.ArrayStore', {
        fields: [
           {name: 'company'},
           {name: 'price',      type: 'float'},
           {name: 'change',     type: 'float'}],
        data: myData
    });

    /* DECLARING OBJECTS WITHOUT FACTORY PATTERN
    var simple = new Ext.ux.Customgrids.SimpleGrid({
        height: 350,
        width: 400,
        title: 'Simple Grid',
        store: store,
        renderTo: 'grid_div'
    });

    var advanced = new Ext.ux.Customgrids.AdvancedGrid({
        height: 350,
        width: 400,
        title: 'Advanced Grid',
        store: store,
        renderTo: 'advancedgrid_div'
    });
    */

    //DECLARING OBJECTS WITH FACTORY PATTERN
    var simple = Ext.ux.factory.GridFactory.create('simple', {
        height: 350,
        width: 400,
        title: 'Simple Grid',
        store: store,
        renderTo: 'grid_div'
    });

    var advanced = Ext.ux.factory.GridFactory.create('advanced', {
        height: 350,
        width: 400,
        title: 'Advanced Grid',
        store: store,
        renderTo: 'advancedgrid_div'
    });

});

11) At the end, you should see something like this:

image

As a conclusion, we can say that the factory pattern decouples the calling class from the target class and it provides a simple way of extending the family of objects with minor changes in application code.

Advertisements

One response to “Implementing Factory pattern in ExtJS

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

%d bloggers like this: