﻿# Создать виджет с динамическим контекстом

> [HTML Version](widget-with-dynamic-context.html)

Рассмотрим пример создания и настройки пользовательского виджета **Калькулятор**, который выполняет определённую бухгалтерскую операцию, выбранную в его настройках. Набор операций, их параметры и формулы расчёта хранятся на сервере, могут обновляться и дополняться.

Несколько таких виджетов можно разместить на странице и настроить их для выполнения разных операций.

## Действия пользователя с виджетом «Калькулятор»

Для пользователя, выполняющего расчёты, виджет **Калькулятор** выглядит как кнопка на странице. Их может быть несколько.

Пользователь заполняет поля с исходными данными бухгалтерской операции и нажимает кнопку **Рассчитать**. После этого в скрипте виджета выполнится запрос на сервер для выполнения расчётов. Полученный с сервера результат отобразится на странице в отдельном поле.

**(widget-with-dynamic-context-1.png)**

Чтобы пользователи могли использовать виджет **Калькулятор**, администратор системы выполняет следующие этапы настроек:

1. Создать виджет **Калькулятор**.

2. Разместить виджет **Калькулятор** на странице и [настроить его работу для пользователей](#customize-widget-on-page). 

## Создать виджет «Калькулятор»

Рассмотрим, как создать виджет **Калькулятор** с динамическим контекстом. В нашем примере используем скрипты, в которых вместо запросов на сервер будем получать заранее подготовленные списки операций, их параметров и формул из дополнительных функций.

Выполните следующие шаги настроек:

1. [Добавьте виджет на уровне раздела](#add-widget), в котором он будет использоваться.

2. [Создайте свойства в контексте виджета](#create-properties).

3. [Создайте форму настроек виджета](#create-settings-form).

4. [Настройте внешний вид виджета и выполнение расчётов](#customize-appearance).

### Шаг 1. Добавить виджет на уровне раздела

1. В настройках раздела, в котором будет использоваться виджет, выберите пункт **Интерфейсы** и на открывшейся странице нажмите **+ Создать**.

2. Введите название **Калькулятор** и код виджета, сохраните настройки.

3. Нажмите на название добавленного виджета. Откроется [конструктор виджета](user_widgets.md#custom-widget-constructor) в дизайнере интерфейсов.

### Шаг 2. Создать свойства виджета «Калькулятор»

В конструкторе виджета перейдите на вкладку **Контекст** и задайте свойства:

- **Операция** (`operations`) — с типом **Категория **и опцией **Один**. Используется на форме настроек виджета для выбора операции. Хранит список операций, полученных с сервера с помощью скрипта. Запрос на сервер выполняется после размещения виджета на странице, когда открывается окно настроек вижета. В настройках свойства оставьте пустым поле **Значения**, т. к. список операций не известен на этапе создания виджета **Калькулятор**;

- **Динамический контекст** (`dynamicContext`) — служебное свойство с типом [Произвольный тип](360009707032.md#any). Позволяет получить с помощью скрипта список параметров и их типов в формате **.json** для значений, которые будут доступны для выбора в поле **Операция**.

Сохраните виджет.

### Шаг 3. Создать форму настроек виджета «Калькулятор»

На этом этапе задайте [форму настроек](user-widgets-context.md#settings) виджета** Калькулятор**, которая откроется в дизайнере интерфейсов после добавления виджета на страницу. На этой форме администратор системы выберет бухгалтерскую операцию из списка и сопоставит её параметры с контекстом страницы.

1. В конструкторе виджета на вкладке **Контекст** в правом верхнем углу нажмите **Создать форму**. Откроется конструктор формы настроек виджета **Калькулятор**.

**(widget-with-dynamic-context-2.png)**

2. Добавьте функцию для получения с сервера списка актуальных операций. Эта функция выполнится, когда администратор системы добавит на страницу виджет **Калькулятор** и откроются настройки виджета. Полученные значения сохранятся в свойстве **Операция** и будут доступны для выбора.  
Для этого перейдите на вкладку **Настройки > Системные функции** и в поле **Инициализация **добавьте функцию `onInit`.

**(widget-with-dynamic-context-3.png)**

Пример скрипта для получения списка бухгалтерских операций



В нашем примере будет получен заранее составленный список операций из функции `getOperations()`.

````
async function onInit(): Promise<void> \{  
    // Получаем в результате запроса список возможных операций  
    const operations = await getOperations();  
    // Заполняем значения свойства Операция полученным списком операций  
    Context.fields.operations.data.variants = operations;  
\}  
  
async function getOperations () \{  
    // Пример формата данных, полученных в результате запроса списка операций  
    return \[  
        \{code: 'calc\_cost\_vat', name: 'Рассчитать стоимость с учетом НДС'\},  
        \{code: 'vat\_from\_amount', name: 'Выделить НДС из суммы'\},  
        \{code: 'project\_cost\_estimation', name: 'Оценить стоимость проекта по часам'\},  
        \{code: 'calc\_margin', name: 'Рассчитать торговую наценку'\},  
    \];  
\}
````

3. ````
Перейдите на вкладку **Шаблон > Основные**, удалите размещённый по умолчанию виджет **Стандартная форма настроек** и добавьте свойство **Операция**. В этом поле администратор системы выберет операцию из списка, полученного с помощью функции `onInit`.

**(widget-with-dynamic-context-4.png)**

4. Перейдите к настройкам свойства **Операция** и задайте следующие параметры:  
**(widget-with-dynamic-context-5.png)**

- на вкладке **Основные** включите опцию **Обязательное поле**;

- на вкладке **События **в поле **Событие при изменении значения** задайте вызов функции `operationChanged`, которая будет запрашивать с сервера набор и типы параметров операции, выбранной в поле **Операция**. Эти данные сохранятся в свойстве **Динамический контекст**.  
В результате запроса список параметров должен иметь тип `DynamicBindingFields`. Если получены данные другого типа, преобразуйте их, чтобы сохранить в свойстве произвольного типа.  
**(widget-with-dynamic-context-6.png)**

Пример скрипта для получения списка параметров выбранной операции



В нашем примере будет получен заранее составленный набор параметров для выбранной операции из функции `getFieldsByOperation (operation: string)`.

````
async function operationChanged(): Promise<void> \{  
    // Определяем выбранную операцию  
    const currentOperation = Context.data.operations;  
    if (\!currentOperation) \{  
        return;  
    \}  
    // Запрашиваем необходимый набор данных для этой операции и записываем их   
    Context.data.dynamicContext = await   
getFieldsByOperation(currentOperation.code);  
\}  
  
// Пример формата данных, полученных в результате запроса списка полей по выбранной операции   
async function getFieldsByOperation (operation: string):   
Promise<DynamicBindingFields> \{  
    const fields: DynamicBindingFields = \{\};  
  
    switch (operation) \{  
    case 'calc\_cost\_vat':  
        fields\[\`summ\`\] = \{  
            name: \`Сумма без НДС\`,  
            type: DynamicFieldType.Float,  
            input: true, // входное поле  
        \};  
        fields\[\`vat\`\] = \{  
            name: \`Ставка НДС (%)\`,  
            type: DynamicFieldType.Float,  
            input: true, // входное поле  
        \};  
        fields\[\`result\`\] = \{  
            name: \`Результат\`,  
            type: DynamicFieldType.Float,  
            output: true, // выходное поле  
        \};  
        break;  
  
    case 'project\_cost\_estimation':  
        fields\[\`urgency\`\] = \{  
            name: \`Повышенная срочность\`,  
            type: DynamicFieldType.Boolean,  
            input: true, // входное поле  
        \};  
        fields\[\`hours\`\] = \{  
            name: \`Количество часов\`,  
            type: DynamicFieldType.Float,  
            input: true, // входное поле  
        \};  
        fields\[\`ratePerHour\`\] = \{  
            name: \`Ставка в час\`,  
            type: DynamicFieldType.Float,  
            input: true, // входное поле  
        \};  
        fields\[\`withTax\`\] = \{  
            name: \`Учитывать налог\`,  
            type: DynamicFieldType.Float,  
            input: true, // входное поле  
        \};  
        fields\[\`result\`\] = \{  
            name: \`Результат\`,  
            type: DynamicFieldType.Float,  
            output: true, // выходное поле  
        \};  
        break;  
  
    case 'vat\_from\_amount':  
        fields\[\`summ\`\] = \{  
            name: \`Сумма c НДС\`,  
            type: DynamicFieldType.Float,  
            input: true, // входное поле  
        \};  
        fields\[\`vat\`\] = \{  
            name: \`Ставка НДС (%)\`,  
            type: DynamicFieldType.Float,  
            input: true, // входное поле  
        \};  
        fields\[\`result\`\] = \{  
            name: \`Результат\`,  
            type: DynamicFieldType.Float,  
            output: true, // выходное поле  
        \};  
        break;  
  
    case 'calc\_margin':  
        fields\[\`costPrice\`\] = \{  
            name: \`Себестоимость\`,  
            type: DynamicFieldType.Float,  
            input: true, // входное поле  
        \};  
        fields\[\`profit\`\] = \{  
            name: \`Желаемая прибыль (%)\`,  
            type: DynamicFieldType.Float,  
            input: true, // входное поле  
        \};  
        fields\[\`result\`\] = \{  
            name: \`Результат\`,  
            type: DynamicFieldType.Float,  
            output: true, // выходное поле  
        \};  
        break;  
    \}  
  
    return fields;  
\}
````

5. ````
Перейдите на вкладку **Шаблон > Основные **и вынесите на форму для моделирования виджет [Динамический биндинг](dynamic-binding.md). Он позволит отобразить набор свойств, полученных с помощью функции `operationChanged` и сопоставить их с контекстом страницы, на которую будет размещён виджет.

6. Задайте настройки виджета **Динамический биндинг**:

**(widget-with-dynamic-context-7.png)**

- **Контекст\*** — заполнится автоматически;

- **Динамические поля\*** — выберите свойство **Динамический контекст** с типом **Произвольный тип**.

7. Сохраните настройки виджета **Динамический биндинг** и затем — форму настроек виджета **Калькулятор**.

**(widget-with-dynamic-context-8.png)**

8. Вернитесь в конструктор виджета **Калькулятор**, нажав значок **(widget-with-dynamic-context-9.png)** в левом верхнем углу формы настроек виджета.

### Шаг 4. Настроить внешний вид виджета «Калькулятор» и выполнение расчётов 

1. В конструкторе виджета на вкладке **Шаблон** разместите виджет [Кнопка](button_widget.md).

2. Задайте настройки виджета **Кнопка**:

**(widget-with-dynamic-context-10.png)**

- укажите надпись **Рассчитать**;

- выберите тип действия **Скрипт** и создайте функцию calc, которая выполнится, когда пользователь нажмёт кнопку** Рассчитать**.

3. Задайте скрипт функции `calc`. В этой функции:

- определите операцию, которую администратор системы выбрал в настройках виджета **Калькулятор** в поле **Операция**;

- определите значения входных параметров, введённые пользователем на странице. Эти значения запишутся в контекст страницы и через сопоставление в виджете **Динамический биндинг** сохранятся в переменной **Динамический контекст**. Из этой переменной с типом **Произвольный тип** значения будут получены с помощью шаблона `Context.data\[\`dynamicContext.\$\{ key \}\`\]`;

- выполните запрос на сервер для расчёта результата с указанием исходных данных. Результат, полученный с сервера, запишется в выходной параметр `result` переменной **Динамический контекст**:  
`Context.data\[\`dynamicContext.result\`\] = result;`

Пример скрипта для получения результата расчёта



В нашем примере расчёт результата выполняется в функции `getOperationResult(operation.code, operands)`.

````
async function calc(): Promise<void> \{  
    // Получаем текущую выбранную операцию  
    const operation = Context.data.operations;  
    if (\!operation) \{  
        return;  
    \}  
    // Извлекаем из динамического контекста значения входных параметров операции  
    const operands: \{\[key: string\]: any\} = \{\};  
    for (const key of Object.keys(Context.data.dynamicContext)) \{  
          
        if (key \!== 'result') \{  
            operands\[key\] = Context.data\[\`dynamicContext.\$\{ key \}\`\];  
        \}  
    \}  
  
    console.log(\`operands:\`, operands);  
  
    // Выполняем запрос на сервер для вычисления и получения результата операции  
    const result = await getOperationResult(operation.code, operands);  
    // Записываем результат выполнения в динамическое свойство result  
    Context.data\[\`dynamicContext.result\`\] = result;  
\}  
  
// Пример функции для вычисления результата операции  
async function getOperationResult (operation: string, operands: \{\[key: string\]: any\}) \{  
    switch (operation) \{  
    case 'calc\_cost\_vat':  
        const summ1 = <number> operands\['summ'\];  
        const vat1 = <number> operands\['vat'\];  
        return summ1 \* (1 + vat1/100);  
  
    case 'project\_cost\_estimation':  
        const urgency = <boolean> operands\['urgency'\];  
        const hours = <number> operands\['hours'\];  
        const ratePerHour = <number> operands\['ratePerHour'\];  
        const withTax = <number> operands\['withTax'\];  
        return hours \* ratePerHour \* (withTax ? withTax : 1) \* (urgency ? 2 : 1);  
  
    case 'vat\_from\_amount':  
        const summ2 = <number> operands\['summ'\];  
        const vat = <number> operands\['vat'\];  
        const vatC = summ2 \* (vat / (100 + vat))  
        return summ2 - vatC;  
    case 'calc\_margin':  
        const costPrice = <number> operands\['costPrice'\];  
        const profit = <number> operands\['profit'\];  
        return costPrice / (1 - profit/100)  
    \}  
\}
````

4. ````
Сохраните и опубликуйте виджет **Калькулятор**.

Теперь виджет готов к размещению на странице, где будет доступен пользователям.

## Настроить работу виджета «Калькулятор» для пользователей

Выполните следующий порядок настроек на странице, где будет использоваться виджет **Калькулятор**:

1. В конструкторе страницы на вкладке **Контекст** создайте свойства, в которых сохранятся:

- значения входных параметров операции, введённые пользователем на странице;

- значение выходного параметра, полученное с сервера с помощью скрипта.

**(widget-with-dynamic-context-11.png)**

2. Перейдите на вкладку **Шаблон**, разместите созданные свойства и добавьте готовый виджет **Калькулятор**.

**(widget-with-dynamic-context-12.png)**

При этом выполнится скрипт инициализации формы настроек виджета. Список актуальных бухгалтерских операций загрузится с сервера и сохранится в переменной **Операция** с типом **Категория**. Откроется окно настроек виджета.

3. Выберите операцию, которую пользователи будут выполнять с помощью виджета.

**(widget-with-dynamic-context-13.png)**

После этого снова выполнится скрипт, и параметры выбранной операции загрузятся с сервера. Они отобразятся в настройках виджета **Калькулятор** с помощью виджета **Динамический биндинг**.

4. Сопоставьте параметры выбранной операции с контекстом страницы в виджете **Динамический биндинг**. Сохраните настройки виджета **Калькулятор**.

**(widget-with-dynamic-context-14.png)**

5. Повторите указанный выше порядок шагов для настройки выполнения другой операции, ещё раз разместив виджет **Калькулятор** в шаблоне страницы. При этом в окне настроек виджета отобразится другой набор входных и выходных параметров для выбранной операции, полученных с сервера.

**(widget-with-dynamic-context-15.png)**

6. Опубликуйте изменения в конструкторе страницы.

Теперь пользователи могут использовать виджеты для выполнения бухгалтерских операций на странице.