﻿# Create a widget with dynamic context

> [HTML Version](widget-with-dynamic-context.html)

Let's look at an example of creating and configuring a custom **Calculator** widget, which performs a specific accounting operation selected in its settings. The set of operations, their parameters, and calculation formulas are stored on the server and can be updated and extended.

You can place several such widgets on a page and configure each to perform a different operation.

## User actions with the Calculator widget

For the user performing calculations, the **Calculator** widget looks like a button on the page. There can be several of them.

The user fills in the fields with the initial data for the accounting operation and clicks the **Calculate** button. This triggers a request from the widget script to the server to perform the calculation. The result returned by the server is displayed on the page in a separate field.

**(widget-with-dynamic-context-1.png)**

For users to be able to use the **Calculator** widget, the system administrator performs the following configuration steps:

1. Create the **Calculator** widget.

2. Place the **Calculator** widget on the page and [configure it for users](#customize-widget-on-page). 

## Create the Calculator widget

Let's look at how to create the **Calculator** widget with dynamic context. In our example, we'll use scripts that retrieve prepared lists of operations, their parameters, and formulas from additional functions instead of sending requests to the server.

Follow these configuration steps:

1. [Add the widget at the workspace level](#add-widget) where it will be used.

2. [Create properties in widget context](#create-properties).

3. [Create the widget settings form](#create-settings-form).

4. [Configure the widget's appearance and set up calculations](#customize-appearance).

### Step 1: Add the widget at the workspace level

1. In the settings of the workspace where the widget will be used, select **Interfaces** and on the page that opens, click **+ Create**.

2. Enter the **Calculator** name and the widget code, then save the settings.

3. Click the name of the added widget. This opens the [widget builder](user_widgets.md#custom-widget-constructor) in Interface Designer.

### Step 2: Create properties for the Calculator widget

In the widget builder, go to the **Context** tab and add the following properties:

- **Operation** (code: `operations`): the **Category **type with the **One **option. Used on the widget settings form to select an operation. Stores the list of operations retrieved from the server via a script. The request to the server is sent after the widget is placed on the page when the widget settings window opens. In the property settings, leave the **Values** field empty, since the list of operations is not known at the stage of creating the **Calculator** widget.

- **Dynamic context** (code: `dynamicContext`): a system property of the [Arbitrary type](360009707032.md#arbitrary-type). It allows you to use a script to retrieve a list of parameters and their types in **.json** format for the values that will be available for selection in the **Operation** field.

Save the widget.

### Step 3: Create the settings form for the Calculator widget

At this step, set the [settings form](user-widgets-context.md#settings) of the** Calculator **widget, which opens in Interface Designer after the widget is added to the page. On this form, the system administrator selects an accounting operation from the list and maps its parameters to the page context.

1. In the widget builder, on the **Context** tab, in the upper-right corner, click **Create Form**. This opens the settings form builder for the **Calculator** widget.

**(widget-with-dynamic-context-2.png)**

2. Add a function to retrieve the current list of operations from the server. This function runs when the system administrator adds the **Calculator** widget to the page and the widget settings open. The retrieved values are saved in the **Operation** property and become available for selection.  
To do this, go to the **Settings > System functions** tab and, in the **Initialization** field, add the `onInit` function.

**(widget-with-dynamic-context-3.png)**

Example script for getting the list of accounting operations

  
In our example, a prepared list of operations is retrieved from the `getOperations()` function.

````
async function onInit(): Promise<void> \{  
    // Get the list of possible operations from the request  
    const operations = await getOperations();  
    // Populate the Operation property with the retrieved list of operations  
    Context.fields.operations.data.variants = operations;  
\}  
  
async function getOperations () \{  
    // Example of the data format returned by the request for the list of operations  
    return \[  
        \{code: 'calc\_cost\_vat', name: 'Calculate the price with VAT'\},  
        \{code: 'vat\_from\_amount', name: 'Amount of VAT in the sum'\},  
        \{code: 'project\_cost\_estimation', name: 'Estimate the project cost in hours'\},  
        \{code: 'calc\_margin', name: 'Calculate the markup'\},  
    \];  
\}
````

3. ````
Go to the **Template > Main** tab, remove the default **Standard settings form** widget, and add the **Operation** property. In this field, the system administrator selects an operation from the list retrieved using the `onInit` function.

**(widget-with-dynamic-context-4.png)**

4. Go to the settings of the **Operation** property and set the following parameters:  
**(widget-with-dynamic-context-5.png)**

- On the **Main** tab, enable the **Required field** option.

- On the **Events** tab, in the **On value change handler** field, set a call to the `operationChanged` function, which requests from the server the set and types of parameters for the operation selected in the **Operation** field. This data is saved in the **Dynamic context** property.  
The list of parameters returned by the request must have the `DynamicBindingFields `type. If the retrieved data is of a different type, convert it so it can be saved in the arbitrary-type property.  
**(widget-with-dynamic-context-6.png)**

Example script for getting the list of parameters for the selected operation

  
In our example, a prepared set of parameters for the selected operation is retrieved from the `getFieldsByOperation (operation: string)` function.

````
async function operationChanged(): Promise<void> \{  
    // Determine the selected operation  
    const currentOperation = Context.data.operations;  
    if (\!currentOperation) \{  
        return;  
    \}  
    // Request the necessary data set for this operation and write it   
    Context.data.dynamicContext = await   
getFieldsByOperation(currentOperation.code);  
\}  
  
// Example of the data format returned by the request for the list of fields for the selected operation   
async function getFieldsByOperation (operation: string):   
Promise<DynamicBindingFields> \{  
    const fields: DynamicBindingFields = \{\};  
  
    switch (operation) \{  
    case 'calc\_cost\_vat':  
        fields\[\`summ\`\] = \{  
            name: \`Amount without VAT\`,  
            type: DynamicFieldType.Float,  
            input: true, // input field  
        \};  
        fields\[\`vat\`\] = \{  
            name: \`VAT (%)\`,  
            type: DynamicFieldType.Float,  
            input: true, // input field  
        \};  
        fields\[\`result\`\] = \{  
            name: \`Result\`,  
            type: DynamicFieldType.Float,  
            output: true, // output field  
        \};  
        break;  
  
    case 'project\_cost\_estimation':  
        fields\[\`urgency\`\] = \{  
            name: \`Urgency\`,  
            type: DynamicFieldType.Boolean,  
            input: true, // input field  
        \};  
        fields\[\`hours\`\] = \{  
            name: \`Hours\`,  
            type: DynamicFieldType.Float,  
            input: true, // input field  
        \};  
        fields\[\`ratePerHour\`\] = \{  
            name: \`Hourly rate\`,  
            type: DynamicFieldType.Float,  
            input: true, // input field  
        \};  
        fields\[\`withTax\`\] = \{  
            name: \`With VAT\`,  
            type: DynamicFieldType.Float,  
            input: true, // input field  
        \};  
        fields\[\`result\`\] = \{  
            name: \`Result\`,  
            type: DynamicFieldType.Float,  
            output: true, // output field  
        \};  
        break;  
  
    case 'vat\_from\_amount':  
        fields\[\`summ\`\] = \{  
            name: \`Amount with VAT\`,  
            type: DynamicFieldType.Float,  
            input: true, // input field  
        \};  
        fields\[\`vat\`\] = \{  
            name: \`VAT (%)\`,  
            type: DynamicFieldType.Float,  
            input: true, // input field  
        \};  
        fields\[\`result\`\] = \{  
            name: \`Result\`,  
            type: DynamicFieldType.Float,  
            output: true, // output field  
        \};  
        break;  
  
    case 'calc\_margin':  
        fields\[\`costPrice\`\] = \{  
            name: \`Initial price\`,  
            type: DynamicFieldType.Float,  
            input: true, // input field  
        \};  
        fields\[\`profit\`\] = \{  
            name: \`Estimated profit (%)\`,  
            type: DynamicFieldType.Float,  
            input: true, // input field  
        \};  
        fields\[\`result\`\] = \{  
            name: \`Result\`,  
            type: DynamicFieldType.Float,  
            output: true, // output field  
        \};  
        break;  
    \}  
  
    return fields;  
\}
````

5. ````
Go to the **Template > Main **tab, and add the [Dynamic binding](dynamic-binding.md) widget to the design form. It lets you display the set of properties retrieved by the `operationChanged` function, and map them to the context of the page where the widget will be placed.

6. Configure the settings of the **Dynamic binding **widget:

**(widget-with-dynamic-context-7.png)**

- **Context\***: It is populated in automatically.

- **Dynamic fields\***. Select the **Dynamic context** property of the **Arbitrary type**.

7. Save the settings of the **Dynamic binding** widget, and then the settings form of the **Calculator** widget.

**(widget-with-dynamic-context-8.png)**

8. Return to the **Calculator** widget builder by clicking the icon **(widget-with-dynamic-context-9.png)** in the upper-left corner of the widget settings form.

### Step 4: Configure the appearance of the Calculator widget and set up calculations

1. In the widget builder, on the **Template** tab, place the [Button](button_widget.md) widget.

2. Configure the settings of the **Button **widget:

**(widget-with-dynamic-context-10.png)**

- Set the title as **Calculate**.

- Select the **Script** action type and create the `calc` function, which runs when the user clicks the** Calculate **button.

3. Set the script for the `calc` function. In this function:

- Set the operation that the system administrator selected in the settings of the **Calculator** widget, in the **Operation** field.

- Set the input parameter values entered by the user on the page. These values are written to the page context and, through mapping in the **Dynamic binding** widget, saved to the **Dynamic context** variable. From the variable of the **Arbitrary type**, values are retrieved using the `Context.data\[\`dynamicContext.\$\{ key \}\`\] `template.

- Send a request to the server to calculate the result, passing the source data. The result returned by the server is written to the output parameter `result` of the **Dynamic context **variable:  
`Context.data\[\`dynamicContext.result\`\] = result;`

Example script for getting the calculation result

  
In our example, the result is calculated in the `getOperationResult(operation.code, operands)` function.

````
async function calc(): Promise<void> \{  
    // Get the currently selected operation  
    const operation = Context.data.operations;  
    if (\!operation) \{  
        return;  
    \}  
    // Extract the operation input parameter values from the dynamic context  
    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);  
  
    // Send a request to the server to calculate and get the operation result  
    const result = await getOperationResult(operation.code, operands);  
    // Write the result to the dynamic result property  
    Context.data\[\`dynamicContext.result\`\] = result;  
\}  
  
// Example function for calculating the operation 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. ````
Save and publish the **Calculator** widget.

The widget is now ready to be placed on a page, where it will be available to users.

## Configure the Calculator widget for users

On the page where the **Calculator** widget will be used, follow this configuration sequence:

1. In the page builder, on the **Context** tab, create properties to store:

- The input parameter values for the operation, entered by the user on the page.

- The output parameter value, retrieved from the server using the script.

**(widget-with-dynamic-context-11.png)**

2. Go to the **Template** tab, place the properties you created, and add the published **Calculator** widget.

**(widget-with-dynamic-context-12.png)**

This runs the initialization script for the widget settings form. The current list of accounting operations loads from the server and is saved to the **Operation** variable with the **Category **type. The widget settings window opens.

3. Select the operation that users will perform using the widget.

**(widget-with-dynamic-context-13.png)**

The script then runs again, and the parameters of the selected operation load from the server. They appear in the settings of the **Calculator** widget, shown by the **Dynamic binding** widget.

4. Map the parameters of the selected operation to the page context in the **Dynamic binding** widget. Save the settings of the **Calculator** widget.

**(widget-with-dynamic-context-14.png)**

5. Repeat the steps above to configure another operation by placing the **Calculator** widget in the page template again. The widget settings window then shows a different set of input and output parameters for the selected operation, retrieved from the server.

**(widget-with-dynamic-context-15.png)**

6. Publish the changes in the page builder.

Now users can use the widgets to perform accounting operations on the page.