Cultivating your code

As you build your application, there will come a point at which you create a new class and yet it doesn't logically fit into the directory structure Sencha Cmd created for you. Let's look at a few examples.

I'm a lumberjack – let's go log in

Many applications have a centralized SessionManager to take care of the currently logged in user, perform authentication operations, and set up persistent storage for session credentials. There's only one SessionManager in an application. A truncated version might look like this:

/**
 * @class CultivateCode.SessionManager
 * @extends extendsClass
 * Description
 */
Ext.define('CultivateCode.SessionManager', {
    singleton: true,
    isLoggedIn: false,

    login: function(username, password) {
        // login impl
    },


    logout: function() {
        // logout impl
    },

    isLoggedIn() {
        return isLoggedIn;
    }
});

We create a singleton class. This class doesn't have to be instantiated using the new keyword. As per its class name, CultivateCode.SessionManager, it's a top-level class and so it goes in the top-level directory. In a more complicated application, there could be a dedicated Session class too and some other ancillary code, so we'd create the following structure:

I'm a lumberjack – let's go log in

The directory structure for our session namespace

What about user interface elements? There's an informal practice in the Ext JS community that helps here. We want to create an extension that shows the coordinates of the currently selected cell (similar to cell references in Excel). In this case, we'd create an ux directory—user experience or user extensions—and then go with the naming conventions of the Ext JS framework:

Ext.define('CultivateCode.ux.grid.plugins.CoordViewer', {
    extend: 'Ext.plugin.Abstract',
    alias: 'plugin.coordviewer',

    mixins: {
        observable: 'Ext.util.Observable'
    },

    init: function(grid) {
        this.mon(grid.view, 'cellclick', this.onCellClick, this);
    },

    onCellClick: function(view, cell, colIdx, record, row, rowIdx, e) {
        var coords = Ext.String.format('Cell is at {0}, {1}', colIdx, rowIdx)

        Ext.Msg.alert('Coordinates', coords);
    }
});

It looks a little like this, triggering when you click on a grid cell:

I'm a lumberjack – let's go log in

Also, the corresponding directory structure follows directly from the namespace:

I'm a lumberjack – let's go log in

You can probably see a pattern emerging already.

Tip

We've mentioned before that organizing an application is often about setting things up to fall into a position of success. A positive pattern like this is a good sign that you're doing things right.

We've got a predictable system that should enable us to create new classes without having to think too hard about where they're going to sit in our application. Let's take a look at one more example of a mathematics helper class (one that is a little less obvious).

Again, we can look at the Ext JS framework itself for inspiration. There's an Ext.util namespace containing over 20 general classes that just don't fit anywhere else. So, in this case, let's create CultivateCode.util.Mathematics that contains our specialized methods for numerical work:

Ext.define('CultivateCode.util.Mathematics', {
    singleton: true,

    square: function(num) {
        return Math.pow(num, 2);
    },

    circumference: function(radius) {
        return 2 * Math.PI * radius;
    }
}); 

There is one caveat here and it's an important one. There's a real danger that rather than thinking about the namespace for your code and its place in your application, a lot of stuff ends up under the utils namespace, thereby defeating the whole purpose. Take time to carefully check whether there's a more suitable location for your code before putting it in the utils bucket.

This is particularly applicable if you're considering adding lots of code to a single class in the utils namespace. Looking again at Ext JS, there are lots of specialized namespaces (such as Ext.state or Ext.draw). If you were working with an application with lots of mathematics, perhaps you'd be better off with the following namespace and directory structure:

Ext.define('CultivateCode.math.Combinatorics', {
    // implementation here!
});
Ext.define('CultivateCode.math.Geometry', {
    // implementation here!
});

The directory structure for the math namespace is shown in the following screenshot:

I'm a lumberjack – let's go log in

This is another situation where there is no definitive right answer. It will come to you with experience and will depend entirely on the application you're building. Over time, putting together these high-level applications, building blocks will become second nature.

Money can't buy class

Now that we're learning where our classes belong, we need to make sure that we're actually using the right type of class. Here's the standard way of instantiating an Ext JS class:

var geometry = Ext.create('MyApp.math.Geometry'),

However, think about your code. Think how rare it's in Ext JS to actually manually invoke Ext.create. So, how else are the class instances created?

Singletons

A singleton is simply a class that only has one instance across the lifetime of your application. There are quite a number of singleton classes in the Ext JS framework. While the use of singletons in general is a contentious point in software architecture, they tend to be used fairly well in Ext JS.

It could be that you prefer to implement the mathematical functions (we discussed earlier) as a singleton. For example, the following command could work:

var area = CultivateCode.math.areaOfCircle(radius);

However, most developers would implement a circle class:

var circle = Ext.create('CultivateCode.math.Circle', { radius: radius });
var area = circle.getArea();

This keeps the circle-related functionality partitioned off into the circle class. It also enables us to pass the circle variable round to other functions and classes for additional processing.

On the other hand, look at Ext.Msg. Each of the methods here are fired and forgotten, there's never going to be anything to do further actions on. The same is true of Ext.Ajax. So, once more we find ourselves with a question that does not have a definitive answer. It depends entirely on the context.

Tip

This is going to happen a lot, but it's a good thing! This book isn't going to teach you a list of facts and figures; it's going to teach you to think for yourself. Read other people's code and learn from experience. This isn't coding by numbers!

The other place you might find yourself reaching for the power of the singleton is when you're creating an overarching manager class (such as the inbuilt StoreManager or our previous SessionManager example). One of the objections about singletons is that they tend to be abused to store lots of global state and break down the separation of concerns we've set up in our code, as follows:

Ext.define('CultivateCode.ux.grid.GridManager', {
    
    singleton: true,
    currentGrid: null,
    grids: [],

    add: function(grid) {
        this.grids.push(grid);
    },

    setCurrentGrid: function(grid) {
        this.focusedGrid = grid;
    }
});

No one wants to see this sort of thing in a code base. It brings behavior and state to a high level in the application. In theory, any part of the code base could call this manager with unexpected results. Instead, we'd do something like this:

Ext.define('CultivateCode.view.main.Main', {
    extend: 'CultivateCode.ux.GridContainer',

    currentGrid: null,
    grids: [],

    add: function(grid) {
        this.grids.push(grid);
    },

    setCurrentGrid: function(grid) {
        this.currentGrid = grid;
    }
});

We still have the same behavior (a way of collecting together grids), but now, it's limited to a more contextually appropriate part of the grid. Also, we're working with the MVVM system. We avoid global state and organize our code in a more correct manner. A win all round.

As a general rule, if you can avoid using a singleton, do so. Otherwise, think very carefully to make sure that it's the right choice for your application and that a standard class wouldn't better fit your requirements. In the previous example, we could have taken the easy way out and used a manager singleton, but it would have been a poor choice that would compromise the structure of our code.

Mixins

We're used to the concept of inheriting from a subclass in Ext JS—a grid extends a panel to take on all of its functionality. Mixins provide a similar opportunity to reuse functionality to augment an existing class with a thin slice of behavior. In Code Complete Second Edition, Steve McConnell, Microsoft Press US, Section 6.3, McConnell says:

"Think of containment as a "has a" relationship. A car "has an" engine, a person "has a" name, etc."

"Think of inheritance as an "is a" relationship. A car "is a" vehicle, a person "is a" mammal, etc."

An Ext.Panel "is an" Ext.Component, but it also "has a" pinnable feature that provides a pin tool via the Ext.panel.Pinnable mixin.

In your code, you should be looking at mixins to provide a feature, particularly in cases where this feature can be reused. In the next example, we'll create a UI mixin called shakeable, which provides a UI component with a shake method that draws the user's attention by rocking it from side to side:

Ext.define('CultivateCode.util.Shakeable', {
    mixinId: 'shakeable',

    shake: function() {
        var el = this.el,
            box = el.getBox(),
            left = box.x - (box.width / 3),
            right = box.x + (box.width / 3),
            end = box.x;

        el.animate({
            duration: 400,
            keyframes: {
                33: {   
                    x: left
                },
                66: {
                    x: right
                },
                100: {
                    x: end
                }
            }
        });
    }
});

We use the animate method (which itself is actually mixed in Ext.Element) to set up some animation keyframes to move the component's element first left, then right, then back to its original position. Here's a class that implements it:

Ext.define('CultivateCode.ux.button.ShakingButton', {
    extend: 'Ext.Button',
    mixins: ['CultivateCode.util.Shakeable'],
    xtype: 'shakingbutton'
});

Also it's used like this:

var btn = Ext.create('CultivateCode.ux.button.ShakingButton', {
   text: 'Shake It!'
});
btn.on('click', function(btn) {
   btn.shake();
});

The button has taken on the new shake method provided by the mixin. Now, if we'd like a class to have the shakeable feature, we can reuse this mixin where necessary.

In addition, mixins can simply be used to pull out the functionality of a class into logical chunks, rather than having a single file of many thousands of lines. Ext.Component is an example of this. In fact, most of its core functionality is found in classes that are mixed in Ext.Component.

This is also helpful when navigating a code base. Methods that work together to build a feature can be grouped and set aside in a tidy little package. Let's take a look at a practical example of how an existing class could be refactored using a mixin. Here's the skeleton of the original:

Ext.define('CultivateCode.ux.form.MetaPanel', {
    extend: 'Ext.form.Panel',

    initialize: function() {
        this.callParent(arguments);
        this.addPersistenceEvents();
    },

    loadRecord: function(model) {
        this.buildItemsFromRecord(model);
        this.callParent(arguments);
    },

    buildItemsFromRecord: function(model) {
        // Implementation
    },

    buildFieldsetsFromRecord: function(model){
        // Implementation
    },

    buildItemForField: function(field){
        // Implementation
    },

    isStateAvailable: function(){
        // Implementation
    },

    addPersistenceEvents: function(){
        // Implementation
    },

    persistFieldOnChange: function(){
        // Implementation
    },

    restorePersistedForm: function(){
        // Implementation
    },

    clearPersistence: function(){
        // Implementation
    }
});

This MetaPanel does two things that the normal FormPanel does not:

  • It reads the Ext.data.Fields from an Ext.data.Model and automatically generates a form layout based on these fields. It can also generate field sets if the fields have the same group configuration value.
  • When the values of the form change, it persists them to localStorage so that the user can navigate away and resume completing the form later. This is useful for long forms.

In reality, implementing these features would probably require additional methods to the ones shown in the previous code skeleton. As the two extra features are clearly defined, it's easy enough to refactor this code to better describe our intent:

Ext.define('CultivateCode.ux.form.MetaPanel', {
    extend: 'Ext.form.Panel',

    mixins: [
        // Contains methods:
        // - buildItemsFromRecord
        // - buildFieldsetsFromRecord
        // - buildItemForField
        'CultivateCode.ux.form.Builder',

        // - isStateAvailable
        // - addPersistenceEvents
        // - persistFieldOnChange
        // - restorePersistedForm
        // - clearPersistence
        'CultivateCode.ux.form.Persistence'
    ],

    initialize: function() {
        this.callParent(arguments);
        this.addPersistenceEvents();
    },

    loadRecord: function(model) {
        this.buildItemsFromRecord(model);
        this.callParent(arguments);
    }
});

We have a much shorter file and the behavior we're including in this class is described a lot more concisely. Rather than seven or more method bodies that may span a couple of hundred lines of code, we have two mixin lines and the relevant methods extracted to a well-named mixin class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset