[comment]: # aside:2

[comment]: # ({04e47dfc-fa26c138})
# Создание виджета (руководство)

[comment]: # ({/04e47dfc-fa26c138})

[comment]: # ({14b8f451-386f5763})
Это пошаговое руководство, которое показывает, как создать простой виджет информационной панели.
Вы можете скачать все файлы этого виджета в виде ZIP-архива: [lesson_gauge_chart.zip](../../../../assets/en/devel/modules/examples/lesson_gauge_chart.zip).

[comment]: # ({/14b8f451-386f5763})

[comment]: # ({096b9349-dead8e1e})
## Что вы создадите

В ходе этого руководства вы сначала создадите [базовый](#part-i--hello-world) виджет "Hello, world!", а затем преобразуете его в [более продвинутый](#part-ii--gauge-chart) виджет, который отображает значение элемента данных в виде диаграммы-датчика.
Вот как будет выглядеть готовый виджет:

![](../../../../assets/en/devel/modules/tutorials/widget/widget_view_finished.png){width="600"}

[comment]: # ({/096b9349-dead8e1e})

[comment]: # ({156649db-03fc601d})
## Часть I - "Hello, world!" 

В этом разделе вы узнаете, как создать минимально необходимые элементы виджета и добавить новый виджет в веб-интерфейс Zabbix.

[comment]: # ({/156649db-03fc601d})

[comment]: # ({33e23fc9-ac51379a})
### Добавление пустого виджета во веб-интерфейс Zabbix

1. Создайте каталог *lesson_gauge_chart* в каталоге *modules* вашей установки веб-интерфейса Zabbix (например, *zabbix/ui/modules*).

::: noteclassic
Все пользовательские виджеты рассматриваются как внешние модули и должны быть добавлены в каталог *modules* вашей установки веб-интерфейса Zabbix (например, *zabbix/ui/modules*).
Каталог *zabbix/ui/widgets* зарезервирован для встроенных виджетов Zabbix и обновляется вместе с веб-интерфейсом Zabbix.
:::

2. Создайте файл *manifest.json* с базовыми метаданными виджета (см. описание поддерживаемых [параметров](../file_structure/manifest)).

**ui/modules/lesson_gauge_chart/manifest.json**

```json
{
    "manifest_version": 2.0,
    "id": "lesson_gauge_chart",
    "type": "widget",
    "name": "Gauge chart",
    "namespace": "LessonGaugeChart",
    "version": "1.1",
    "author": "Zabbix"
}
```

3. В веб-интерфейсе Zabbix перейдите в раздел *Administration → General → Modules* и нажмите кнопку *Scan directory*.

![](../../../../assets/en/devel/modules/tutorials/widget/scan_dir.png)

4. Найдите в списке новый модуль *Gauge chart* и нажмите на гиперссылку "Disabled", чтобы изменить статус модуля с "Disabled" на "Enabled" (если модуль не отображается в списке, см. раздел [troubleshooting](/manual/extensions/frontendmodules#installation)).

![](../../../../assets/en/devel/modules/tutorials/widget/widget_register.png){width="600"}

5. Откройте дашборд, переключите его в режим редактирования и добавьте новый виджет.
В поле *Type* выберите "Gauge chart".

![](../../../../assets/en/devel/modules/tutorials/widget/widget_add.png){width="600"}

6. На этом этапе конфигурация виджета *Gauge chart* содержит только общие поля виджета *Name* и *Refresh interval*.
Нажмите *Add*, чтобы добавить виджет на дашборд.

![](../../../../assets/en/devel/modules/tutorials/widget/widget_conf_b.png){width="600"}

7. На дашборде должен появиться пустой виджет.
Нажмите *Save changes* в правом верхнем углу, чтобы сохранить дашборд.

![](../../../../assets/en/devel/modules/tutorials/widget/widget_blank_b.png){width="600"}

[comment]: # ({/33e23fc9-ac51379a})

[comment]: # ({6ca1b1e7-a953cd01})
### Добавление представления виджета

:::noteclassic
Файл **view** виджета должен находиться в каталоге *views* (для этого руководства — *ui/modules/lesson_gauge_chart/views/*).
Если файл имеет имя по умолчанию *widget.view.php*, регистрировать его в файле *manifest.json* не нужно.
Если файл имеет другое имя, укажите его в разделе *actions/widget.lesson_gauge_chart.view* файла [manifest.json](/devel/modules/file_structure/manifest).
:::

1. Создайте каталог *views* в каталоге *lesson_gauge_chart*.

2. Создайте файл *widget.view.php* в каталоге *views*.

**ui/modules/lesson_gauge_chart/views/widget.view.php**

```php
<?php

/**
 * Представление виджета Gauge chart.
 *
 * @var CView $this
 * @var array $data
 */

(new CWidgetView($data))
    ->addItem(
        new CTag('h1', true, 'Hello, world!')
    )
    ->show();
```

3. Обновите панель мониторинга.
Теперь виджет *Gauge chart* отображает "Hello, world!".

![](../../../../assets/en/devel/modules/tutorials/widget/widget_hello_world.png){width="600"}

[comment]: # ({/6ca1b1e7-a953cd01})

[comment]: # ({f5b50cf9-b89998cf})
## Часть II - Диаграмма измерений

[comment]: # ({/f5b50cf9-b89998cf})

[comment]: # ({d1da3c35-b23d5e39})
### Добавление настроек в представление конфигурации и использование их в представлении виджета

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

Конфигурация виджета состоит из формы (*Zabbix\\Widgets\\CWidgetForm*) и представления формы виджета (*widget.edit.php*).
Чтобы добавить поля (*Zabbix\\Widgets\\CWidgetField*), нужно создать класс *WidgetForm*, который будет расширять *Zabbix\\Widgets\\CWidgetForm*.

Форма содержит набор полей (*Zabbix\\Widgets\\CWidgetField*) различных типов, которые используются для проверки значений, введенных пользователем.
Поле формы (*Zabbix\\Widgets\\CWidgetField*) для каждого типа элемента ввода преобразует значение в единый формат для хранения в базе данных.

:::noteclassic
Файл **form** виджета должен находиться в каталоге *includes* (для этого руководства — *ui/modules/lesson_gauge_chart/includes/*).
Если файл имеет имя по умолчанию *WidgetForm.php*, регистрировать его в файле *manifest.json* не нужно.
Если файл имеет другое имя, укажите его в разделе *widget/form_class* файла [manifest.json](/devel/modules/file_structure/manifest).
:::

1. Создайте новый каталог *includes* в каталоге *lesson_gauge_chart*.

2. Создайте файл *WidgetForm.php* в каталоге *includes*.

**ui/modules/lesson_gauge_chart/includes/WidgetForm.php**

```php
<?php

namespace Modules\LessonGaugeChart\Includes;

use Zabbix\Widgets\CWidgetForm;

class WidgetForm extends CWidgetForm {
}
```

3. Добавьте поле *Description* в форму конфигурации виджета.
Это обычное текстовое поле, в которое пользователь может ввести любой набор символов.
Для него можно использовать класс *CWidgetFieldTextBox*.

**ui/modules/lesson_gauge_chart/includes/WidgetForm.php**

```php
<?php

namespace Modules\LessonGaugeChart\Includes;

use Zabbix\Widgets\CWidgetForm;

use Zabbix\Widgets\Fields\CWidgetFieldTextBox;

class WidgetForm extends CWidgetForm {

    public function addFields(): self {
        return $this
            ->addField(
               new CWidgetFieldTextBox('description', _('Description'))
            );
   }
}
```

4. В каталоге *views* создайте файл представления конфигурации виджета *widget.edit.php* и добавьте представление для нового поля *Description*.
Для класса поля *CWidgetFieldTextBox* представление — *CWidgetFieldTextBoxView*.

**ui/modules/lesson_gauge_chart/views/widget.edit.php**

```php
<?php

/**
 * Представление формы виджета Gauge chart.
 *
 * @var CView $this
 * @var array $data
 */

(new CWidgetFormView($data))
    ->addField(
        new CWidgetFieldTextBoxView($data['fields']['description'])
    )
    ->show();
```

5. Перейдите на панель и нажмите на значок шестеренки в виджете, чтобы открыть форму конфигурации виджета.

6. Теперь форма конфигурации виджета содержит новое текстовое поле *Description*.
Введите любое значение, например *Gauge chart description*.

![](../../../../assets/en/devel/modules/tutorials/widget/widget_conf_descr.png){width="600"}

7. Нажмите *Apply* в форме конфигурации виджета.
Затем нажмите *Save changes* в правом верхнем углу, чтобы сохранить панель.
Обратите внимание, что новое описание нигде не отображается, и виджет по-прежнему показывает "Hello, world!".

Чтобы новое описание появилось в виджете, значение поля *Description* нужно получить из базы данных и передать в представление виджета.
Для этого необходимо создать класс действия.

8. Создайте новый каталог *actions* в каталоге *lesson_gauge_chart*.

9. Создайте файл *WidgetView.php* в каталоге *actions*.
Класс действия *WidgetView* будет расширять класс *CControllerDashboardWidgetView*.

Значения полей конфигурации виджета хранятся в свойстве **$fields_values** класса действия.

**ui/modules/lesson_gauge_chart/actions/WidgetView.php**

```php
<?php

namespace Modules\LessonGaugeChart\Actions;

use CControllerDashboardWidgetView,
    CControllerResponseData;

class WidgetView extends CControllerDashboardWidgetView {

    protected function doAction(): void {
        $this->setResponse(new CControllerResponseData([
            'name' => $this->getInput('name', $this->widget->getName()),
            'description' => $this->fields_values['description'],
            'user' => [
                'debug_mode' => $this->getDebugMode()
            ]
        ]));
    }
}
```

10. Откройте *manifest.json* и зарегистрируйте *WidgetView* как класс действия в разделе *actions/widget.lesson_gauge_chart.view*.

**ui/modules/lesson_gauge_chart/manifest.json**

```json
{
    "manifest_version": 2.0,
    "id": "lesson_gauge_chart",
    "type": "widget",
    "name": "Gauge chart",
    "namespace": "LessonGaugeChart",
    "version": "1.0",
    "author": "Zabbix",
    "actions": {
        "widget.lesson_gauge_chart.view": {
            "class": "WidgetView"
        }
    }
}
```

11. Теперь вы можете использовать значение поля описания, содержащееся в *$data['description']*, в представлении виджета.
Откройте *views/widget.view.php* и замените статический текст "Hello, world!" на *$data['description']*.

**ui/modules/lesson_gauge_chart/views/widget.view.php**

```php
<?php

/**
 * Представление виджета Gauge chart.
 *
 * @var CView $this
 * @var array $data
 */

(new CWidgetView($data))
    ->addItem(
        new CTag('h1', true, $data['description'])
    )
    ->show();
```

12. Обновите страницу панели.
Теперь вместо "Hello, world!" вы должны увидеть текст описания виджета.

![](../../../../assets/en/devel/modules/tutorials/widget/widget_view_descr.png){width="600"}

[comment]: # ({/d1da3c35-b23d5e39})

[comment]: # ({c52f5522-3f0e28b2})
### Получение значения элемента данных через API

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

В этом разделе вы узнаете, как добавить поле выбора элемента данных в форму виджета и как добавить визуальную часть этого поля в представление конфигурации.
Затем контроллер виджета сможет получать данные элемента данных и его значение через запрос API.
После получения значение можно отобразить в представлении виджета.

1. Откройте *includes/WidgetForm.php* и добавьте поле *CWidgetFieldMultiSelectItem*.
Это позволит выбирать элемент данных в форме конфигурации.

**ui/modules/lesson_gauge_chart/includes/WidgetForm.php**

```php
<?php

namespace Modules\LessonGaugeChart\Includes;

use Zabbix\Widgets\{
    CWidgetField,
    CWidgetForm
};

use Zabbix\Widgets\Fields\{
    CWidgetFieldMultiSelectItem,
    CWidgetFieldTextBox
};

/**
 * Форма виджета диаграммы-датчика.
 */
class WidgetForm extends CWidgetForm {
    
    public function addFields(): self {
        return $this
            ->addField(
                (new CWidgetFieldMultiSelectItem('itemid', _('Item')))
                    ->setFlags(CWidgetField::FLAG_NOT_EMPTY | CWidgetField::FLAG_LABEL_ASTERISK)
                    ->setMultiple(false)
            )
            ->addField(
                new CWidgetFieldTextBox('description', _('Description'))
            );
    }
}
```

2. Откройте *views/widget.edit.php* и добавьте визуальный компонент поля в представление конфигурации.

**ui/modules/lesson_gauge_chart/views/widget.edit.php**

```php
<?php

/**
 * Представление формы виджета диаграммы-датчика.
 *
 * @var CView $this
 * @var array $data
 */

(new CWidgetFormView($data))
    ->addField(
        new CWidgetFieldMultiSelectItemView($data['fields']['itemid'])
    )
    ->addField(
        new CWidgetFieldTextBoxView($data['fields']['description'])
    )
    ->show();
```

3. Вернитесь на панель и нажмите значок шестеренки в виджете, чтобы открыть форму конфигурации виджета.

4. Теперь форма конфигурации виджета содержит новое поле ввода *Item*.
Выберите узел сети "Zabbix server" и элемент данных "Load average (1m avg)".

![](../../../../assets/en/devel/modules/tutorials/widget/widget_conf_item.png){width=600}

5. Нажмите *Apply* в форме конфигурации виджета.
Затем нажмите *Save changes* в правом верхнем углу, чтобы сохранить панель.

6. Откройте и измените *actions/WidgetView.php*.

Теперь идентификатор элемента данных будет доступен в контроллере виджета в *$this->fields\_values\['itemid'\]*.
Метод контроллера *doAction()* получает данные элемента данных (имя, тип значения, единицы измерения) с помощью метода API *[item.get](/manual/api/reference/item/get)* и последнее значение элемента данных с помощью метода API *[history.get](/manual/api/reference/history/get)*.

**ui/modules/lesson_gauge_chart/actions/WidgetView.php**

```php
<?php

namespace Modules\LessonGaugeChart\Actions;

use API,
    CControllerDashboardWidgetView,
    CControllerResponseData;

class WidgetView extends CControllerDashboardWidgetView {

    protected function doAction(): void {
        $db_items = API::Item()->get([
            'output' => ['itemid', 'value_type', 'name', 'units'],
            'itemids' => $this->fields_values['itemid'],
            'webitems' => true,
            'filter' => [
                'value_type' => [ITEM_VALUE_TYPE_UINT64, ITEM_VALUE_TYPE_FLOAT]
            ]
        ]);

        $value = null;

        if ($db_items) {
            $item = $db_items[0];

            $history = API::History()->get([
                'output' => API_OUTPUT_EXTEND,
                'itemids' => $item['itemid'],
                'history' => $item['value_type'],
                'sortfield' => 'clock',
                'sortorder' => ZBX_SORT_DOWN,
                'limit' => 1
            ]);

            if ($history) {
                $value = convertUnitsRaw([
                    'value' => $history[0]['value'],
                    'units' => $item['units']
                ]);
            }
        }

        $this->setResponse(new CControllerResponseData([
            'name' => $this->getInput('name', $this->widget->getName()),
            'value' => $value,
            'description' => $this->fields_values['description'],
            'user' => [
                'debug_mode' => $this->getDebugMode()
            ]
        ]));
    }
}
```

7. Откройте *views/widget.view.php* и добавьте значение элемента данных в представление виджета.

**ui/modules/lesson_gauge_chart/views/widget.view.php**

```php
<?php

/**
 * Представление виджета диаграммы-датчика.
 *
 * @var CView $this
 * @var array $data
 */

(new CWidgetView($data))
    ->addItem([
        new CTag('h1', true, $data['description']),
        new CDiv($data['value'] !== null ? $data['value']['value'] : _('No data'))
    ])
    ->show();
```

8. Обновите страницу панели.
Виджет будет отображать последнее значение элемента данных.

![](../../../../assets/en/devel/modules/tutorials/widget/widget_view_item.png){width="600"}

[comment]: # ({/c52f5522-3f0e28b2})

[comment]: # ({aa1b47ba-8f001ece})
### Добавление расширенных настроек конфигурации в представление конфигурации

В этом разделе вы узнаете, как добавить разворачиваемый/сворачиваемый раздел *Расширенная конфигурация* с дополнительными параметрами, такими как цвет, минимальное и максимальное значения, единицы измерения и поле *Описание*, созданное ранее.

1. Создайте файл *Widget.php* в основном каталоге виджетов *lesson_gauge_chart*, чтобы создать новый класс *Widget*.

Класс *Widget* расширит базовый класс *CWidget*, чтобы добавить/переопределить настройки виджета по умолчанию (в данном случае — переводы).
JavaScript, представленный ниже, отображает строку "Нет данных" в случае отсутствия данных.
Строка "Нет данных" присутствует в файлах перевода Zabbix UI.

Если есть какие-либо константы виджета, рекомендуется также указать их в классе *Widget*.

**ui/modules/lesson_gauge_chart/Widget.php**

```php
<?php

namespace Modules\LessonGaugeChart;

use Zabbix\Core\CWidget;

class Widget extends CWidget {

    public const UNIT_AUTO = 0;
    public const UNIT_STATIC = 1;

    public function getTranslationStrings(): array {
        return [
            'class.widget.js' => [
                'No data' => _('No data')
            ]
        ];
    }
}
```

2. Откройте *includes/WidgetForm.php* и добавьте новые поля *Color* (выбор цвета), *Min* (числовое поле), *Max* (числовое поле) и *Units* (выберите) и определите цветовая палитра по умолчанию для палитры цветов, чтобы ее можно было использовать на следующих шагах.

**ui/modules/lesson_gauge_chart/includes/WidgetForm.php**

```php
<?php

namespace Modules\LessonGaugeChart\Includes;

use Modules\LessonGaugeChart\Widget;

use Zabbix\Widgets\{
    CWidgetField,
    CWidgetForm
};

use Zabbix\Widgets\Fields\{
    CWidgetFieldColor,
    CWidgetFieldMultiSelectItem,
    CWidgetFieldNumericBox,
    CWidgetFieldSelect,
    CWidgetFieldTextBox
};

/**
 * Gauge chart widget form.
 */
class WidgetForm extends CWidgetForm {

    public const DEFAULT_COLOR_PALETTE = [
        'FF465C', 'B0AF07', '0EC9AC', '524BBC', 'ED1248', 'D1E754', '2AB5FF', '385CC7', 'EC1594', 'BAE37D',
        '6AC8FF', 'EE2B29', '3CA20D', '6F4BBC', '00A1FF', 'F3601B', '1CAE59', '45CFDB', '894BBC', '6D6D6D'
    ];

    public function addFields(): self {
        return $this
            ->addField(
                (new CWidgetFieldMultiSelectItem('itemid', _('Item')))
                    ->setFlags(CWidgetField::FLAG_NOT_EMPTY | CWidgetField::FLAG_LABEL_ASTERISK)
                    ->setMultiple(false)
            )
            ->addField(
                (new CWidgetFieldColor('chart_color', _('Color')))->setDefault('FF0000')
            )
            ->addField(
                (new CWidgetFieldNumericBox('value_min', _('Min')))
                    ->setDefault(0)
                    ->setFlags(CWidgetField::FLAG_NOT_EMPTY | CWidgetField::FLAG_LABEL_ASTERISK)
            )
            ->addField(
                (new CWidgetFieldNumericBox('value_max', _('Max')))
                    ->setDefault(100)
                    ->setFlags(CWidgetField::FLAG_NOT_EMPTY | CWidgetField::FLAG_LABEL_ASTERISK)
            )
            ->addField(
                (new CWidgetFieldSelect('value_units', _('Units'), [
                    Widget::UNIT_AUTO => _x('Auto', 'history source selection method'),
                    Widget::UNIT_STATIC => _x('Static', 'history source selection method')
                ]))->setDefault(Widget::UNIT_AUTO)
            )
            ->addField(
                (new CWidgetFieldTextBox('value_static_units'))
            )
            ->addField(
                new CWidgetFieldTextBox('description', _('Description'))
            );
    }
}
```

3. Откройте *views/widget.edit.php* и добавьте визуальные компоненты поля в представление конфигурации.

**ui/modules/lesson_gauge_chart/views/widget.edit.php**

```php
<?php

/**
 * Gauge chart widget form view.
 *
 * @var CView $this
 * @var array $data
 */

$lefty_units = new CWidgetFieldSelectView($data['fields']['value_units']);
$lefty_static_units = (new CWidgetFieldTextBoxView($data['fields']['value_static_units']))
    ->setPlaceholder(_('value'))
    ->setWidth(ZBX_TEXTAREA_TINY_WIDTH);

(new CWidgetFormView($data))
    ->addField(
        (new CWidgetFieldMultiSelectItemView($data['fields']['itemid']))
            ->setPopupParameter('numeric', true)
    )
    ->addFieldset(
        (new CWidgetFormFieldsetCollapsibleView(_('Advanced configuration')))
            ->addField(
                new CWidgetFieldColorView($data['fields']['chart_color'])
            )
            ->addField(
                new CWidgetFieldNumericBoxView($data['fields']['value_min'])
            )
            ->addField(
                new CWidgetFieldNumericBoxView($data['fields']['value_max'])
            )
            ->addItem([
                $lefty_units->getLabel(),
                (new CFormField([
                    $lefty_units->getView()->addClass(ZBX_STYLE_FORM_INPUT_MARGIN),
                    $lefty_static_units->getView()
                ]))
            ])
            ->addField(
                new CWidgetFieldTextBoxView($data['fields']['description'])
            )
    )
    ->show();
```

::: noteclassic
Метод *addField()* класса *CWidgetFormView* принимает строку класса CSS в качестве второго параметра.
:::

4. Вернитесь на панель, переключитесь в режим редактирования и нажмите на значок шестеренки в виджете, чтобы открыть форму настройки виджета.
Форма настройки виджета теперь содержит новый расширяемый/сворачиваемый раздел *Расширенная конфигурация*.

![](../../../../assets/en/devel/modules/tutorials/widget/widget_conf_advanced.png){width="600"}

5. Разверните раздел *Расширенная конфигурация*, чтобы увидеть дополнительные поля конфигурации виджета.
Обратите внимание, что поле *Цвет* пока не имеет палитры цветов.
Это связано с тем, что палитра цветов должна быть инициализирована с помощью JavaScript, который будет добавлен в следующем разделе — [*Добавьте JavaScript в виджет*](#add-javascript-to-the-widget).

![](../../../../assets/en/devel/modules/tutorials/widget/widget_conf_advanced_a.png){width="600"}

[comment]: # ({/aa1b47ba-8f001ece})

[comment]: # ({175c9f96-c573cc71})
### Добавление JavaScript в виджет

В этом разделе вы узнаете, как добавить диаграмму-датчик, созданную с помощью JavaScript, которая показывает, является ли последнее значение нормальным или слишком высоким/слишком низким.

1. Создайте файл *widget.edit.js.php* в каталоге *views*.

JavaScript будет отвечать за инициализацию палитры цветов в представлении конфигурации.

**ui/modules/lesson_gauge_chart/views/widget.edit.js.php**

```php
<?php

use Modules\LessonGaugeChart\Widget;

?>

window.widget_lesson_gauge_chart_form = new class {

    init({color_palette}) {
        this._unit_select = document.getElementById('value_units');
        this._unit_value = document.getElementById('value_static_units');

        this._unit_select.addEventListener('change', () => this.updateForm());

        colorPalette.setThemeColors(color_palette);

        for (const colorpicker of jQuery('.<?= ZBX_STYLE_COLOR_PICKER ?> input')) {
            jQuery(colorpicker).colorpicker();
        }

        const overlay = overlays_stack.getById('widget_properties');

        for (const event of ['overlay.reload', 'overlay.close']) {
            overlay.$dialogue[0].addEventListener(event, () => { jQuery.colorpicker('hide'); });
        }

        this.updateForm();
    }

    updateForm() {
        this._unit_value.disabled = this._unit_select.value == <?= Widget::UNIT_AUTO ?>;
    }
};
```

2. Откройте *views/widget.edit.php* и добавьте файл *widget.edit.js.php* с JavaScript в представление конфигурации.
Для этого используйте метод *includeJsFile()*.
Чтобы добавить встроенный JavaScript, используйте метод *addJavaScript()*.

**ui/modules/lesson_gauge_chart/views/widget.edit.php**

```php
<?php

/**
 * Gauge chart widget form view.
 *
 * @var CView $this
 * @var array $data
 */

use Modules\LessonGaugeChart\Includes\WidgetForm;

$lefty_units = new CWidgetFieldSelectView($data['fields']['value_units']);
$lefty_static_units = (new CWidgetFieldTextBoxView($data['fields']['value_static_units']))
    ->setPlaceholder(_('value'))
    ->setWidth(ZBX_TEXTAREA_TINY_WIDTH);

(new CWidgetFormView($data))
    ->addField(
        (new CWidgetFieldMultiSelectItemView($data['fields']['itemid']))
            ->setPopupParameter('numeric', true)
    )
    ->addFieldset(
        (new CWidgetFormFieldsetCollapsibleView(_('Advanced configuration')))
            ->addField(
                new CWidgetFieldColorView($data['fields']['chart_color'])
            )
            ->addField(
                new CWidgetFieldNumericBoxView($data['fields']['value_min'])
            )
            ->addField(
                new CWidgetFieldNumericBoxView($data['fields']['value_max'])
            )
            ->addItem([
                $lefty_units->getLabel(),
                (new CFormField([
                    $lefty_units->getView()->addClass(ZBX_STYLE_FORM_INPUT_MARGIN),
                    $lefty_static_units->getView()
                ]))
            ])
            ->addField(
                new CWidgetFieldTextBoxView($data['fields']['description'])
            )
    )
    ->includeJsFile('widget.edit.js.php')
    ->addJavaScript('widget_lesson_gauge_chart_form.init('.json_encode([
        'color_palette' => WidgetForm::DEFAULT_COLOR_PALETTE
    ], JSON_THROW_ON_ERROR).');')
    ->show();
```

3. Вернитесь на панель, нажмите на значок шестеренки в виджете, чтобы открыть форму конфигурации виджета.
Теперь разверните раздел *Advanced configuration*, чтобы увидеть инициализированную палитру цветов.
Заполните поля значениями и выберите цвет для диаграммы-датчика.

![](../../../../assets/en/devel/modules/tutorials/widget/widget_conf_advanced_b.png){width="600"}

4. Нажмите *Apply* в форме конфигурации виджета.
Затем нажмите *Save changes* в правом верхнем углу, чтобы сохранить панель.

5. Откройте *actions/WidgetView.php* и обновите контроллер.

Свойство **$this->fields_values** теперь содержит значения всех полей *Advanced configuration*.
Завершите настройку контроллера, чтобы передавать конфигурацию и выбранное значение элемента данных в представление виджета.

**ui/modules/lesson_gauge_chart/actions/WidgetView.php**

```php
<?php

namespace Modules\LessonGaugeChart\Actions;

use API,
    CControllerDashboardWidgetView,
    CControllerResponseData;

class WidgetView extends CControllerDashboardWidgetView {

    protected function doAction(): void {
        $db_items = API::Item()->get([
            'output' => ['itemid', 'value_type', 'name', 'units'],
            'itemids' => $this->fields_values['itemid'],
            'webitems' => true,
            'filter' => [
                'value_type' => [ITEM_VALUE_TYPE_UINT64, ITEM_VALUE_TYPE_FLOAT]
            ]
        ]);

        $history_value = null;

        if ($db_items) {
            $item = $db_items[0];

            $history = API::History()->get([
                'output' => API_OUTPUT_EXTEND,
                'itemids' => $item['itemid'],
                'history' => $item['value_type'],
                'sortfield' => 'clock',
                'sortorder' => ZBX_SORT_DOWN,
                'limit' => 1
            ]);

            if ($history) {
                $history_value = convertUnitsRaw([
                    'value' => $history[0]['value'],
                    'units' => $item['units']
                ]);
            }
        }

        $this->setResponse(new CControllerResponseData([
            'name' => $this->getInput('name', $this->widget->getName()),
            'history' => $history_value,
            'fields_values' => $this->fields_values,
            'user' => [
                'debug_mode' => $this->getDebugMode()
            ]
        ]));
    }
}
```

6. Откройте и измените *views/widget.view.php*.

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

Чтобы передать значения в JavaScript как объект JSON, используйте метод *setVar()*.

**ui/modules/lesson_gauge_chart/views/widget.view.php**

```php
<?php

/**
 * Gauge chart widget view.
 *
 * @var CView $this
 * @var array $data
 */

(new CWidgetView($data))
    ->addItem([
        (new CDiv())->addClass('chart'),
        $data['fields_values']['description']
            ? (new CDiv($data['fields_values']['description']))->addClass('description')
            : null
    ])
    ->setVar('history', $data['history'])
    ->setVar('fields_values', $data['fields_values'])
    ->show();
```

7. Создайте новый каталог *assets* в каталоге *lesson_gauge_chart*.
Этот каталог будет использоваться для хранения JavaScript, CSS и, возможно, других ресурсов, таких как шрифты или изображения.

8. Для JavaScript представления виджета создайте каталог *js* в каталоге *assets*.

9. Создайте файл *class.widget.js* в каталоге *assets/js*.

Этот JavaScript-класс виджета будет расширять базовый JavaScript-класс всех виджетов панели - *CWidget*.

Панель зависит от корректной реализации виджета и передает всю необходимую информацию в виджет через вызов соответствующих методов JavaScript.
Панель также ожидает, что виджет будет генерировать события при возникновении определенного взаимодействия.
Поэтому класс *CWidget* содержит набор методов с реализацией поведения виджета по умолчанию, которое можно настроить путем расширения класса.

В данном случае требуется некоторая настройка, поэтому собственная логика будет реализована для следующего поведения виджета:

- инициализация виджета, которая отвечает за определение начального состояния виджета (см. метод *onInitialize()*);
- отображение содержимого виджета (то есть отрисовка диаграммы-датчика), если процесс обновления виджета завершился успешно и без ошибок (см. метод *processUpdateResponse(response)* и связанные с ним методы *\_resizeChart()* и *\_updatedChart()*);
- изменение размера виджета (см. метод *onResize()* и связанный с ним метод *\_resizeChart()*)

Для других аспектов виджета диаграммы-датчика будет использоваться реализация поведения виджета по умолчанию.
Подробнее о методах JavaScript класса *CWidget* см.: [JavaScript](/devel/modules/widgets/presentation/javascript).

Поскольку этот JavaScript требуется для представления виджета, он должен загружаться вместе со страницей панели.
Чтобы включить загрузку JavaScript, необходимо обновить параметры *assets/js* и *js_class* в файле ***manifest.json***, как показано в шаге 10.

**ui/modules/lesson_gauge_chart/assets/js/class.widget.js**

```js
class WidgetLessonGaugeChart extends CWidget {

    static UNIT_AUTO = 0;
    static UNIT_STATIC = 1;

    onInitialize() {
        super.onInitialize();

        this._refresh_frame = null;
        this._chart_container = null;
        this._canvas = null;
        this._chart_color = null;
        this._min = null;
        this._max = null;
        this._value = null;
        this._last_value = null;
        this._units = '';
    }

    processUpdateResponse(response) {
        if (response.history === null) {
            this._value = null;
            this._units = '';
        }
        else {
            this._value = Number(response.history.value);
            this._units = response.fields_values.value_units == WidgetLessonGaugeChart.UNIT_AUTO
                ? response.history.units
                : response.fields_values.value_static_units;
        }

        this._chart_color = response.fields_values.chart_color;
        this._min = Number(response.fields_values.value_min);
        this._max = Number(response.fields_values.value_max);

        super.processUpdateResponse(response);
    }

    setContents(response) {
        if (this._canvas === null) {
            super.setContents(response);

            this._chart_container = this._body.querySelector('.chart');
            this._chart_container.style.height =
                `${this._getContentsSize().height - this._body.querySelector('.description').clientHeight}px`;
            this._canvas = document.createElement('canvas');

            this._chart_container.appendChild(this._canvas);

            this._resizeChart();
        }

        this._updatedChart();
    }

    onResize() {
        super.onResize();

        if (this._state === WIDGET_STATE_ACTIVE) {
            this._resizeChart();
        }
    }

    _resizeChart() {
        const ctx = this._canvas.getContext('2d');
        const dpr = window.devicePixelRatio;

        this._canvas.style.display = 'none';
        const size = Math.min(this._chart_container.offsetWidth, this._chart_container.offsetHeight);
        this._canvas.style.display = '';

        this._canvas.width = size * dpr;
        this._canvas.height = size * dpr;

        ctx.scale(dpr, dpr);

        this._canvas.style.width = `${size}px`;
        this._canvas.style.height = `${size}px`;

        this._refresh_frame = null;

        this._updatedChart();
    }

    _updatedChart() {
        if (this._last_value === null) {
            this._last_value = this._min;
        }

        const start_time = Date.now();
        const end_time = start_time + 400;

        const animate = () => {
            const time = Date.now();

            if (time <= end_time) {
                const progress = (time - start_time) / (end_time - start_time);
                const smooth_progress = 0.5 + Math.sin(Math.PI * (progress - 0.5)) / 2;
                let value = this._value !== null ? this._value : this._min;
                value = (this._last_value + (value - this._last_value) * smooth_progress - this._min) / (this._max - this._min);

                const ctx = this._canvas.getContext('2d');
                const size = this._canvas.width;
                const char_weight = size / 12;
                const char_shadow = 3;
                const char_x = size / 2;
                const char_y = size / 2;
                const char_radius = (size - char_weight) / 2 - char_shadow;

                const font_ratio = 32 / 100;

                ctx.clearRect(0, 0, size, size);

                ctx.beginPath();
                ctx.shadowBlur = char_shadow;
                ctx.shadowColor = '#bbb';
                ctx.strokeStyle = '#eee';
                ctx.lineWidth = char_weight;
                ctx.lineCap = 'round';
                ctx.arc(char_x, char_y, char_radius, Math.PI * 0.749, Math.PI * 2.251, false);
                ctx.stroke();

                ctx.beginPath();
                ctx.strokeStyle = `#${this._chart_color}`;
                ctx.lineWidth = char_weight - 2;
                ctx.lineCap = 'round';
                ctx.arc(char_x, char_y, char_radius, Math.PI * 0.75,
                    Math.PI * (0.75 + (1.5 * Math.min(1, Math.max(0, value)))), false
                    );
                ctx.stroke();

                ctx.shadowBlur = 2;
                ctx.fillStyle = '#1f2c33';
                ctx.font = `${(char_radius * font_ratio)|0}px Arial`;
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText(`${this._value !== null ? this._value : t('No data')}${this._units}`,
                    char_x, char_y, size - char_shadow * 4 - char_weight * 2
                );

                ctx.fillStyle = '#768d99';
                ctx.font = `${(char_radius * font_ratio * .5)|0}px Arial`;
                ctx.textBaseline = 'top';

                ctx.textAlign = 'left';
                ctx.fillText(`${this._min}${this._min != '' ? this._units : ''}`,
                    char_weight * .75, size - char_weight * 1.25, size / 2 - char_weight
                );

                ctx.textAlign = 'right';
                ctx.fillText(`${this._max}${this._max != '' ? this._units : ''}`,
                    size - char_weight * .75, size - char_weight * 1.25, size / 2 - char_weight
                );

                requestAnimationFrame(animate);
            }
            else {
                this._last_value = this._value;
            }
        };

        requestAnimationFrame(animate);
    }
}
```

10. Откройте *manifest.json* и добавьте:

- имя файла (*class.widget.js*) в массив в разделе *assets/js*;
- имя класса (*WidgetLessonGaugeChart*) в параметр *js_class* в разделе *widget*.

Теперь класс *WidgetLessonGaugeChart* будет автоматически загружаться вместе с панелью.

**ui/modules/lesson_gauge_chart/manifest.json**

```json
{
    "manifest_version": 2.0,
    "id": "lesson_gauge_chart",
    "type": "widget",
    "name": "Gauge chart",
    "namespace": "LessonGaugeChart",
    "version": "1.0",
    "author": "Zabbix",
    "actions": {
        "widget.lesson_gauge_chart.view": {
            "class": "WidgetView"
        }
    },
    "widget": {
        "js_class": "WidgetLessonGaugeChart"
    },
    "assets": {
        "js": ["class.widget.js"]
    }
}
```

[comment]: # ({/175c9f96-c573cc71})

[comment]: # ({6ea664c3-71184963})
### Добавление стилей CSS в виджет

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

1. Для стилей виджетов создайте новый каталог *css* в каталоге *assets*.

2. Создайте файл *widget.css* в каталоге *assets/css*.
Чтобы стилизовать элементы виджета, используйте селектор *div.dashboard-widget-{widget id}*.
Чтобы настроить CSS для всего виджета, используйте селектор *form.dashboard-widget-{idget id}*

**ui/modules/lesson_gauge_chart/assets/css/widget.css**

``` CSS
div.dashboard-widget-lesson_gauge_chart {
    display: grid;
    grid-template-rows: 1fr;
    padding: 0;
}

div.dashboard-widget-lesson_gauge_chart .chart {
    display: grid;
    align-items: center;
    justify-items: center;
}

div.dashboard-widget-lesson_gauge_chart .chart canvas {
    background: white;
}

div.dashboard-widget-lesson_gauge_chart .description {
    padding-bottom: 8px;
    font-size: 1.750em;
    line-height: 1.2;
    text-align: center;
}

.dashboard-grid-widget-hidden-header div.dashboard-widget-lesson_gauge_chart .chart {
    margin-top: 8px;
}
```

3. Откройте *manifest.json* и добавьте имя файла CSS (*widget.css*) в массив в разделе *assets/css*.
Это позволит стилям CSS, определенным в *widget.css*, загружаться вместе со страницей панели.

**ui/modules/lesson_gauge_chart/manifest.json**

```json
{
    "manifest_version": 2.0,
    "id": "lesson_gauge_chart",
    "type": "widget",
    "name": "Gauge chart",
    "namespace": "LessonGaugeChart",
    "version": "1.0",
    "author": "Zabbix",
    "actions": {
        "widget.lesson_gauge_chart.view": {
            "class": "WidgetView"
        }
    },
    "widget": {
        "js_class": "WidgetLessonGaugeChart"
    },
    "assets": {
        "css": ["widget.css"],
        "js": ["class.widget.js"]
    }
}
```

4. Обновите страницу панели, чтобы увидеть готовую версию виджета.

![](../../../../assets/en/devel/modules/tutorials/widget/widget_view_finished.png){width="600"}

[comment]: # ({/6ea664c3-71184963})

