Sandbox

Средний

Модуль в Creatio является изолированной программной единицей. Ему ничего не известно об остальных модулях системы, кроме имен модулей, от которых он зависит. Для организации взаимодействия модулей предназначен специальный объект — sandbox.

Sandbox предоставляет два ключевых механизма взаимодействия модулей в системе:

  • Механизм обмена сообщениями между модулями. 
  • Загрузка и выгрузка модулей по требованию (для визуальных модулей).  

Важно. Для того чтобы модуль мог взаимодействовать с другими модулями системы, он должен импортировать в качестве зависимости модуль sandbox.

Обмен сообщениями между модулями 

Модули могут общаться друг с другом только посредством сообщений. Модуль, которому требуется сообщить об изменении своего состояния другим модулям в системе, публикует сообщение. Если модулю необходимо получать сообщения об изменении состояний других модулей, он должен быть подписанным на эти сообщения.

На заметку. Указывать базовые модули в зависимостях "ext-base", "terrasoft", "sandbox" не обязательно, если модуль экспортирует конструктор класса. Объекты Ext, Terrasoft и sandbox после создания объекта класса модуля будут доступны как свойства объекта: this.Ext, this.Terrasoft, this.sandbox.

Зарегистрировать сообщение 

Чтобы модули могли обмениваться сообщениями, сообщения необходимо зарегистрировать.

Для регистрации сообщений модуля предназначен метод sandbox.registerMessages(messageConfig), где messageConfig — конфигурационный объект сообщений модуля.

Конфигурационный объект является коллекцией "ключ-значение", в которой каждый элемент имеет вид, представленный ниже.

Конфигурационный объект сообщения
"MessageName": {
    mode: [Режим работы сообщения],
    direction: [Направление сообщения]
}

Здесь MessageName — ключ элемента коллекции, содержащий имя сообщения. Значением является конфигурационный объект, содержащий два свойства:

  • mode — режим работы сообщения. Должно содержать значение перечисления Terrasoft.MessageMode (Terrasoft.core.enums.MessageMode).
  • direction — направление сообщения. Должно содержать значение перечисления Terrasoft.MessageDirectionType (Terrasoft.core.enums.MessageDirectionType).

Режимы обмена сообщениями (свойство mode):

  • Широковещательный — режим работы сообщения, при котором количество подписчиков заранее неизвестно. Соответствует значению перечисления Terrasoft.MessageMode.BROADCAST.
  • Адресный — режим работы сообщения, при котором сообщение может быть обработано только одним подписчиком. Соответствует значению перечисления Terrasoft.MessageMode.PTP.

Важно. В адресном режиме подписчиков может быть несколько, но сообщение обработает только один, как правило, последний зарегистрированный подписчик.

Направления сообщения (свойство direction):

  • Публикация — модуль может только опубликовать сообщение в sandbox. Соответствует значению перечисления Terrasoft.MessageDirectionType.PUBLISH.
  • Подписка — модуль может только подписаться на сообщение, опубликованное из другого модуля. Соответствует значению перечисления Terrasoft.MessageDirectionType.SUBSCRIBE.
  • Двунаправленное — позволяет публиковать и подписываться на одно и то же сообщение в разных экземплярах одного и того же класса или в рамках одной и той же иерархии наследования схем. Соответствует значению перечисления Terrasoft.MessageDirectionType.BIDIRECTIONAL.

В схемах модели представления регистрировать сообщения с помощью метода sandbox.registerMessages() не нужно. Достаточно объявить конфигурационный объект сообщений в свойстве messages.

Для отказа от регистрации сообщений в модуле можно воспользоваться методом sandbox.unRegisterMessages(messages), где messages — имя или массив имен сообщений.

Добавить сообщение в схему модуля 

Зарегистрировать сообщения можно также, добавив их в схему модуля с помощью дизайнера.

Для добавления сообщения в схему модуля:

  1. В области свойств модуля дизайнера схемы модуля добавьте сообщение в узел Сообщения(Messages).

    scr_add.png
  2. Для добавленного сообщения установите необходимые свойства:

    • Название (Name) — имя сообщения, совпадающее с ключом в конфигурационном объекте модуля.
    • Направление (Direction) — направление сообщения. Возможные значения "Подписка" ("Follow") и "Публикация" ("Publish").
    • Режим (Mode) — режим работы сообщения. Возможные значения "Широковещательное" ("Broadcast") и "Адрес" ("Address").
    scr_props.png

    Важно. В схемах модели представления добавлять сообщения в структуру схемы не нужно.

Опубликовать сообщение 

Для публикации сообщения предназначен метод sandbox.publish(messageName , messageArgs, tags).

Для сообщения, опубликованного с массивом тегов, будут вызваны только те обработчики, для которых совпадает хотя бы один тег. Сообщения, опубликованные без тегов, смогут обработать только подписчики без тегов.

Подписаться на сообщение 

Подписаться на сообщение можно, используя метод sandbox.subscribe(messageName, messageHandler, scope, tags).

Загрузка и выгрузка модулей 

При работе с пользовательским интерфейсом Creatio может возникнуть необходимость загрузки не объявленных как зависимости модулей во время выполнения приложения. 

Загрузить модуль 

Для загрузки не объявленных в качестве зависимостей модулей предназначен метод sandbox.loadModule(moduleName, config). Параметры метода:

  • moduleName — название модуля.
  • config — конфигурационный объект, содержащий параметры модуля. Обязательный параметр для визуальных модулей.
Примеры вызова метода sandbox.loadModule()
// Загрузка модуля без дополнительных параметров.
this.sandbox.loadModule("ProcessListenerV2");
// Загрузка модуля с дополнительными параметрами.
this.sandbox.loadModule("CardModuleV2", {
    renderTo: "centerPanel",
    keepAlive: true,
    id: moduleId
});

Выгрузить модуль 

Для выгрузки модуля необходимо использовать метод sandbox.unloadModule(id, renderTo, keepAlive). Параметры метода:

  • id — идентификатор модуля.
  • renderTo — название контейнера, из которого необходимо удалить представление визуального модуля. Обязателен для визуальных модулей.
  • keepAlive — признак сохранения модели модуля. При выгрузке модуля ядро может сохранить его модель для возможности использовать ее свойства, методы, сообщения. Не рекомендуется к использованию.
Примеры вызова метода sandbox.unloadModule()
...
// Метод получения идентификатора выгружаемого модуля.
getModuleId: function() {
    return this.sandbox.id + "_ModuleName";
},
...
// Выгрузка невизуального модуля.
this.sandbox.unloadModule(this.getModuleId());
...
// Выгрузка визуального модуля, ранее загруженного в контейнер "ModuleContainer".
this.sandbox.unloadModule(this.getModuleId(), "ModuleContainer");

Создать цепочку модулей 

Иногда возникает необходимость показать представление некой модели на месте представления другой модели. Например, для установки значения определенного поля на текущей странице нужно отобразить страницу выбора значения из справочника SelectData. В таких случаях нужно, чтобы модуль текущей страницы не выгружался, а на месте его контейнера отображалось представление модуля страницы выбора из справочника. Для этого можно использовать цепочки модулей.

Чтобы начать построение цепочки, достаточно добавить свойство keepAlive в конфигурационный объект загружаемого модуля.

Реализовать обмен сообщениями между модулями
Средний

Пример. Создать модуль UsrSomeModule. В модуле реализовать сообщения:

  • Адресное сообщение MessageToSubscribe с направлением Subscribe.
  • Широковещательное сообщение MessageToPublish с направлением Publish.

Подписаться на сообщение MessageToSubscribe, которое отправляет другой модуль. Отменить регистрацию сообщений.

1. Создать модуль 

  1. Перейдите в раздел Конфигурация (Configuration) и выберите пользовательский пакет, в который будет добавлена схема.
  2. На панели инструментов реестра раздела нажмите Добавить —> Модуль (Add —> Module).

  3. В дизайнере модуля заполните свойства схемы:

    • Код (Code) — "UsrSomeModule".
    • Заголовок (Title) — "SomeModule".

    Для применения заданных свойств нажмите Применить (Apply).

  4. В дизайнере модуля добавьте исходный код.

    UsrSomeModule
    /* Объявление модуля с именем UsrSomeModule. Модуль не имеет никаких зависимостей, поэтому в качестве второго параметра передается пустой массив. */
    define("UsrSomeModule", [], function() {
        Ext.define("Terrasoft.configuration.UsrSomeModule", {
            alternateClassName: "Terrasoft.UsrSomeModule",
            extend: "Terrasoft.BaseModule",
            Ext: null,
            sandbox: null,
            Terrasoft: null,
            
            init: function() {
                this.callParent(arguments);
            },
            destroy: function() {
                this.callParent(arguments);
            }
        });
        return Terrasoft.UsrSomeModule;
    });
    
  5. На панели инструментов дизайнера модуля нажмите Сохранить (Save).

2. Зарегистрировать сообщения 

  1. Объявите конфигурационные объекты сообщений в свойстве messages схемы.
  2. В метод init() добавьте вызов метода sandbox.registerMessages(), который регистрирует сообщения.
Регистрация сообщений модуля
...
/* Коллекция конфигурационных объектов сообщений. */
messages: {
    "MessageToSubscribe": {
        mode: Terrasoft.MessageMode.PTP,
        direction: Terrasoft.MessageDirectionType.SUBSCRIBE
    },
    "MessageToPublish": {
        mode: Terrasoft.MessageMode.BROADCAST,
        direction: Terrasoft.MessageDirectionType.PUBLISH
    }
},
...
init: function() {
    this.callParent(arguments);
    /* Регистрирует коллекцию сообщений. */
    this.sandbox.registerMessages(this.messages);
},
...

3. Опубликовать сообщение 

  1. В схеме модуля реализуйте метод processMessages().
  2. В методе processMessages() вызовите метод sandbox.publish(), который публикует сообщение MessageToPublish.
  3. В метод init() добавьте вызов метода processMessages().
Публикация сообщения модуля
...
init: function() {
    ...
    this.processMessages();
},
...
processMessages: function() {
    this.sandbox.publish("MessageToPublish", null, [this.sandbox.id]);
},
...

4. Подписаться на сообщение 

  1. В метод processMessages() добавьте вызов метода sandbox.subscribe(), который подписывается на сообщение MessageToSubscribe, которое отправляет другой модуль.
  2. В параметрах метода укажите метод-обработчик onMessageSubscribe() и добавьте его в исходный код модуля.
Подписка на сообщение другого модуля
...
processMessages: function() {
    this.sandbox.subscribe("MessageToSubscribe", this.onMessageSubscribe, this, ["resultTag"]);
    this.sandbox.publish("MessageToPublish", null, [this.sandbox.id]);
},
onMessageSubscribe: function(args) {
    console.log("'MessageToSubscribe' received");
    /* Изменяет параметр. */
    args.arg1 = 15;
    args.arg2 = "new arg2";
    /* Возвращает результат. */
    return args;
},
...

5. Отменить регистрацию сообщений 

Отмена регистрации сообщений
...
destroy: function() {
    if (this.messages) {
        var messages = this.Terrasoft.keys(this.messages);
        /* Отменяет регистрацию массива сообщений. */
        this.sandbox.unRegisterMessages(messages);
    }
    this.callParent(arguments);
}
...
Полный исходный код схемы страницы
Реализовать асинхронный обмен сообщениями
Средний

Пример. Реализовать асинхронный обмен сообщениями между модулями.

  1. В модуле, который публикует сообщение, укажите конфигурационный объект в качестве параметра функции-обработчика.
  2. В конфигурационный объект добавьте функцию обратного вызова (callback-функцию).

    Пример публикации сообщения и получения результата
    ...
    this.sandbox.publish("AsyncMessageResult",
    /* Конфигурационный объект, который указан в качестве параметра функции-обработчика. */
    {
        /* Функция обратного вызова. */
        callback: function(result) {
            this.Terrasoft.showInformation(result);
        },
        /* Контекст выполнения функции обратного вызова. */
        scope: this
    });
    ...
    
  3. В методе-обработчике модуля-подписчика при подписке на сообщение верните асинхронный результат. Используйте параметр callback-функции опубликованного сообщения.

    Пример подписки на сообщение
    ...
    this.sandbox.subscribe("AsyncMessageResult",
    /* Функция-обработчик сообщения. */
    function(config) {
        /* Обрабатывает входящий параметр. */
        var config = config || {};
        var callback = config.callback;
        var scope = config.scope || this;
        /* Подготавливает результирующее сообщение. */
        var result = "Message from callback function";
        /* Выполняет функцию обратного вызова. */
        if (callback) {
            callback.call(scope, result);
        }
    },
    /* Контекст выполнения функции-обработчика сообщения. */
     this);
    ...
    
Пример использования двунаправленных сообщений
Средний

В схеме BaseEntityPage пакета CrtNUI зарегистрировано сообщение CardModuleResponse. Схема BaseEntityPage — базовая схема модели представления страницы записи.

BaseEntityPage
define("BaseEntityPage", [...], function(...) {
    return {
        messages: {
            ...
            "CardModuleResponse": {
                "mode": this.Terrasoft.MessageMode.PTP,
                "direction": this.Terrasoft.MessageDirectionType.BIDIRECTIONAL
            },
            ...
        },
        ...
    };
});

Например, Creatio публикует сообщение после сохранения измененной записи. Эта функциональность реализована в дочерней схеме BasePageV2 пакета CrtNUI.

BasePageV2
define("BasePageV2", [..., "LookupQuickAddMixin", ...], function(...) {
    return {
        ...
        methods: {
            ...
            onSaved: function(response, config) {
                ...
                    this.sendSaveCardModuleResponse(response.success);
                ...
            },
            ...
            sendSaveCardModuleResponse: function(success) {
                var primaryColumnValue = this.getPrimaryColumnValue();
                var infoObject = {
                    action: this.get("Operation"),
                    success: success,
                    primaryColumnValue: primaryColumnValue,
                    uId: primaryColumnValue,
                    primaryDisplayColumnValue: this.get(this.primaryDisplayColumnName),
                    primaryDisplayColumnName: this.primaryDisplayColumnName,
                    isInChain: this.get("IsInChain")
                };
                return this.sandbox.publish("CardModuleResponse", infoObject, [this.sandbox.id]);
            },
            ...
        },
        ...
    };
});

Миксин LookupQuickAddMixin указан в схеме BasePageV2 в качестве зависимости. В миксине реализована подписка на сообщение CardModuleResponse. Читайте подробнее в статье Клиентская схема.

LookupQuickAddMixin
define("LookupQuickAddMixin", [...], function(...) {
    Ext.define("Terrasoft.configuration.mixins.LookupQuickAddMixin", {
        alternateClassName: "Terrasoft.LookupQuickAddMixin",
        ...
        /* Объявляет сообщение. */
        _defaultMessages: {
            "CardModuleResponse": {
                "mode": this.Terrasoft.MessageMode.PTP,
                "direction": this.Terrasoft.MessageDirectionType.BIDIRECTIONAL
            }
        },
        ...
        /* Регистрирует сообщение. */
        _registerMessages: function() {
            this.sandbox.registerMessages(this._defaultMessages);
        },
        ...
        /* Инициализирует экземпляр класса. */
        init: function(callback, scope) {
            ...
            this._registerMessages();
            ...
        },
        ...
        /* Выполняется после добавления новой записи в справочник. */
        onLookupChange: function(newValue, columnName) {
            ...
            /* Выполняется цепочка вызовов методов.
            В результате вызывается метод _subscribeNewEntityCardModuleResponse(). */
            ...
        },
        ...
        /* Метод, в котором выполняется подписка на сообщение "CardModuleResponse".
        В callback-функции выполняется установка в справочное поле значения, которое отправлено при публикации сообщения. */
        _subscribeNewEntityCardModuleResponse: function(columnName, config) {
            this.sandbox.subscribe("CardModuleResponse", function(createdObj) {
                var rows = this._getResponseRowsConfig(createdObj);
                this.onLookupResult({
                    columnName: columnName,
                    selectedRows: rows
                });
            }, this, [config.moduleId]);
        },
        ...
    });
    return Terrasoft.LookupQuickAddMixin;
});

Последовательность работы с двунаправленным сообщением при добавлении нового адреса на странице контакта:

  1. Модуль ContactAddressPageV2 загружается в цепочку модулей на деталь Адреса (Addresses).

    scr_add_address.png
  2. Oткрывается страница адреса контакта.

    scr_new_city.png

    Поскольку схема ContactAddressPageV2 наследует схемы BaseEntityPage и BasePageV2, то в ней уже зарегистрировано сообщение CardModuleResponse. Это сообщение также регистрируется в методе _registerMessages() миксина LookupQuickAddMixin при его инициализации в качестве модуля-зависимости BasePageV2.

  3. При добавлении нового значения в справочные поля страницы ContactAddressPageV2 (например, нового города) вызывается метод onLookupChange() миксина LookupQuickAddMixin.
  4. Модуль CityPageV2 загружается в цепочку модулей.
  5. В методе onLookupChange() вызывается метод _subscribeNewEntityCardModuleResponse(), в котором осуществляется подписка на сообщение CardModuleResponse.
  6. Открывается страница города (схема CityPageV2 пакета CrtUIv2).

    scr_city_page.png
  7. Поскольку схема CityPageV2 наследует схему BasePageV2, то после сохранения записи (кнопка Сохранить (Save)) выполняется метод onSaved() базовой схемы.
  8. Метод onSaved() вызывает метод sendSaveCardModuleResponse(), который публикует сообщение CardModuleResponse. При этом передается объект с необходимыми результатами сохранения.
  9. После публикации сообщения выполняется callback-функция подписчика (метод _subscribeNewEntityCardModuleResponse() миксина LookupQuickAddMixin), в которой обрабатываются результаты сохранения нового города в справочник.

Таким образом, публикация и подписка на двунаправленное сообщение выполнены в рамках одной иерархии наследования схем. В этой иерархии базовая схема BasePageV2 содержит всю необходимую функциональность.

Настроить загрузку модуля
Средний

Пример. Загрузите пользовательский визуальный модуль UsrCardModule в пользовательский модуль UsrModule.

1. Создать класс визуального модуля 

Создайте класс модуля UsrCardModule, который наследует базовый класс BaseSchemaModule. Класс должен быть инстанцируемым, то есть возвращать функцию-конструктор. В этом случае, при загрузке модуля извне вы можете передать в конструктор необходимые параметры.

UsrCardModule
/* Модуль, который возвращает экземпляр класса. */
define("UsrCardModule", [...], function(...) {
    Ext.define("Terrasoft.configuration.UsrCardModule", {
        /* Псевдоним класса. */
        alternateClassName: "Terrasoft.UsrCardModule",
        /* Родительский класс. */
        extend: "Terrasoft.BaseSchemaModule",
        /* Признак установки параметров схемы извне. */
        isSchemaConfigInitialized: false,
        /* Признак использования состояния истории при загрузке модуля. */
        useHistoryState: true,
        /* Название схемы отображаемой сущности. */
        schemaName: "",
        /* Признак использования режима совместного отображения с реестром раздела.
        Если указано значение false, то на странице отображается SectionModule. */
        isSeparateMode: true,
        /* Название схемы объекта. */
        entitySchemaName: "",
        /* Значение первичной колонки. */
        primaryColumnValue: Terrasoft.GUID_EMPTY,
        /* Режим работы страницы записи. */
        operation: ""
    });
    /* Возвращает экземпляр класса. */
    return Terrasoft.UsrCardModule;
}

2. Создать класс модуля, в который загружается визуальный модуль 

Создайте класс модуля UsrModule, который наследует класс BaseModel.

UsrModule
define("UsrModule", [...], function(...) {
    Ext.define("Terrasoft.configuration.UsrModule", {
        alternateClassName: "Terrasoft.UsrModule",
        extend: "Terrasoft.BaseModel",
        Ext: null,
        sandbox: null,
        Terrasoft: null,
    });
}

3. Загрузить модуль 

Вы можете передавать параметры в конструктор класса инстанцируемого модуля при загрузке модуля. Для этого:

  1. В классе модуля UsrModule создайте конфигурационный объект.
  2. В качестве свойств конфигурационного объекта укажите требуемые значения.
  3. Загрузите визуальный модуль UsrCardModule с помощью метода sandbox.loadModule().
  4. В метод sandbox.loadModule() добавьте свойство instanceConfig.
  5. В качестве значения свойства instanceConfig передайте конфигурационный объект, который содержит требуемые значения.
UsrModule
...
init: function() {
    this.callParent(arguments);
    /* Конфигурационный объект. Свойства объекта указываются в качестве параметров конструктора. */
    var configObj = {
        isSchemaConfigInitialized: true,
        useHistoryState: false,
        isSeparateMode: true,
        schemaName: "QueueItemEditPage",
        entitySchemaName: "QueueItem",
        operation: ConfigurationEnums.CardStateV2.EDIT,
        primaryColumnValue: "{3B58C589-28C1-4937-B681-2D40B312FBB6}"
    };

    /* Загружает модуль. */
    this.sandbox.loadModule("UsrCardModule", {
        renderTo: "DelayExecutionModuleContainer",
        id: this.getQueueItemEditModuleId(),
        keepAlive: true,
        /* Конфигурационный объект указывается в конструкторе модуля в качестве параметра. */
        instanceConfig: configObj
    }
});
...

Чтобы передать дополнительные параметры при загрузке модуля, используйте свойство parameters конфигурационного объекта. Предварительно реализуйте такое же свойство в классе модуля или в одном из родительских классов. Свойство parameters определено в базовом классе BaseModule. При создании экземпляра модуля свойство parameters модуля будет проинициализировано значениями, которые переданы в свойстве parameters конфигурационного объекта.