Bàn về Class trong Ext Js 4

Class trong Extjs được giới thiệu lần đầu tiên tại Extjs 4.0 và là thành phần chính để xây dựng hệ thống OOP code bằng Javascript. Core này được dùng cho cả Extjs và Sencha Touch.

Tạo Class
Code tạo class đơn giản như sau:

var Person = new Ext.Class({});

Tham số đầu tiên của Class là 1 Object với key-value:

var Person = new Ext.Class({
    name: 'Mr. Unknown',
    walk: function(steps) {
        alert(this.name + ' is walking ' + steps + ' steps');
    }
});


Sử dụng class Person trên:

var person = new Person();
person.walk(10); // alerts "Mr. Unknown is walking 10 steps"

Class Constructor

...
constructor: function(name) {
	this.name = name;
	return this;
},
...

Class NameSpacing
Namespacing giúp code bạn dễ quản lý, để dễ cho quản lý convention ta thường đặt namespace như sau:
..

Ext.chart.Label
Ext.data.writer.Xml
MyCompany.util.VideoConverter

Vậy với class Person mà ở trên ta đã định nghĩa thì làm sao ta đặt nó được vào namespace?

Ext.ns('My.sample');
My.sample.Person = new Ext.Class({ /*…*/ });

Nếu viết như trên thì ta sẽ gặp 1 số problem như sau:
– Chúng ta phải kiểm tra xem namespace kia có tồn tại không trước khi gắn nó vào class.
– Điều gì xảy ra nếu class chúng ta đè lên class đã define trước đó.
– Điều gì xảy ra nếu ta muốn run class verstion 1 trước rồi sau đó run class version 2?
Để tránh cho chúng ta gặp rắc rối thì Ext js đã tạo ra Ext.define

Ext.define
Cấu trúc như sau:

Ext.define((String) className, (Object) classMembers, (Optional Function) onClassCreatedCallback);

Ví dụ:

Ext.define('My.sample.Person', {
    name: 'Mr. Unknown',
 
    constructor: function(name) { /* … */ },
 
    walk: function(steps) { /* … */ }
});

Với cách viết mới như trên thì Ext.define đã giúp ta giải quyết được những khúc mắc mà ta gặp phải:
– Ta không cần quan tâm về namespace đã được tạo chưa nữa.
– Class được định nghĩa chỉ với 1 chuỗi đơn giản cách nhau bởi dấu “.”
– Run bởi nhiều version của class. Cho nên version sau có thể dùng lại những giá trị đã định nghĩa ở version trước đó mà không cần phải viết lại.
– Class bây giờ được gọi chỉ bởi name của nó. Ta không cần quan tâm đến những name khác.

Xử lý class
Sencha class system giờ được xây dựng trên 2 nhóm là pre và post.
Pre chỉ đơn giản là nó chạy trước khi class khởi tạo tức là khi khởi tạo class mà bạn có gán 1 trong những property đặc biệt sau thì Ext sẽ load nó trước rồi mới load class: “extend” (thừa kế), “mixins” (trộn), “requires”, “config”, …
Tương tự, Post là chạy sau khi class đã khởi tạo, và property đặc biệt mà nó dùng là: “singleton”, “alias”…

A] Pre-processors
1) Static:

// My/sample/Point.js
Ext.define('My.sample.Point', {
    statics: {
        fromEvent: function(e) {
            return new this(e.pageX, e.pageY);
        }
    },
    x: 0,
 
    y: 0,
    constructor: function(x, y) {
        this.x = x;
        this.y = y;
    }
});
// app.js
/* … */
var point = My.sample.Point.fromEvent(e);

2) Thừa kế: Là properties mà thông qua nó, class hiện tại có thể dùng để thừa kế với class khác.
Bạn chú ý là extend thì giá trị truyền vào là 1 string cho nên nó chỉ có thể thừa kế được 1 class duy nhất.
Để override method của class cha nó thì ta phải gọi thêm method this.callParent(). Cho nên sinh ra vấn đề là đặt this.callParent() trước hay sau function của chúng ta:
+ Đặt trước khi ta dùng lớp cha trước, sau đó mới truyền giá trị của ta vào. (thường dùng trong constructor của controller)
+ Đặt sau khi ta muốn ghi giá trị vào trước, sau đó mới gọi lớp cha.(thường dùng trong initComponent của view)

// My/sample/Developer.js
Ext.define('My.sample.Developer', {
    extend: 'My.sample.Person'
    code: function(language) { /* … */ },
    walk: function(steps) {
        if (steps > 100) {
            alert("Are you serious? That's too far! I'm lazy…");
        }
        else {
            return this.callParent(arguments);
        }
    }
});
// app.js
var tommy = new My.sample.Developer('Tommy');
tommy.walk(50);    // alerts "Tommy is walking 50 steps"
tommy.walk(101);   // alerts "Are you serious? That's too far! I'm lazy…"

3) Mixins: Là properties mà thông qua nó, class hiện tại có thể dùng để mix với class khác. Nó cũng tương tự như thừa kế nhưng nó khác thừa kế ở chỗ nó có thể mix nhiều class khác nhau, trong khi thừa kế thì chỉ có 1.

// My/sample/CanSing.js
Ext.define('My.sample.CanSing', {
    sing: function(songName) {
        alert("I'm singing " + songName);
    }
});
// My/sample/CanPlayGuitar.js
Ext.define('My.sample.CanPlayGuitar', {
    playGuitar: function() {
        alert("I'm playing guitar");
    }
});
// My/sample/CanComposeSongs.js
Ext.define('My.sample.CanComposeSongs', {
    composeSongs: function() {
        alert("I'm composing songs");
        return this;
    }
});
// My/sample/CoolGuy.js
Ext.define('My.sample.CoolGuy', {
    extend: 'My.sample.Person',
    mixins: {
        canSing: 'My.sample.CanSing',
        canPlayGuitar: 'My.sample.CanPlayGuitar'
    }
});
 
// My/sample/Musician.js
Ext.define('My.sample.Musician', {
    extend: 'My.sample.Person',
    mixins: {
        canSing: 'My.sample.CanSing',
        canPlayGuitar: 'My.sample.CanPlayGuitar',
        canComposeSongs: 'My.sample.CanComposeSongs'
    }
});
// app.js
var nicolas = new My.sample.CoolGuy("Nicolas");
nicolas.sing("November Rain"); // alerts "I'm singing November Rain"

Lưu ý là với mixins ta cũng có thể override method của lớp cha được.

// My/sample/CoolGuy.js
Ext.define('My.sample.CoolGuy', {
    extend: 'My.sample.Person',
    mixins: {
        canSing: 'My.sample.CanSing',
        canPlayGuitar: 'My.sample.CanPlayGuitar'
    },
    sing: function() {
        alert("Attention!");
        // this.mixins is a special object holding references to all mixins' prototypes
        return this.mixins.canSing.sing.apply(this, arguments);
    }
});
 
// app.js
var nicolas = new My.sample.CoolGuy("Nicolas");
nicolas.sing("November Rain"); // alerts "Attention!"
                               // alerts "I'm singing November Rain"

4) Config
Đây là 1 tính năng mới của Extjs. Mà thông qua nó, ta có thể get và set được.

Ext.define('My.sample.Person', {
    config: {
        name: 'Mr. Unknown',
        age: 0,
        gender: 'Male'
    },
    constructor: function(config) {
        this.initConfig(config);
        return this;
    }
    /* … */
});
var jacky = new Person({
    name: "Jacky",
    age: 35
});
alert(jacky.getAge());      // alerts 35
alert(jacky.getGender());   // alerts "Male"
jacky.walk(10);             // alerts "Jacky is walking 10 steps"
jacky.setName("Mr. Nguyen");
alert(jacky.getName());     // alerts "Mr. Nguyen"
jacky.walk(10);             // alerts "Mr. Nguyen is walking 10 steps"

Đặc biệt với config ta còn có thể thêm validation bằng apply trước khi giá trị được set và fireEvent bằng update sau khi giá trị set (giống như 2 event before và after). Dưới đây mình sẽ mô tả ví dụ: Khi set 1 age thì nó sẽ chạy qua applyAge trước rồi sau đó mới chạy tới updateAge

Ext.define('My.sample.Person', {
    config: { /* … */ },
    constructor: { /* … */ }
    applyAge: function(age) {
        if (typeof age != 'number' || age < 0) {
            console.warn("Invalid age, must be a positive number");
            return;
        }
 
        return age;
    },
    updateAge: function(newAge, oldAge) {
        // age has changed from "oldAge" to "newAge"
        this.fireEvent('agechange', this, newAge, oldAge);
    }
    /* … */
});
var jacky = new Person({
    name: "Jacky",
    age: 'invalid'
});
alert(jacky.getAge());      // alerts 0
alert(jacky.setAge(-100));  // alerts 0
alert(jacky.getAge());      // alerts 0
alert(jacky.setAge(35));    // alerts 0
alert(jacky.getAge());      // alerts 35

Require
Trong trường hợp bạn muốn sử dụng 1 method của class khác mà không muốn thừa kế, hay mixins gì cả thì bạn dùng require. Trong trường hợp sau mình sẽ gọi tới 1 method của class khác và dùng nó:

Ext.define('My.sample.Person', {
    requires: ['My.sample.Validator', 'My.sample.Formatter'],
    /* … */
    applyAge: function(age) {
        if (!My.sample.Validator.validateAge(age)) {
            console.warn("Invalid age, must be a positive number");
            return;
        }
        return age;
    },
    applyName: function(name) {
        return My.sample.Formatter.capitalize(name);
    }
    /* … */
});

B]Post-processors
Singleton: Là 1 cách để biến class thành 1 instance ngay. Ví dụ ta muốn khai báo class ABC và dùng ngay class ABC đó mà không cần phải new thì ta dùng singleton

Ext.define('My.sample.Validator', {
    singleton: true,
    validateNumber: function(number) {
        return typeof number == 'number';
    }
    validateAge: function(number) {
        return this.validateNumber(number) && number >= 0;
    }
    /* … */
});
alert(My.sample.Validator.validateNumber('invalid')); // alerts false
var ValidatorClass = Ext.getClass(My.sample.Validator);

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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s