Chapter 4. Class System

In the previous chapter, we looked into Sencha MVC architecture and implemented a small application where we defined and used various classes. A couple of things we did were as follows:

  • We defined classes using Ext.define
  • We defined classes by extending existing classes
  • We instantiated classes using Ext.create
  • We used aliases while defining the view classes and used them as xtype
  • We used ext-debug.js/ext.js for the ExtJS application, and sencha-touch-debug.js/sencha-touch.js for the Sencha Touch application rather than the xxx-all-xxx variants
  • We included only the main app.js file in index.html

Questions that arose out of these items were:

  • Why Ext.define and not Ext.extend?
  • Why Ext.create and not the new operator?
  • How does framework load all my classes, correctly?
  • How can the framework use the alias names to identify and instantiate classes?

The two important features, which help the framework to answer the earlier questions, are:

  • Class System: This feature lays the foundation for defining classes in ExtJS and Sencha Touch
  • Loader: This feature takes care of resolving the class dependencies by ensuring that the dependencies are loaded before the class can be initialized

In this chapter, we will visit the class system and the loader to understand what they offer and what do they require to provide their functionality.

Class system

Let us look closer at the API doc of Ext.form.Panel in ExtJS and Sencha Touch. The following diagram depicts the class hierarchy and some of the dependent classes:

Class system

In ExtJS, Form Panel extends the Panel class and uses the FieldAncestor mixin. The Panel class extends the AbstractComponent class and requires the Header class. The AbstractComponent class uses Stateful and Observable as its two mixins to make the component stateful and allows them to fire and handle events.

In Sencha Touch, Form Panel extends the Panel class. The Panel class extends the AbstractComponent class and requires the LineSegment class. The AbstractComponent class uses the Observable class as one of its mixins to allow the component to fire and handle events.

Let us take an example and look at what happens when we use the classes as per the class system (for example, use Ext.create) and let the loader take care of the dependency loading. Say, we want to create the following UI in ExtJS and Sencha Touch:

Class system

To create the UI, we use the following steps:

  1. Create a folder named ch03 under the WebContent folder.
  2. Create two folders under ch03 and name them extjs and touch.
  3. Create a file named ch03_01.js under the ch03extjs folder and save the following code inside it:
    Ext.onReady(function() {
      
      var pnl = Ext.create('Ext.panel.Panel', {
        title: 'My Panel',
        renderTo: Ext.getBody(),
        width: 300,
        height: 100,
        items: [{
          xtype: 'textfield',
          fieldLabel: 'Name',
          anchor: '90%'
        }, {
          xtype: 'numberfield',
          fieldLabel: 'Age'
        }]
      });
    });
  4. Create a file named index.html under the ch03extjs folder and save the following code inside it:
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title id="page-title">Account Manager</title>
    
        <link rel="stylesheet" type="text/css" href="../../extjsapp/extjs-4.1.0-rc1/resources/css/ext-all.css">
        <script type="text/javascript" src="../../extjsapp/extjs-4.1.0-rc1/ext-debug.js"></script>
        
        <script type="text/javascript" src="ch03_01.js"></script>
    </head>
    <body>
    
    </body>
    </html>

    Change the path of ExtJS files based on their location on your system.

  5. Run the application and look at your browser tool to see how the loader loads various files to bring up the application. The following screenshot shows the output of the Network tab on Chrome's developer tool:
    Class system

    As soon as the app.js file is loaded and it tries to instantiate Panel, the loader identifies its dependencies, for example, Panel extends AbstractPanel, and it loads them immediately.

  6. Create a file ch03_01.js under the ch03 ouch folder and save the following code inside it:
    Ext.setup({
      onReady: function() {
        var pnl = Ext.create('Ext.Panel', {
          fullscreen: true,
          items: [{
            xtype: 'titlebar',
              docked: 'top',
              title: 'My Panel'
          }, {
            xtype: 'textfield',
            label: 'Name'
          }, {
            xtype: 'numberfield',
            label: 'Age'
          }]
        });
        
        Ext.Viewport.add(pnl);
      }
    });
  7. Create a file named index.html under the ch03 ouch folder and save the following code inside it:
    <!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">
    <title>Account Manager</title>
    
    <!-- Sencha Touch specific files -->
    <link rel="stylesheet" type="text/css" href="../../touchapp/sencha-touch-2.0.0/resources/css/sencha-touch.css">
    <script type="text/javascript" src="../../touchapp/sencha-touch-2.0.0/sencha-touch-debug.js"></script>
    
    <!-- Application specific files -->
    <script type="text/javascript" src="app.js"></script>
     
    </head>
    <body>
    
    </body>
    </html>

    Change the path of Sencha Touch files based on their location on your system.

  8. Run the application and look at your browser tool to see how the Loader loads various files to bring up the application. The following screenshot shows the output of the Network tab on Chrome's developer tool:
    Class system

The class system offers various functionalities, which we will see in the subsequent sections.

Naming convention

The new class system has implemented various naming conventions including classes, methods, files, variables, properties, and so on. Usage of this makes the code more readable, structured, and maintainable.

Class

Class has the following properties:

  • Class names contain alphanumeric characters
  • Numbers are permitted but discouraged
  • Use of underscores, hyphens, or any other non-alphanumeric characters is discouraged
  • Class names should be grouped into packages where appropriate
  • Class names should be properly namespaced using object property dot notation(.)
  • The top-level namespaces and the actual class names should be camelCased. For example, MyApp.view.user.CustomerList
  • Acronyms should also follow camelCased convention. For example,(). Ext.data.JsonProxy

File

While writing the code, a source file maps to a class defined inside it. As per this, one file must define a single class. For example, the AM.view.user.List class is stored in the <app root src path>viewuserList.js file. AM is the namespace which maps to the application's root source folder.

Always have a separate folder for each namespace and maintain all the code within that namespace. For example, in the application that we built in the previous chapter, AM points to the default app folder. For middleware related classes, we created a new folder, mw, and kept the classes under the MW namespace.

Methods and variables

Methods and variables have the following properties:

  • Method and variable names contain alphanumeric characters
  • Numbers are permitted but are discouraged
  • Use of underscores, hyphens, or any other non-alphanumeric characters is discouraged
  • Method and variable name shall be camelCased (note that it starts with lowercase). For example, getUserByDepartment() or isValid

Properties

The class properties has the following properties:

  • The naming convention that applies to methods and variables also applies to class properties other than static properties, which are constants.
  • For static properties, which are constants, name, will all be upper-case. For example, Ext.MessageBox.YES.

Defining a class

The new class system has introduced a new method called Ext.define to define a new class. The following code shows how we have defined a User class, which has got three properties—name, address, and age—a constructor, and a getName method:

Ext.onReady(function() {
  
  //Define class
  Ext.define('User', {
    name: '',
    address: '',
    age: 0,
    
    constructor: function(name, address, age) {
      this.name = name;
      this.address = address;
      this.age = age;
    },
    getName: function() {
      return this.name;
    }
  });
  
  //Create an instance
  var user = new User('Ajit Kumar', 'Plot# 66, ABC Lane, XYZ Street, My State, My Country - 500014', 34);
  
  alert(user.getName());
});

Once the class is defined, we used the Ext.create method offered by the class system to create an instance of the User object. Once instantiated, we can access the properties or call methods as shown in the earlier code where we are calling the getName method to show the username on the screen. You may save the earlier code in a file, say ch03_02.js, include it in index.html and run it in your browser to play with it.

Configuration

The previous code has a couple of issues:

  • The class is defined and instantiated in the same file, whereas it should have been in a separate file
  • We had to implement the getter method for the name property. Similarly, we may have to write explicit getter/setter methods for the other properties as the accessors. In JavaScript, every character that we add to the source, it adds to the file size and hence to the application's download time. Imagine an enterprise application with 20-50 classes, where you would like to follow the good programming practice by providing the accessor methods!

To take care of these listed problems, we will have to make some changes to our code. First, we will move the class definition to a separate file called User.js. So, User.js will have the following code:

Ext.define('User', {
  config: {
    name: '',
    address: '',
    age: 0
  },
  constructor: function(config) {
    this.initConfig(config);
  }
});

Enclose the three class properties inside config, as shown in the code we just used. Modify the constructor to accept a configuration object and use that to set the members using the initConfig method. config is a special property on a class, which the class system uses to automatically generate the getter and setter methods for the properties listed inside it. So, now that we have moved our properties inside the config, we don't have to implement the getName() or getAddress() methods. They are generated at run-time.

Now, we would modify the code where we were instantiating the class with the following:

Ext.onReady(function() {
  
  var user = Ext.create('User', {
    name: 'Ajit Kumar',
    address: 'Plot# 66, ABC Lane, XYZ Street, My State, My Country - 500014',
    age: 34
  });
    
  alert(user.name + ":" + user.getAddress());
});

Running this code would show the user's name and address.

Alias

In the previous chapter, we gave the alias names to our various view classes, for example, widget.userlist to AM.view.user.List class. Alias is another name by which the class can be referred. There are rules built into the system around this property in some cases. For example, when we defined the widget.userlist property as an alias, we could use userlist as an xtype and the class loader would be able to resolve the userlist property to AM.view.user.List based on the built-in rule around the alias.

If we give an alias to our class name it will have multiple names, and different parts of the applications can use different names but still mean the same object. Let us say, we want to give an alias name AppUser to our User class so that we can instantiate it using the alias name.

To achieve this, first, we will have to set the alias property on the User class, as shown:

Ext.define('User', {
  alias: ['AppUser'],
  config: {

Now, if we try to create the user instance using the AppUser name, the class loader will fail to locate the file where the User class is defined as it will be looking for the AppUser.js file, which does not exist. To address this problem, we will have to add the following statement before the Ext.onReady() call:

Ext.require('User'),

In the previous statement, we have explicitly mentioned the dependency so that the loader loads it and once it is loaded, we are free to create the instance using the alias name as shown in the following code:

  var user = Ext.create('AppUser', {
    name: 'Ajit Kumar',
    address: 'Plot# 66, ABC Lane, XYZ Street, My State, My Country - 500014',
    age: 34
  });

alias is generally used to define as xtype.

Alternate class name

A class has a property—alternateClassName—which can be used to define any number of alternate names for the class. Once defined, the class can be referred using any of these alternate names. For example, in the following code, we have defined an alternate name AppUser for our User class as shown in the following code:

Ext.define('User', {
  alternateClassName: 'AppUser',
  config: {

Once defined, the instance of the class can be created using the alternate name, similar to how we instantiated using the alias name. The Ext.require statement must mention the User class if you want to use the alternate name to instantiate the class.

Extend

A class can be extended from an existing class and this is indicated using the extend property on a class. In the following code, we have defined the Employee class, which extends the User class. It defines an additional property: department.

Ext.define('Employee', {
  extend: 'User',
  config: {
    department: ''
  },
  constructor: function(config) {
    this.initConfig(config);
  }
});

Save this code in the Employee.js file for future references.

Now, we can create an instance of Employee by passing the properties for it, as shown in the following code:

  var employee = Ext.create('Employee', {
    name: 'Ajit Kumar',
    address: 'Plot# 66, ABC Lane, XYZ Street, My State, My Country - 500014',
    age: 34,
    department: 'Finance'
  });

Since we used the config term in the Employee class, the getter/setter methods are available to us and we can call them to show the employee detail by using the following command:

alert(employee.getName() + ":" + employee.getDepartment());

Statics

In a class we can define static properties as well as methods. Static properties and methods are called class-level properties and methods, which means to access them we do not need an instance object. They are accessed directly using the class.

In the following code, we modified our User class' definition inside User.js by adding a count static property to keep track of the number of instances created in the system, as shown in the following code:

statics: {
    count: 0
  },
constructor: function(config) {
    this.initConfig(config);
    this.self.count++;
  }

To access static properties, one has to use the this.self keyword, which gives the reference to the class, as we did in the constructor code to increment the count counter.

The following code shows how we can use the static property to print the number of users created in the system:

alert('Total users in the system: ' + User.count);

Inheritable statics

In case we want to inherit a static property from the parent class to the child class, we will have to use the inheritableStatics keyword in place of statics, as shown in the following code snippet:

inheritableStatics: {
    count: 0
  },

Once this change is made in the User class, the count property can be accessed inside the Employee class as shown in the following code snippet:

constructor: function(config) {
    this.initConfig(config);
    this.self.count++;
  }

Now, we can create as many instances as we need in the application and the count property will keep track of them:

  Ext.create('AppUser', {
    …
  });
  
  Ext.create('Employee', {
    …
  });
  
  Ext.create('Employee', {
    …
  });
  
  Ext.create('Employee', {
    …
  });

alert('Total users in the system: ' + Employee.count);

When the previous code is run, it will print 4.

Mixin

Mixin is a pattern which allows us to mix one class' properties and their behavior into the other class. The classes (say, A) mentioned as mixins are merged into a class (say, B) then the properties and methods of class A can directly be accessed through class B.

Say, we want to have a method getString on our User class, which can return the string format of the object containing the user's name, age, and address. Now, we want to implement this in such a way that it can be applied to any class, which has got three properties—name, address, and age. So, we first define a ToString class with the getString method, as shown in the following code:

Ext.define('ToString', {
  getString: function() {
    return "NAME: " + this.name + "
ADDRESS: " + this.address + "
AGE: " + this.age;
  }
});

As per the naming convention, we will save the previous code in the ToString.js file.

Now, we would mention the ToString class as a mixin property on the User class, as shown in the following code:

config: {
    name: '',
    address: '',
    age: 0
  },
  mixins: {
    Stringyfy: 'ToString'
  },

Mixins are, by default, inheritable. So, getString can be called on the Employee instances, as well. However, for the employee, let us say we also want to add the department info to the string. To achieve that, in the Employee class we will add a getString method, which will call the base getString method of the mixin class, that can give us the name, address, and age information, and append the department information to it, as shown in the following code:

config: {
    department: ''
  },
  getString: function() {
    return this.mixins.Stringyfy.getString.call(this) + "
DEPARTMENT: " + this.department;
  },

Now we can call the getString method on the user object to show the details of the user, such as the name, address, and age by using the following command:

alert(user.getString());
Mixin

We can also call the getString method on the employee object to show the name, address, age, and department detail of the employee by using the following command:

alert(employee.getString());

When we run the code, it shows the employee detail in the alert, as shown in the following screenshot:

Mixin

Singleton

A class has a singleton property which can be used to tell the framework that only one instance of the class needs to be created. For example, in the following code, we have defined a Logger class as a singleton, which provides a log method that can be used to log messages on the console:

Ext.define('Logger', {
    singleton: true,
    log: function(msg) {
        console.log(msg);
    }
});

Save this code in the Logger.js file for future reference.

We will have to include the Logger class in the Ext.require list so that it is loaded:

Ext.require(['User', 'Logger']);

Now, we can make use of the class and call the log method in the code, as shown:

Logger.log(employee.getString());

When we run the application, we can see the employee information logged on the browser console, as shown in the following screenshot:

Singleton

Uses versus requires

In the earlier code, we used Ext.require to list down the dependencies. A similar property exists on the class requires which is used to list out the class dependencies. All the dependencies listed in the class requires are loaded before the class can be initialized. The following code shows that Logger is mentioned as a dependency, which is required by the User class to log anything:

Ext.define('User', {
  alias: ['AppUser'],
  requires: ['Logger'],

When we run the application, we can see that the Logger.js is being loaded right after User.js is loaded, as shown in the following screenshot:

Uses versus requires

The property uses is available on a class to specify the classes that the class uses and it may not be a pre-requisite to the initialization of the class. If that is the case, we can use the uses property and list out the classes in it, as shown in the following code:

Ext.define('User', {
  alias: ['AppUser'],
  uses: ['Logger'],

In this case, the class Logger gets loaded before the onReady listener is called. The following screenshot shows that the sequence of load is different from the one in case of requires class:

Uses versus requires

Error reporting

Error reporting is a very important aspect of an application. The better the error reporting, the better the application usage and robustness would be. In the new class system, support has been built to report errors along with the stack trace. If the new Error class is used, it shows a nice error trace in the browser console. So, we would replace the alert message that we put in the User class to show a message of the age value for a user who is less than 18 years with the following statement:

throw new Error('Age must be greater than 18 Years'),

Now when a user is instantiated where the age is less than 18 years, we can see the following error trace:

Error reporting

This makes the error handling and debugging a lot better. But, here is the not-so-good news. This functionality is, currently, available only on Webkit browsers.

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

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