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:
Ext.define
Ext.create
xtype
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 variantsapp.js
file in index.html
Questions that arose out of these items were:
Ext.define
and not Ext.extend
?Ext.create
and not the new
operator?The two important features, which help the framework to answer the earlier questions, are:
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.
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:
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:
To create the UI, we use the following steps:
ch03
under the WebContent
folder.ch03
and name them extjs
and touch.
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' }] }); });
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.
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.
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); } });
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.
The class system offers various functionalities, which we will see in the subsequent sections.
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 has the following properties:
MyApp.view.user.CustomerList
Ext.data.JsonProxy
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 have the following properties:
getUserByDepartment()
or isValid
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.
The previous code has a couple of issues:
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.
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.
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.
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());
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);
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);
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());
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:
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:
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:
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:
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:
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.
18.118.2.225