Sergiy Shychynov (Sergei Shichinov) Kiev, Ukraine Flex/JavaScript blog (JavaScript, Flex, ActionScript, AS3, AIR) | http://www.linkedin.com/in/shichinov | http://shichinov.moikrug.ru

Thursday, October 30, 2008

One more note about Singleton in Flex

Could you imagine that this code throws a Run-Time Error 1115 - because of class RecordTypesManagerInstance does not exist at the static initialization time

package com.os.sp.common
{
    public class RecordTypesManager
    {
        public static var instance:RecordTypesManagerInstance = new RecordTypesManagerInstance();
    }
}

class RecordTypesManagerInstance
{
    ..
}


correct code is:

package com.os.sp.common
{
    public class RecordTypesManager
    {
        public static function get instance():RecordTypesManagerInstance
        {
            if(_instance == null) _instance = new RecordTypesManagerInstance();

            return _instance;
        }

        private static var _instance:RecordTypesManagerInstance;
    }
}

class RecordTypesManagerInstance
{
}





My epistle to Flexers of my team (russian)

After three days of refactoring the code of my team - I decide to send this epistle to my "Flex software engineer":

Небольшой flex-дайджест (для флекс-разработчиков к прочтению ОБЯЗАТЕЛЬНО и крайне желательно все это применять в работе)

0)
 a) у нес нет PMD, у нас нет CheckStyle - поэтмоу иметь флексовые варнинги - НЕДОПУСТИМО
    обязательно исправляйте ситуацию которая вызвала варнинг!

 b) мы НЕ пользуемся табуляция в исходниках (в as и mxml)!

 c) отступы делаются 4 пробелами (в as и mxml)!
  
   для этого в настройках эклипса нужно везде в настройках редакторов установать испльзование пробелов вместо табуляций и размер отступа в 4 пробела
  
   для облегчения жизни и автоматического устраниния пробелов нужно использовать (пока не найдена достойная альтернатива)
   http://andrei.gmxhome.de/anyedit/index.html

1)НАПОМИНАНИЕ по конвеншенам

 a) в классах (даже если это mxml - приватные переменные (филды) объявляем в конце класса и название всегда с _ (подчеркиванием в начале) - это всегда позволяет сходу определить приватные филды класса

   если работаете с кодом в котором это соглашение нарушено (анпример испольшуются _ для локальных переменных метода - исправляйте это)
  
   быстро преименовывать переменные класса помогает ctrl-alt-R  (это единственный рефакторинг флекс билдера - и он вроде нормально работает в пределах скомпиленного класса :-))
  

 b) в mxmxl классах
  
   - сначала идет целиком описание комопнента с помощью mxml тегов (можно первый, после корневого тега, начинать с 0 отступа - для экономии места на экране)
  
   описание неймспейсов в корне
  
<?xml version="1.0" encoding="utf-8"?>
<sp:SPBox xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="com.os.sp.view.contactGroups.*"
    xmlns:controls="com.os.core.controls.*"
    xmlns:validators="com.os.core.validators.*"
    xmlns:sp="com.os.sp.controls.sp.*"
    xmlns:spTabBar="com.os.sp.controls.sp.spTabBar.*"
    width="100%"
    height="100%"
    backgroundColor="#ffffff"
    bundle="contacts"
    styleName="vboxpanel"
    verticalGap="0">
   

    <sp:Первый_Тег id="aaa" ...
  
 
   - потому идут валидаторы - которые как правило заключены в валидаторсГроуп (тег ValidatorsGroup тоже можно начинать с 0 отступа)
  
 
   - дальше (в конце) идет весь код, заключенный в тег Script (тег Script тоже можно начинать с 0 отступа для экономии места)
  
<mx:Script>
    <![CDATA[
        import org.puremvc.as3.utilities.flexpage.events.EditingStateEvent;
        import com.os.sp.constants.Pages;
        ...


 с) для сложных компонентов (сложных страниц) которые требуют для своего создания своих кастомных событий, рендереров, классов-дескрипторов, классов-ошибок или своих кастомных субкомпонентов - мы должны создавать отдельную папку со следующей структурой

в самой папке лежат mxml компонента (либо нескольких его наиболее крупных частей) и медиатор, если он необходим

  • controls - кастомыне контролы (субкомпоненты) именно для этого компонента (общие контролы которые используются в нескольких местах системы лежат в shadow-planner-flex-ui\src\com\os\sp\controls )
  • events - кастомыне события именно этого комопнента (общие события которые используются в нескольких часятх системы должны быть в shadow-planner-flex-ui\src\com\os\sp\view\events )
  • validators - кастомные валидаторы именно этого комопнента (общие валидаторы которые используются в нескольких часятх системы должны быть в shadow-planner-flex-ui\src\com\os\sp\view\validators  )
  • common - папка для всевозможных классов, необходимых для этого компонента (хелперов, утилит, классов-дескрипторов и т.д.) - структуры общие для всей системы находятся в shadow-planner-flex-ui\src\com\os\sp\common )
  • errors - то же самое для классов ошибок (наследников Error) - для отдельных компонентов определяются редко
  • constants - классы описатели - обычно редко используются для компонента, а общие константы лежат в shadow-planner-flex-ui\src\com\os\sp\constants

!!! отдельное упоминание о неймспейсе (пакеджах) из src\com\os\core - сюда складываются компоненты, которые достаточно независимы чтобы быть (возможно позднее) использованы в других проектах кроме офис шедова (независимы от этого проекта)

!!!
все классы и компоненты из com\os\core не должны иметь зависимостей от com\os\sp (его внутренних классов и структур)

пустые папки создавать не нужно - папки создаем по мере необходимости

примеры PerspectivesPage или com\os\sp\view\administration\customFields

 d) для операторов с фигурными скобками {}  - в соответствии с конвеншенами - скобки должны быть на одно уровне отступа, а для операторa if - если фигурные скобки использовались в одной ветви, то должны быть использованы и в другом

if(блабла == блаблабла)
{
    траляля;
    if(хухуху)
    { 
        делай-хохохо;
    }
else
{
    трулюлю;
}


 e) тег [Bindable] пишем над филдом (переменной) или проперти (геттром или сеттером - тем что идет первым в классе) - не в одной строке
        [Bindable]
        protected var editable:Boolean;
       
 f) если дефолтное значение булевской переменной false - то его можно не писать (это опционально)

 g) в самом классе описания идут почти по конвеншенам flex sdk, а именно
  
  • константы (пишутся большими буквами через подчеркивание) (static const) в следующем порядке порядке public - protected - private
  • все статические переменный класса (static var) (public - protected - private)
  • потом филды (var) - сначала public - потом protected    (примечание - pivate var описываются в самом конце класса и начинаются с подчерка - только они начинаются с подчерка)
  • конструктор
  • public getters and setters
  • public functions
  • protected getters and setters
  • protected functions
  • private functions
  • private vars (pivate var описываются в самом конце класса и начинаются с подчерка - только они начинаются с подчерка)  
 
2) новое в системных Messadge

 a) появился  новый тип месседжа STRICT_CONFIRM - который прсит чтото подтвердить и делает кнопку Yes (Ok) доступной только если ты введешь правильно сгенерированную случайно строку

 b) в классе SPMessage есть ряд статических функций которые позволяют получить SPMessage нужного типа (вместо использования конструктора) - лучше использовать их чем констркутор
public static function getLocalizedOkMessage(textKey:String, callback:Function=null, params:Array=null):SPMessage<br />public static function getLocalizedConfirmMessage(textKey:String, callback1:Function=null, callback2:Function=null, params:Array=null):SPMessage<br />public static function getLocalizedStrictConfirmMessage(textKey:String, callback1:Function=null, callback2:Function=null, params:Array=null):SPMessage<br />public static function getLocalizedErrorMessage(textKey:String, callback:Function=null, params:Array=null):SPMessage<br />public static function getErrorMessage(text:String, callback:Function=null):SPMessage <br /></pre></blockquote> с) SPMessage и класс его отображения теперь при переводе строк из локалей используют params - для подстановки значений в по шаблонам {0} {1} и так далее<br />        <br /> d) в базовом медиаторе SPMediator появлись<br /><br />  d.1) bundle и messageBundle - позволяют установить префиксы локализации для функций показывающих сообщения (позволяют сформипровать полный ключ локализыции)<br />  <br />  d.2) функции которые позволяют отправлять нужного типа нотификейшен для того чтобы показать нужного типа мессидж (используют вышеописанные bundle)<br />  ими можно и нужно пользоваться во всех медиаторах наследниках (практически во всех пейдж и слот медиаторах)<br />  Пример использования в PerspectivesPageMediator<br /><br /><pre>protected function showConfirmationMessage(key:String, callback1:Function=null,callback2:Function=null, params:Array=null):void<br />protected function showStrictConfirmationMessage(key:String,callback1:Function=null, callback2:Function=null,params:Array=null):void<br />protected function showSuccessfulMessage(key:String, callback1:Function=null, params:Array=null):void<br />protected function showErrorMessage(key:String, callback1:Function=null, params:Array=null):void<br />public function showMessageBox(message:SPMessage):void<br /></pre> e) написание комментариев в стиле Java-doc (as-doc) поощряется  (теги @param @default @return @see @private)<br />    для пар public геттеров-сеттеров - мы пишем комментарий для первого геттера а сеттер помечаем тегом @private<br /><br /><blockquote><pre>/**<br />* Current selected Record Type<br />*/<br />public function get currentRecordType():RecordType<br />{<br />    return _currentRecordType;<br />}<br /><br />/**<br />* @private<br />*/<br />public function set currentRecordType(value:RecordType):void<br />{<br />    if(_currentRecordType != value)<br />    {<br />        _currentRecordType = value;<br />        loadCustomFieldsList();<br />    }<br />}<br />


   если нужен хотя бы какой-то генератор геттеров сеттеров (пока адоби не разродятся флекс билдером с нормальным функционалом) можно пользоваться плагином Monkey

    http://download.eclipse.org/technology/dash/update
    http://panellabs.net/eclipse-monkey-asctionsript-generate-scripts/
    http://wiki.eclipse.org/Eclipse_Monkey_Overview#Eclipse_Monkey_Script_Exchange
    http://www.mandrew182.org.ua/node/1


3) некоторые изменения в Spxxx компонентах

  a) SPText - теперь позволяет задать setKeyParams() - для задания массива подстановоак при локализации текста по ключу key (по шаблонам {0} {1} и так далее)
      примечание - эту функциоанльность легко включить для всех компонентов использующих SPHelper
     
  b) SPFormItemCheckBox и  SPFormItemTextInput теперь позволяют подписаться на событие "change" (которое редиспатчится от обернутого ими компонента)

 

4) теоретически правильный способ использования Notification когда в теле (body) переается несколько разнотипных параметров
   создаем класс для объектов котоыре будут преедаватсья в body (этот класс лучше всего создавать в папке common рядом с классом медатора или классом команды, который получает этот нотификейшен)
  
   Если есть желание кроме постоянных полей body использовать некие дополнительные (не добавляя их в описание класса - то класс можно сделать динамическим) - но имейте в виду что для полей не описанных в классе мы теряем контроль ошибок во время компиляции и требуются дополнительыне преобразования типа во время выполнения.
  
   пример CustomFieldsForRecordTypeBody
  
   ну и соответсветнно - источник нотификейшена создает именно этот body-класс вместо обычного нетипизированного объекта, заполняет его и отсылает. А приемник сразу достает body и пользуется им для своих целей

   case SPNotification.CUSTOM_FIELDS_FOR_RECORD_TYPE_WITHIN_RECORD_RULESETS_LOADED:
  
       var body:CustomFieldsForRecordTypeBody = CustomFieldsForRecordTypeBody(notification.getBody());
       if(body.recordTypeId == RecordTypeBase.RT_CONTACT_PERSONAL_INFO ....
 
   Преимущество такого подхода - копилятор выяdляет ошибки на ранних стадиях
   Недостаток - необходимость создания нового допольнительго класса
   Рекомендуется - если необходимо передавать в теле Notification более одного параметра
  
  
5) касательно pureMVC

  a) медиаторы и прокси как правило СОЗДАЮТСЯ и регистрируются в системе один раз -
     поэтому блоки кода типа facade.registerProxy(new XXX())  в местах которые повтоно исполняются несколко раз неприемлемы - в этом случае раньше создавались множественные копии проксей или медиаторов и доступ мы имели только к последней
     теперь я поставил эксепшены в ядре PureMVC и каждый такой случай будет сразу замечен!
    
  b) может кто и не заметил, хотя работает это давно - все медиаторы описанный в дескрпторе страницы (PagesHelper) регистрируются в системе (и становятся доступными через фасад) в момент активации страеницы и удаляются из PureMVC в момент перед переходом на другую страницу
     то есть эти медиаторы становятся недоступны через фасад, но при этом сами по себе они не уничтожаются (а хранятся во внутреннем кэше приложения - и при последующем показе этой страницы не создаются заново а просто достаются из кеша и регистрируются)
     то же самое касается и компонентов, которые эти медиаторы обслуживают - создаются (находятся и регистрируются в медиаторе) они только в первый раз - когда создается медиатор.
 
  c) в случае если вы в прокси делаете запрос на сервер и возвращаете полученные данные в нотификейшене не сохраняя в самом прокси и не делая никаких дополнительных операций можно пользоваться методами, описанными в базовом прокси
      getSendNotificationResultHandler и getSendNotificationFaultHandler - которые принимают имя нотификейшена который должны послать по получении ответа от сервера и создают соответствующие callback функции
      Например:
        public function getCompanyCurrency():void
        {
            new PerspectifiedDelegate(
                getSendNotificationResultHandler(SPNotification.COMPANY_CURRENCY_LOADED),
                getSendNotificationFaultHandler(SPNotification.COMPANY_CURRENCY_FAULT)
                ,true, false).retrieveSingletonRecord(RecordTypeBase.RT_COMPANY_CURRENCY);
        }
       
     Но создание отдельных коллбэк функций как приватных методов класса является предпочтительным с точки зрения оптимизации по памяти и для простоты дальнейшего расширения функционала

6) ближайшие планы
   
    a) как вы наверное знаете упраление включением-выключением лоадера сейчас просходит следующим образом
    - включает лоадер обынчо команда ViewPаge
    - выключает - любой ответ от сервер
    - дополнительно к этому - выключением занимался метод update PageBaseMediator-а и всех его наследников в случае если устанавливался флаг resetLoadingStateWhenUpdate
        override public function update():void // use viewParams & stateParams
        {
            super.update();
            if(resetLoadingStateWhenUpdate) setLoadingState(false);
            ....


    эта схема была хороша для быстрого введения и повсеместного использования
   
    но на многих страницах это приводит к преждевременному выключению лоадера (особенно в методе update)
   
    в ближайшем будущем от этой практики необходимо отходить - будет так
    - включает лоадер команда ViewPаge
    - а выключать нужно обудет ручками в том месте где это необходимо данной конкретной странице...
   
    для того чтобы сделать переход на новую систему более плавным - можно просто последовательно отключать дефолтные хендлеры в конструкторе делегатов при соответствующих вызовах
   
    Например
   
    если было так (по умолчанию оба дифолтных хендлера вызывались и отключали loadingState
    new CustomFieldDelegate(
        loadCustomFieldsForRecordTypeResult,
        loadCustomFieldsForRecordTypeFault
        ).findCustomFieldsByRecordTypeId(recordTypeId);
   
    то теперь можно сделать так (в этом случае мы отключаем только default resutHandler - и в случае Fault - будет выводиться ошибки и отключаться лоадер)
    new CustomFieldDelegate(
        loadCustomFieldsForRecordTypeResult,
        loadCustomFieldsForRecordTypeFault
        ,false, true).findCustomFieldsByRecordTypeId(recordTypeId);

    или так (в этом случае мы отключаем все - и в случае Fault - не будет ни отключения лоадера ни сообщения об ошибке)
    new CustomFieldDelegate(
        loadCustomFieldsForRecordTypeResult,
        loadCustomFieldsForRecordTypeFault
        ,false, false).findCustomFieldsByRecordTypeId(recordTypeId);

Wednesday, October 08, 2008

[RemoteClass(alias... wrong belief

Are You know that [RemoteClass] metadata tag is equal to registerClassAlias() function. So we don't need duplicate registration operation.

package com.os.core{

[RemoteClass(alias="com.os.core.shData")]
public class shData
...
}


is the same as

registerClassAlias(getQualifiedClassName(shData), shData);

or

registerClassAlias("com.os.core::shData", shData);

How to get Class of the instance

How to get Class of the instance?

The standard approach is to use "constructor" field of the instance. But yesterday I found out that it's absent in my class instances and following code leads to Syntactic error:

var dat:shData = new shData();
//var clas:* = dat.constructor; error Syntax Error

while Object class instances still workvar dat:Object= new Object();
var clas:* = dat.constructor; // its Ok


Using something like http://livedocs.adobe.com/flex/3/langref/flash/net/package.html#getClassByAlias()
getClassByAlias(getQualifiedClassName(object));
smells bad because to work properly it needs preliminary call to registerClassAlias()

Using http://livedocs.adobe.com/flex/3/langref/flash/utils/package.html#getDefinitionByName()
var datClass:Class = getDefinitionByName(getQualifiedClassName(dat)) as Class;
looks inattractive too...

So this is my investigation

var dat:shData = new shData();
var obj:Object = new shData();
//var clas:* = dat.constructor; error Syntax Error
var clas1:* = obj.constructor;
var c1:* = getQualifiedClassName(dat);
var c2:* = getQualifiedClassName(obj);
//var c3:* = getClassByAlias(getQualifiedClassName(dat)); ReferenceError: Error #1014: Class com.os.core::shData could not be found. at global/flash.net::getClassByAlias()
//var c4:* = getClassByAlias(getQualifiedClassName(obj)); ReferenceError: Error #1014: Class com.os.core::shData could not be found. at global/flash.net::getClassByAlias()

obj = dat;
var clas2:* = obj.constructor;
var clas3:* = Object(dat).constructor;
var obj2:Object = Object(dat);
var obj3:Object = dat as Object;
var clas4:* = obj2.constructor;
var clas5:* = obj3.constructor;
// Resume
var datClass:Class = Object(dat).constructor;


And that is the result:

var datClass:Class = Object(dat).constructor;

Followers