Нюансы использования ООП, Ext.Config в ExtJS

С самого начала ExtJS реализовала систему классов для расширения JavaScript. Цель системы классов заключалась в поддержке эффективной организации кода и реализации псевдоклассического наследования, существующего во многих объектно-ориентированных языках программирования. Давным давно, с выходом ExtJS 4.0 Sencha решила пересмотреть свою систему классов и улучшить ее с помощью большего количества объектно-ориентированных методов. В частности, они разработали конфигурационную систему классов, наверное хоть раз вы видели свойство

config: { ... }
config: { ... } в определении классов в ExtJS. Эта статья посвящена обзору возможностей проектирования классов.

Определение классов

В ExtJS большинство классов наследуется от Ext.Base, за некоторым исключением классы должны следовать определенному соглашению об именах, связанному непосредственно с их файловым путем. Вот пример определения класса, создания экземпляра и его использования. Fiddle доступен тут.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Define class with name "Car"
Ext.define('Car', {
// Define config properties
config : {
make : undefined,
model : undefined
},
// First method that gets called upon class instantiation
constructor : function (config) {
// Initializes config properties
this.initConfig(config);
},
// Define drive method
drive : function () {
console.log('Driving ' + this.getMake() + ' ' + this.getModel());
}
});
// Instantiate class "Car" with config properties
var car = Ext.create('Car', {
make : 'Honda',
model : 'Accord'
});
// Call method "drive" on class instance
// Outputs "Driving Honda Accord" to the console
car.drive();
// Define class with name "Car" Ext.define('Car', { // Define config properties config : { make : undefined, model : undefined }, // First method that gets called upon class instantiation constructor : function (config) { // Initializes config properties this.initConfig(config); }, // Define drive method drive : function () { console.log('Driving ' + this.getMake() + ' ' + this.getModel()); } }); // Instantiate class "Car" with config properties var car = Ext.create('Car', { make : 'Honda', model : 'Accord' }); // Call method "drive" on class instance // Outputs "Driving Honda Accord" to the console car.drive();
// Define class with name "Car"
Ext.define('Car', {
    // Define config properties
    config : {
        make  : undefined,
        model : undefined
    },
    // First method that gets called upon class instantiation
    constructor : function (config) {
        // Initializes config properties
        this.initConfig(config);
    },
    // Define drive method
    drive : function () {
        console.log('Driving ' + this.getMake() + ' ' + this.getModel());
    }
});

// Instantiate class "Car" with config properties
var car = Ext.create('Car', {
    make  : 'Honda',
    model : 'Accord'
});

// Call method "drive" on class instance
// Outputs "Driving Honda Accord" to the console
car.drive();

Использование свойств конфигурации

Когда вы объявляете какое либо свойство с помощью директивы 

config: { ... }
config: { ... } в определении класса, то движок ExtJS сгенерирует 2 основных метода для работы с этим свойством конфигурации.

1. Getter (создается автоматически)

Стандартный метод доступа, который возвращает текущее значение свойства конфигурации. Пример Fiddle тут.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
car.getMake(); // returns “Honda”
car.getMake(); // returns “Honda”
car.getMake(); // returns “Honda”

2. Setter (создается автоматически)

Мутатор, который устанавливает значение свойства конфигурации в экземпляре класса. Пример Fiddle тут.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
car.setMake(“Toyota”);
car.getMake(); // returns “Toyota”
car.setMake(“Toyota”); car.getMake(); // returns “Toyota”
car.setMake(“Toyota”);
car.getMake(); // returns “Toyota”

3. Applier (создается вручную)

Эта функция вызывается setter’ом при изменении свойства конфигурации (например, из 

undefined
undefined до
Foo
Foo). Данная функция должна вернуться значение, которое будет установлено в данное свойство конфигурации. По сути это
prehook
prehook на установку значения. Обычно данный метод определяется для выполнения какой-либо логики перед установкой значения конфигурации. Пример Fiddle тут.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Example applier definition
applyMake : function (newValue, oldValue) {
// Execute some arbitrary logic
if(oldValue.length > newValue.length) {
return oldValue.toUpperCase();
}
return newValue.toUpperCase();
}
// Example applier definition applyMake : function (newValue, oldValue) { // Execute some arbitrary logic if(oldValue.length > newValue.length) { return oldValue.toUpperCase(); } return newValue.toUpperCase(); }
// Example applier definition
applyMake : function (newValue, oldValue) {
    // Execute some arbitrary logic
    if(oldValue.length > newValue.length) {
        return oldValue.toUpperCase();
    } 
 
    return newValue.toUpperCase(); 
}

4. Updater (создается вручную)

Эта функция вызывается setter’ом после изменении свойства конфигурации (например, из 

Foo
Foo до
Bar
Bar). По сути это
posthook
posthook на установку значения. Обычно используется классом для реакции на изменение свойства конфигурации. Пример Fiddle тут.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Example updater definition
updateModel : function (newValue, oldValue) {
if(newValue === 'Accord') {
this.setMake('Honda');
} else if(newValue === 'Camry') {
this.setMake('Toyota');
}
}
// Example updater definition updateModel : function (newValue, oldValue) { if(newValue === 'Accord') { this.setMake('Honda'); } else if(newValue === 'Camry') { this.setMake('Toyota'); } }
// Example updater definition
updateModel : function (newValue, oldValue) {
    if(newValue === 'Accord') {
        this.setMake('Honda');
    } else if(newValue === 'Camry') {
        this.setMake('Toyota');
    }
}

Типовые ситуации использования

Изменение состояние другого компонента, при изменении значения свойства конфигурации.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Enable Drive button if make is set
applyMake : function (newValue, oldValue) {
if(newValue) {
this.down('button#drivebutton').enable();
}
return newValue;
}
// Enable Drive button if make is set applyMake : function (newValue, oldValue) { if(newValue) { this.down('button#drivebutton').enable(); } return newValue; }
// Enable Drive button if make is set
applyMake : function (newValue, oldValue) {
    if(newValue) {
      this.down('button#drivebutton').enable();
    }
    return newValue;
}

Создание требуемой зависимость после установки значения свойства конфигурации.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Instantiate a store if "storeUrl" config property is set
applyStoreUrl : function (url) {
if(url) {
this.setStore(Ext.create('Ext.data.Store', {
fields : ['name'],
proxy : {
type : 'ajax',
url : url,
}
}));
}
return url;
}
// Instantiate a store if "storeUrl" config property is set applyStoreUrl : function (url) { if(url) { this.setStore(Ext.create('Ext.data.Store', { fields : ['name'], proxy : { type : 'ajax', url : url, } })); } return url; }
// Instantiate a store if "storeUrl" config property is set
applyStoreUrl : function (url) {
    if(url) {
        this.setStore(Ext.create('Ext.data.Store', {
            fields : ['name'],
            proxy  : {
                type : 'ajax',
                url  : url,
            }
        }));
    }
       
    return url;
}

Создание дочернего класса на основе значения конфигурации.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Instantiate Ext.Toolbar with config and return class instance
applyHeaderToolbar : function (cfg) {
if(cfg) {
var tbar = this.tbar = Ext.create('Ext.Toolbar', cfg);
return tbar;
}
}
// Instantiate Ext.Toolbar with config and return class instance applyHeaderToolbar : function (cfg) { if(cfg) { var tbar = this.tbar = Ext.create('Ext.Toolbar', cfg); return tbar; } }
// Instantiate Ext.Toolbar with config and return class instance
applyHeaderToolbar : function (cfg) {
    if(cfg) {
        var tbar = this.tbar = Ext.create('Ext.Toolbar', cfg);
        return tbar;
    }
}

Теперь мы разобрались с классовой системой которая появилась в ExtJS 4.0 . Далее я расскажу о новых функциях Ext.Config, которые появились в ExtJS 6.

Ext.Config

1. cached

Если cached установлено как true, свойство config будет храниться в прототипе класса, как только у первого экземпляра была возможность обработать значение по умолчанию. То есть другими словами Applier будет вызван с дефолтным значением, только для первого экземпляра класса — если значение явно не указанно. Пример можно посмотреть тут , а если лень то GIF чуть ниже.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
config: {
foo: {
$value: 42,
cached: true
}
}
config: { foo: { $value: 42, cached: true } }
config: {
    foo: {
        $value: 42,
        cached: true
    }
}

Cached — очень специфичное свойство, реализация может быть найдена в свойстве Ext.util.ElementContainer.childEls. Кэширование 

childEls
childEls в прототипе является оптимизацией — оно сохраняет память и снижает нагрузку на CPU при создании множества инстансов класса.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
childEls: {
$value: {},
cached: true,
lazy: true,
merge: function (newValue, oldValue, target, mixinClass) {
childEls: { $value: {}, cached: true, lazy: true, merge: function (newValue, oldValue, target, mixinClass) {
childEls: {
    $value: {},
    cached: true,
    lazy: true,
 
    merge: function (newValue, oldValue, target, mixinClass) {

Проверка свойств конфигураций на директиву cached легко достигается путем доступа к свойству cachedConfigs с помощью метода getConfigurator() :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
myObj.self.getConfigurator().cachedConfigs
myObj.self.getConfigurator().cachedConfigs
myObj.self.getConfigurator().cachedConfigs

Это становится возможным из-за того что Ext.Configurator, создается при первом вызове getConfigurator() для определенного класса.

2. evented

Если evented установлено как true, свойство конфигурации будет рассматриваться как Ext.Evented. Это означает, что всякий раз, когда вызывается setter этого свойства конфигурации, ExtJS автоматически создает событие [configname + change]. Обратите внимание, что вы должны подключить миксин Ext.mixin.Observable и правильно его проинициализировать в конструкторе. Пример использования на Fiddle тут, а GIF чуть ниже.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Ext.define('MyClass', {
mixins: ['Ext.mixin.Observable'],
config: {
foo: {
$value: 42,
evented: true
}
},
constructor: function(config) {
console.log('MyClass Instantiated');
this.initConfig(config);
this.mixins.observable.constructor.call(this, config);
return this;
}
});
myObj = new MyClass();
myObj.on('foochange', function() {
console.log(arguments);
});
Ext.define('MyClass', { mixins: ['Ext.mixin.Observable'], config: { foo: { $value: 42, evented: true } }, constructor: function(config) { console.log('MyClass Instantiated'); this.initConfig(config); this.mixins.observable.constructor.call(this, config); return this; } }); myObj = new MyClass(); myObj.on('foochange', function() { console.log(arguments); });
Ext.define('MyClass', {
    mixins: ['Ext.mixin.Observable'],
    config: {
        foo: {
            $value: 42,
            evented: true
        }
    },
    constructor: function(config) {
        console.log('MyClass Instantiated');
        this.initConfig(config);
        this.mixins.observable.constructor.call(this, config);
        return this;
    }
});
myObj = new MyClass();
myObj.on('foochange', function() {
   console.log(arguments);
});

Другим способом определения evented свойств конфигураций является использование eventedConfig класса Ext.Evented, который обрабатывается в процессе работы onClassExtended:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Ext.define('MyEventedClass', {
extend: 'Ext.Evented',
eventedConfig: {
boo: 34
},
constructor: function(config) {
this.initConfig(config);
this.mixins.observable.constructor.call(this, config);
return this;
}
});
myEventedObj = new MyEventedClass();
myEventedObj.on('boochange', function() {
console.log('boochange');
console.log(arguments);
});
Ext.define('MyEventedClass', { extend: 'Ext.Evented', eventedConfig: { boo: 34 }, constructor: function(config) { this.initConfig(config); this.mixins.observable.constructor.call(this, config); return this; } }); myEventedObj = new MyEventedClass(); myEventedObj.on('boochange', function() { console.log('boochange'); console.log(arguments); });
Ext.define('MyEventedClass', {
    extend: 'Ext.Evented',
 
    eventedConfig: {
        boo: 34
    },
    constructor: function(config) {
        this.initConfig(config);
        this.mixins.observable.constructor.call(this, config);
        return this;
    }
});
myEventedObj = new MyEventedClass();
myEventedObj.on('boochange', function() {
    console.log('boochange');
    console.log(arguments);
});

Определенный класс должен наследоваться от Ext.Evented для создания evented свойств конфигураций. Реализация этого подхода может быть найдена в свойстве Ext.Widget.width.

3. lazy

Если lazy установлено ​​как true, свойство конфигурации не будет немедленно инициализировано во время вызова initConfig().

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
config: {
foo: {
$value: 42,
lazy: true
}
}
config: { foo: { $value: 42, lazy: true } }
config: {
     foo: {
         $value: 42,
         lazy: true
     }
 }

Пример на Fiddle можно найти тут, GIF ниже.

Реализация может быть найдена в свойстве Ext.app.Application.mainView, где mainView создается именно как lazy. Таким образом, с точки зрения производительности, он создается, когда он используется, а не когда приложение создается.

4. merge

Конфигурация merge принимает функцию, которая будет вызвана при создании экземпляров или если уже определены производные классы. Функция merge принимает новые значения и унаследованное значение и возвращает комбинированное значение конфигурации. При последующих вызовах это возвращаемое значение будет предоставлено как oldValue через аргумент. Пример на Fiddle тут, картинка ниже.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Ext.define('MyClass', {
config: {
foo: {
$value: [42],
merge: function(newValue, oldValue) {
var val = [].concat(newValue, oldValue);
return val;
}
}
},
constructor: function(config) {
this.initConfig(config);
return this;
}
});
Ext.define('MyExtendedClass', {
extend: 'MyClass',
foo: [23]
});
var myObj = new MyClass({
foo: 123
});
//MyClass.foo: – [123, 42]
var myExtendedObj = new MyExtendedClass({
foo: 321
});
//MyExtendedClass.foo: – [321, 23, 42]
Ext.define('MyClass', { config: { foo: { $value: [42], merge: function(newValue, oldValue) { var val = [].concat(newValue, oldValue); return val; } } }, constructor: function(config) { this.initConfig(config); return this; } }); Ext.define('MyExtendedClass', { extend: 'MyClass', foo: [23] }); var myObj = new MyClass({ foo: 123 }); //MyClass.foo: – [123, 42] var myExtendedObj = new MyExtendedClass({ foo: 321 }); //MyExtendedClass.foo: – [321, 23, 42]
Ext.define('MyClass', {
    config: {
        foo: {
            $value: [42],
            merge: function(newValue, oldValue) {
                var val = [].concat(newValue, oldValue);
                return val;
            }
        }
    },
    constructor: function(config) {
        this.initConfig(config);
        return this;
    }
});
 
Ext.define('MyExtendedClass', {
    extend: 'MyClass',
    foo: [23]
});
 
var myObj = new MyClass({
    foo: 123
});
//MyClass.foo:  – [123, 42]
 
var myExtendedObj = new MyExtendedClass({
    foo: 321
});
//MyExtendedClass.foo:  – [321, 23, 42]

Реализация может быть найдена в свойстве Ext.panel.Header.title, где merge используется для создания заголовка по умолчанию:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
title: {
$value: {
xtype: 'title',
flex: 1
},
merge: function(newValue, oldValue) {
if (typeof newValue !== 'object') {
newValue = {
text: newValue
};
}
return Ext.merge(oldValue ? Ext.Object.chain(oldValue) : {}, newValue);
}
}
title: { $value: { xtype: 'title', flex: 1 }, merge: function(newValue, oldValue) { if (typeof newValue !== 'object') { newValue = { text: newValue }; } return Ext.merge(oldValue ? Ext.Object.chain(oldValue) : {}, newValue); } }
title: {
    $value: {
        xtype: 'title',
        flex: 1
    },
    merge: function(newValue, oldValue) {
        if (typeof newValue !== 'object') {
            newValue = {
                text: newValue
            };
        }
        return Ext.merge(oldValue ? Ext.Object.chain(oldValue) : {}, newValue);
    }
}

Кроме того, вы можете использовать метод Ext.Config.mergeSets для выполнения ваших слияний:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
twoWayBindable: {
$value: null,
lazy: true,
merge: function (newValue, oldValue) {
return this.mergeSets(newValue, oldValue);
}
}
twoWayBindable: { $value: null, lazy: true, merge: function (newValue, oldValue) { return this.mergeSets(newValue, oldValue); } }
twoWayBindable: {
    $value: null,
    lazy: true,
    merge: function (newValue, oldValue) {
        return this.mergeSets(newValue, oldValue);
    }
}

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *