Виджеты
=======

Виджеты представляют собой многоразовые строительные блоки, используемые в [представлениях](structure-views.md)
для создания сложных и настраиваемых элементов пользовательского интерфейса в рамках объектно-ориентированного
подхода. Например, виджет выбора даты (date picker) позволяет генерировать интерактивный интерфейс для выбора дат,
предоставляя пользователям приложения удобный способ для ввода данных такого типа. Все, что нужно для
подключения виджета - это добавить следующий код в представление:

```php
<?php
use yii\bootstrap\DatePicker;
?>
<?= DatePicker::widget(['name' => 'date']) ?>
```

В комплект Yii входит большое количество виджетов, например: [[yii\widgets\ActiveForm|active form]],
[[yii\widgets\Menu|menu]], [виджеты jQuery UI](https://github.com/yiisoft/yii2-jui/blob/master/docs/guide/README.md), [виджеты Twitter Bootstrap](https://github.com/yiisoft/yii2-bootstrap/blob/master/docs/guide/usage-widgets.md).
Далее будут представлены базовые сведения о виджетах. Для получения сведений относительно использования
конкретного виджета, следует обратиться к документации соответствующего класса.


## Использование Виджетов <span id="using-widgets"></span>

Главным образом, виджеты применяют в [представлениях](structure-views.md). Для того, чтобы использовать виджет
в представлении, достаточно вызвать метод [[yii\base\Widget::widget()]]. Метод принимает массив [настроек](concept-configurations.md)
для инициализации виджета и возвращает результат его рендеринга. Например, следующий
код добавляет виджет для выбора даты, сконфигурированный для использования русского в качестве языка интерфейса
виджета и хранения вводимых данных в атрибуте `from_date` модели `$model`.

```php
<?php
use yii\bootstrap\DatePicker;
?>
<?= DatePicker::widget([
    'model' => $model,
    'attribute' => 'from_date',
    'language' => 'ru',
    'clientOptions' => [
        'dateFormat' => 'yy-mm-dd',
    ],
]) ?>
```

Некоторые виджеты могут иметь внутреннее содержимое, которое следует располагать между вызовами методов
[[yii\base\Widget::begin()]] и [[yii\base\Widget::end()]]. Например, для генерации формы входа, в следующем
фрагменте кода используется виджет [[yii\widgets\ActiveForm]]. Этот виджет сгенерирует открывающий и закрывающий
тэги `<form>` в местах вызова методов `begin()` и `end()` соответственно. При этом, содержимое, расположенное
между вызовами указанных методов будет выведено без каких-либо изменений.

```php
<?php
use yii\widgets\ActiveForm;
use yii\helpers\Html;
?>

<?php $form = ActiveForm::begin(['id' => 'login-form']); ?>

    <?= $form->field($model, 'username') ?>

    <?= $form->field($model, 'password')->passwordInput() ?>

    <div class="form-group">
        <?= Html::submitButton('Login') ?>
    </div>

<?php ActiveForm::end(); ?>
```

Обратите внимание на то, что в отличие от метода [[yii\base\Widget::widget()]], который возвращает результат
рендеринга, метод [[yii\base\Widget::begin()]] возвращает экземпляр виджета, который может быть
использован в дальнейшем для формирования его внутреннего содержимого.

### Задание глобальных умолчаний

Глобальные умолчания для определённого типа виджета могут быть заданы через DI контейнер:

```php
\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]);
```

Подробнее это описано в [подразделе «Практическое использование» раздела «Контейнер внедрения зависимостей»](concept-di-container.md#practical-usage).

## Создание Виджетов <span id="creating-widgets"></span>

Для того, чтобы создать виджет, следует унаследовать класс [[yii\base\Widget]] и переопределить методы
[[yii\base\Widget::init()]] и/или [[yii\base\Widget::run()]]. Как правило, метод `init()` должен содержать
код, выполняющий нормализацию свойств виджета, а метод `run()` - код, возвращающий результат рендеринга виджета.
Результат рендеринга может быть выведен непосредственно с помощью конструкции "echo" или же возвращен
в строке методом `run()`.

В следующем примере, виджет `HelloWidget` HTML-кодирует и отображает содержимое, присвоенное свойству `message`.
В случае, если указанное свойство не установлено, виджет, в качестве значения по умолчанию отобразит строку "Hello World".

```php
namespace app\components;

use yii\base\Widget;
use yii\helpers\Html;

class HelloWidget extends Widget
{
    public $message;

    public function init()
    {
        parent::init();
        if ($this->message === null) {
            $this->message = 'Hello World';
        }
    }

    public function run()
    {
        return Html::encode($this->message);
    }
}
```

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

```php
<?php
use app\components\HelloWidget;
?>
<?= HelloWidget::widget(['message' => 'Good morning']) ?>
```

Ниже представлен вариант виджета `HelloWidget`, который принимает содержимое, обрамленное вызовами методов
`begin()` и `end()`, HTML-кодирует его и выводит.

```php
namespace app\components;

use yii\base\Widget;
use yii\helpers\Html;

class HelloWidget extends Widget
{
    public function init()
    {
        parent::init();
        ob_start();
    }

    public function run()
    {
        $content = ob_get_clean();
        return Html::encode($content);
    }
}
```

Как Вы можете видеть, в методе `init()` происходит включение буферизации вывода PHP таким образом, что весь вывод
между вызовами `init()` и `run()` может быть перехвачен, обработан и возвращен в `run()`.

> Info: При вызове метода [[yii\base\Widget::begin()]] будет создан новый экземпляр виджета, при этом
вызов метода `init()` произойдет сразу после выполнения остального кода в конструкторе виджета.
При вызове метода [[yii\base\Widget::end()]], будет вызван метод `run()`, а возвращенное им значение будет выведено
методом `end()`.

Следующий фрагмент кода содержит пример использования модифицированного варианта `HelloWidget`:

```php
<?php
use app\components\HelloWidget;
?>
<?php HelloWidget::begin(); ?>

    content that may contain <tag>'s

<?php HelloWidget::end(); ?>
```

В некоторых случаях, виджету может потребоваться вывести крупный блок содержимого. И хотя это содержимое может
быть встроено непосредственно в метод `run()`, целесообразней поместить его в [представление](structure-views.md)
и вызвать метод [[yii\base\Widget::render()]] для его рендеринга. Например,

```php
public function run()
{
    return $this->render('hello');
}
```

По умолчанию, файлы представлений виджетов должны находиться в директории `WidgetPath/views`, где `WidgetPath` -
директория, содержащая файл класса виджета. Таким образом, в приведенном выше примере, для виджета будет
использован файл представления `@app/components/views/hello.php`, при этом файл с классом виджета расположен в
`@app/components`. Для того, чтобы изменить директорию, в которой содержатся файлы-представления для виджета,
следует переопределить метод [[yii\base\Widget::getViewPath()]].


## Лучшие Практики <span id="best-practices"></span>

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

При создании виджетов, следует придерживаться основных принципов концепции MVC. В общем случае, основную логику
следует располагать в классе виджета, разделяя при этом код, отвечающий за разметку в [представления](structure-views.md).

Разрабатываемые виджеты должны быть самодостаточными. Это означает, что для их использования должно быть
достаточно всего лишь добавить виджет в представление. Добиться этого бывает затруднительно в том случае,
когда для его функционирования требуются внешние ресурсы, такие как CSS, JavaScript, изображения и т.д.
К счастью, Yii предоставляет поддержку механизма для работы с ресурсами [asset bundles](structure-assets.md),
который может быть успешно использован для решения данной проблемы.

В случае, когда виджет не содержит логики, а содержит только код, отвечающий за вывод разметки, он мало
отличается от [представления](structure-views.md). В действительности, единственное его отличие состоит в том, что
виджет представляет собой отдельный и удобный для распространения класс, в то время как представление - это
обычный PHP скрипт, подходящий для использования только лишь в конкретном приложении.
