С самого начала ExtJS реализовала систему классов для расширения JavaScript. Цель системы классов заключалась в поддержке эффективной организации кода и реализации псевдоклассического наследования, существующего во многих объектно-ориентированных языках программирования. Давным давно, с выходом ExtJS 4.0 Sencha решила пересмотреть свою систему классов и улучшить ее с помощью большего количества объектно-ориентированных методов. В частности, они разработали конфигурационную систему классов, наверное хоть раз вы видели свойство config: { ... }
в определении классов в ExtJS. Эта статья посвящена обзору возможностей проектирования классов.
Определение классов
В ExtJS большинство классов наследуется от Ext.Base, за некоторым исключением классы должны следовать определенному соглашению об именах, связанному непосредственно с их файловым путем. Вот пример определения класса, создания экземпляра и его использования. Fiddle доступен тут.
// 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: { ... }
в определении класса, то движок ExtJS сгенерирует 2 основных метода для работы с этим свойством конфигурации.
1. Getter (создается автоматически)
Стандартный метод доступа, который возвращает текущее значение свойства конфигурации. Пример Fiddle тут.
car.getMake(); // returns “Honda”
2. Setter (создается автоматически)
Мутатор, который устанавливает значение свойства конфигурации в экземпляре класса. Пример Fiddle тут.
car.setMake(“Toyota”); car.getMake(); // returns “Toyota”
3. Applier (создается вручную)
Эта функция вызывается setter’ом при изменении свойства конфигурации (например, из undefined
до Foo
). Данная функция должна вернуться значение, которое будет установлено в данное свойство конфигурации. По сути это prehook
на установку значения. Обычно данный метод определяется для выполнения какой-либо логики перед установкой значения конфигурации. Пример Fiddle тут.
// 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
до Bar
). По сути это posthook
на установку значения. Обычно используется классом для реакции на изменение свойства конфигурации. Пример Fiddle тут.
// Example updater definition updateModel : function (newValue, oldValue) { if(newValue === 'Accord') { this.setMake('Honda'); } else if(newValue === 'Camry') { this.setMake('Toyota'); } }
Типовые ситуации использования
Изменение состояние другого компонента, при изменении значения свойства конфигурации.
// Enable Drive button if make is set applyMake : function (newValue, oldValue) { if(newValue) { this.down('button#drivebutton').enable(); } return newValue; }
Создание требуемой зависимость после установки значения свойства конфигурации.
// 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 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 чуть ниже.
config: { foo: { $value: 42, cached: true } }
Cached — очень специфичное свойство, реализация может быть найдена в свойстве Ext.util.ElementContainer.childEls. Кэширование childEls
в прототипе является оптимизацией — оно сохраняет память и снижает нагрузку на CPU при создании множества инстансов класса.
childEls: { $value: {}, cached: true, lazy: true, merge: function (newValue, oldValue, target, mixinClass) {
Проверка свойств конфигураций на директиву cached легко достигается путем доступа к свойству cachedConfigs с помощью метода getConfigurator() :
myObj.self.getConfigurator().cachedConfigs
Это становится возможным из-за того что Ext.Configurator, создается при первом вызове getConfigurator() для определенного класса.
2. evented
Если evented установлено как true, свойство конфигурации будет рассматриваться как Ext.Evented. Это означает, что всякий раз, когда вызывается setter этого свойства конфигурации, ExtJS автоматически создает событие [configname + change]. Обратите внимание, что вы должны подключить миксин Ext.mixin.Observable и правильно его проинициализировать в конструкторе. Пример использования на Fiddle тут, а GIF чуть ниже.
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:
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().
config: { foo: { $value: 42, lazy: true } }
Пример на Fiddle можно найти тут, GIF ниже.
Реализация может быть найдена в свойстве Ext.app.Application.mainView, где mainView создается именно как lazy. Таким образом, с точки зрения производительности, он создается, когда он используется, а не когда приложение создается.
4. merge
Конфигурация merge принимает функцию, которая будет вызвана при создании экземпляров или если уже определены производные классы. Функция merge принимает новые значения и унаследованное значение и возвращает комбинированное значение конфигурации. При последующих вызовах это возвращаемое значение будет предоставлено как oldValue через аргумент. Пример на Fiddle тут, картинка ниже.
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 используется для создания заголовка по умолчанию:
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 для выполнения ваших слияний:
twoWayBindable: { $value: null, lazy: true, merge: function (newValue, oldValue) { return this.mergeSets(newValue, oldValue); } }