﻿$engine JScript
$uname ScriptFormClass
$dname Класс ScriptForm
$addin stdlib

////////////////////////////////////////////////////////////////////////////////////////
////{ Cкрипт-библиотека ScriptForm (ScriptForm.js) для проекта "Снегопат"
////
//// Описание: Базовый класс ScriptForm для наследования при реализации
//// форм скрипта. Поддерживает автоматическое назначение обработчиков событий
//// формы и элементов управления формы. Пример использования см. в скрипте
////    Tests\ScriptFormExample\ScriptFormClassExample.js
////
//// Классы, наследующие от ScriptForm сами могут быть использованы в качестве
//// родителей. При этом будут унаследованы все обработчики событий из родительского
//// класса, если их не переопределить в дочернем.
////
//// Автор: Александр Кунташов <kuntashov@gmail.com>, http://compaud.ru/blog
////}
////////////////////////////////////////////////////////////////////////////////////////

ScriptForm = stdlib.Class.extend({

    //{ Свойства
    
    // Отключить автоназначение обработчиков событий.
    disableAutoEvents: false,
    
    // Путь к сохраняемым настройкам в хранилище настроек.
    settingsRootPath : '',
    
    /* Настройки: сохраняемые реквизиты формы.
    Ключ - имя значение перечисления ProfileStoreType, тип хранилища, 
    в котором хранить настройки. Значение - ассоциативный массив. 
    Ключ - имя реквизита формы, который сохранять. 
    Значение - значение настройки по умолчанию. */
    settings : {
        //pflSnegopat: {/* FormPropName : DefaultValue, ... */},
        //pflBase: {},
        //pflBaseUser: {},
        //pflCompBase: {},
        //pflCompBaseUser: {},
        //pflComputer: {},
        //pflSeanse: {}
    },

    //} Свойства

    construct: function (formPath) {

	    this.form = null;
    	this.handlers = {};
        
        this.loadForm(formPath);        
    },
    
    show: function (modal) {
        return modal ? this.form.DoModal() : this.form.Open();
    },    
        
    close: function (retVal) {
        if (this.isOpen())
            this.form.Close(retVal);
    },
        
    isOpen: function () {
        return this.form.IsOpen();
    },
        
    addHandler: function (eventName, handler) {
    
        if (!this.hasEventHandler(eventName))
            this.initEvenHandler(eventName);            
        
        this.handlers[eventName].push(handler);
    },
    
    fireEvent : function (eventName, eventArgs) {
    
        if (!this.hasEvent(eventName))
            this.throwError('Неизвестное имя события: ' + eventName + '!');
    
        this.fire(eventName, eventArgs);
    },

    hasEvent : function (eventName) {
        return !!ScriptForm.FORM_EVENTS[eventName];
    },
    
    throwError: function (error) {
        Message(error);
        throw new Error(error);
    },
   
    // Возвращает маccив всех кнопок коммандной панели с заданным именем.
    // При поиске кнопки командной панели кешируются, чтобы повторный поиск
    // кнопок в этой же командной панели выполнялся максимально быстро.
    getCommandBarButtonsByName: function(cmdBar, btName) {
 
        // Если ни одной командной панели еще не обрабатывалось, то 
        // проинициализируем кэш кнопок командных панелей.
        if (!this._cmdBarsCache)
            this._cmdBarsCache = {};
            
        // Если командная панель обрабатывается впервые, 
        // то заполним кэш кнопок этой командной панели.
        if (!this._cmdBarsCache[cmdBar.Name]) {
     
            // Чтобы не импортировать весь глобальный контекст.
            var CommandBarButtonType = globalContext("{D041F9A0-476B-4558-8EFC-D895DC695E72}").CommandBarButtonType;
            
            // Рекурсивно кэширует кнопки командной панели в ассоциативном массиве
            // со следующей структурой:
            //  { ИмяКнопки => [Массив кнопок с одинаковым именем], ... }
            function fillBtCache(buttons, cache) {
                for (var i=0; i<buttons.Count(); i++) 
                {
                    var bt = buttons.Get(i);
                    if (bt.ButtonType == CommandBarButtonType.Action) 
                    {
                        if (!cache[bt.Name])
                            cache[bt.Name] = new Array();
                        cache[bt.Name].push(bt);
                    }
                    else if (bt.ButtonType == CommandBarButtonType.Popup)
                    {
                        fillBtCache(bt.Buttons, cache);
                    }
                }
                return cache;
            }
            
            this._cmdBarsCache[cmdBar.Name] = fillBtCache(cmdBar.Buttons, {});
        }
        
        return this._cmdBarsCache[cmdBar.Name][btName];            
    },
   
    //{ Чтение/сохранение сохраняемых значений (реквизитов формы).
    hasSettings : function () {
        return this.settingsRootPath && this.settings;
    },
    
    loadSettings : function () {
        
        if (!this.hasSettings())
            return false;
			
		var _reuseDefaults = false; // Пока так обходим неприятный баг с настройками.
        if (!this._defaultSettings) {
        	this._defaultSettings = {};			
        	for(var prop in this.settings) {
	        	if (this.settings[prop].DefaultSettings) { 
					this._defaultSettings[prop] = this.settings[prop].DefaultSettings
					_reuseDefaults = true;
				}
				else {
					this._defaultSettings[prop] = this.settings[prop];
				}
        	}
        }
		
		if (_reuseDefaults) {
			this.settings = this._defaultSettings;
		}
		            
        var sm = stdlib.require('SettingsManagement.js').SettingsManagement;
        for (var pflType in this._defaultSettings) 
        {
            var defaults = this._defaultSettings[pflType];
            // Имя ключа = строка - имя значения перечисления. Выполняя строку получаем необходимое значение. 
            var pflTypeValue = eval(pflType);
					
            var settings = sm.CreateManager(this.settingsRootPath, defaults, pflTypeValue);
            
            settings.LoadSettings();
            if (this.form)
                settings.ApplyToForm(this.form);            
    
            this.settings[pflType] = settings;
        }
        
        return true;
    },
    
    saveSettings : function () {
        
        if (!this.hasSettings())
            return false;
        
        for (var pflType in this.settings)
        {
            settings = this.settings[pflType];
            settings.ReadFromForm(this.form);
            settings.SaveSettings();
        }
        
        return true;
    },
	
	restoreDefaultSettings: function () {
		this.settings = this._defaultSettings;
	},
    //} Чтение/сохранение сохраняемых значений (реквизитов формы).
    
    //{ Приватные методы
    loadForm: function (path) {
        this.form = loadScriptForm(path, this);
        // Автоматически подключим обработчики событий.
        if (!this.disableAutoEvents) 
        {
            // Автоматически подключаем обработчики событий формы.
            // Метод объекта считается обработчиком события формы, если
            // его имя имеет форму Form_ИмяСобытия.
            for (var event in ScriptForm.FORM_EVENTS) 
            {
                if (typeof this['Form_' + event] == 'function') 
                {
                    this.initEvenHandler(event);
                    this.handlers[event].push(this['Form_' + event]);
                }   
            }
      
            // Автоматически подключаемся к обработчикам событий элементов формы.
            // Метод объекта считается обработчиком события элемента формы, если
            // его имя имеет форму ИмяЭлементаУправления_ИмяСобытия.
            // Имена обработчиков событий элементов управления колонок табличного 
            // поля должны иметь имя вида ИмяТабличногоПоля_ИмяКолонки_ИмяСобытия.
            // Имена обработчиков событий нажатия кнопок командной панели 
            // должны формироваться в виде ИмяКоманднойПанели_ИмяКнопки.
            for(var fname in this) 
            {
                if (typeof this[fname] == 'function')
                {
                    var matches = fname.match(/([^_]+?)_([^_]+)(?:_(.+))?/);
                    if (matches && matches[1] && matches[1] != 'Form') 
                    {
                        var ctrl = this.form.Controls.Find(matches[1]);
                        if (ctrl && !matches[3])
                        {
                            var tName = toV8Value(ctrl).typeName();
                            if (tName == 'КомманднаяПанель' || tName == 'CommandBar') 
                            {
                                // Обработчик нажатия кнопки командной панели (ИмяКоманднойПанели_ИмяКнопки).
                                var buttons = this.getCommandBarButtonsByName(ctrl, matches[2]);
                                if (buttons) { 
                                    var action = v8New('Action', fname);
                                    for (var i=0; i<buttons.length; i++) 
                                    {
                                        // По непонятной причине при установке свойства Action 
                                        // возникает исключение "Объект не поддерживает это свойство или метод",
                                        // но свойство все равно устанавливается, поэтому для обхода проблемы
                                        // исключение проглатываем.
                                        try {
                                            buttons[i].Action = action;
                                        } catch(e){ /*Message(e.description);*/}
                                    }
                                }
                            }
                            else
                            {
                                // Событие элемента управления на форме (ИмяЭлементаУправления_ИмяСобытия).
                                ctrl.SetAction(matches[2], v8New('Action', fname));                            
                            }
                        }
                        else if (ctrl)
                        {
                            // Событие элемента управления колонки табличного поля (ИмяТабПоля_ИмяКолонки_ИмяСобытия).
                            var col = ctrl.Columns.Find(matches[2]);
                            col.Control.SetAction(matches[3], v8New('Action', fname));
                        }
                    }
                }
            }

        }
    },
    
    hasEventHandler : function (eventName) {
        return this.hasEvent(eventName) && this.handlers && this.handlers[eventName];
    },
    
    initEvenHandler : function (eventName) {
        
        if (!this.hasEvent(eventName))
            this.throwError('Неизвестное имя события: ' + eventName + '!');
                        
        var ed = ScriptForm.FORM_EVENTS[eventName];
        
        // Генерируем программный код, создающий проксирующий обработчик события формы вида
        // new Function("a0", "a1", "this.fire(\"BeforeOpen\", arguments)");        
        var ff = 'new Function(';        
        for (var i=0; i<ed.args.length; i++)
            ff += '"a' + i.toString() + '",';
        ff += '"this.fire(\\\"' + eventName + '\\\", arguments)");';
        
        // Создаем проксирующий обработчик события нашей формы
        // и устанавл
        // иваем в качестве обработчика события.
        
        this[this.hName(eventName)] = eval(ff);
                
        this.handlers[eventName] = new Array();        
        this.form.SetAction(eventName, v8New('Action', this.hName(eventName)));
        
    }, 

    hName: function (eventName) {
        return '__' + eventName;
    },
      
    fire: function (eventName, eventArgs) {
        // Вызываем все обработчики, подписанные на событие.
        for (var i=0; i<this.handlers[eventName].length; i++)
            this.handlers[eventName][i].apply(this, eventArgs);    
    }    
    //} Приватные методы    
});

// static
ScriptForm.FORM_EVENTS = {
    // ПередОткрытием(Отказ, СтандартнаяОбработка)
    BeforeOpen: { args: ['Отказ', 'СтандартнаяОбработка'] },
    
    // ПриОткрытии()
    OnOpen: { args: [] },
    
    // ПриПовторномОткрытии(СтандартнаяОбработка)
    OnReopen: { args: ['СтандартнаяОбработка'] },
    
    // ОбновлениеОтображения()
    RefreshDisplay: { args: [] },
    
    // ПередЗакрытием(Отказ, СтандартнаяОбработка)
    BeforeClose: { args: ['Отказ', 'СтандартнаяОбработка'] },
    
    // ПриЗакрытии()
    OnClose: { args: [] },
    
    // ОбработкаВыбора(ЗначениеВыбора, Источник)
    ChoiceProcessing: { args: ['ЗначениеВыбора', 'Источник'] },
    
    // ОбработкаАктивизацииОбъекта(АктивныйОбъект, Источник)
    ObjectActivationProcessing: { args: ['АктивныйОбъект', 'Источник'] },
    
    // ОбработкаЗаписиНовогоОбъекта(Объект, Источник) 
    NewObjectWriteProcessing: { args: ['Объект', 'Источник'] },

    // ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
    NotificationProcessing: { args: ['ИмяСобытия', 'Параметр', 'Источник'] },

    // ОбработкаВнешнегоСобытия(Источник, Событие, Данные)
    ExternalEvent: { args: ['Источник', 'Событие', 'Данные'] },

    // ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)
    FillCheckProcessing: { args: ['Отказ', 'ПроверяемыеРеквизиты'] },

    // ПриСменеСтраницы(ТекущаяСтраница)
    OnCurrentPageChange: { args: ['ТекущаяСтраница'] }    
}
