Criando um Input Widget no Yii2 – Parte 2/3

Essa é a segunda parte da série de posts sobre Input Widget. Para entendimento total dessa série é necessário ler PARTE 1 onde fazemos um introdução sobre widgets e já falamos sobre a estrutura do nosso exemplo.

Vamos refatorar essa bagaça

Depois de muito sofrimento e dor, vamos refatorar essa parada criando nosso widget e deixando uma paz interior dentro de nossos corações. Acredito que isso é o que mais vocês desejam nesse momento.

Vamos criar a nossa classe que representará o nosso Input Widget. Levando em consideração que estamos utilizando o template basic do Yii 2, geralmente os widgets ficam dentro do diretório widgets.

Arquitetura de Pastas do nosso Widget

Dentro de widgets vamos criar essa estrutura de arquivos:

Vamos às explicações dessa estrutura:

  • myCepInput: é o nosso pacote. Aqui onde vamos por todos os arquivos necessários para que nosso widget funcione lindamente;

  • myCepInput/medias: aqui ficarão todas as medias que o widget usará. Aqui você vai por arquivos javascripts, css, imagens e etc.

  • myCepInput/views: são os arquivos de view que o nosso widget irá renderizar. Aqui deve ser colocados o(s) template(s) usados pelo widget;

  • myCepInput/MyCepInput.php: aqui é nossa classe onde terá toda a lógica de funcionamento do nosso widget. É ela que será invocada e fará toda a mágica para nós;

  • myCepInput/MyCepInputAsset.php: como vocês bem sabem, a pasta widgets não fica dentro do document root da aplicação, e por isso, precisa de um AssetBundle para que seja publicado dentro do diretório web/assets

Implementação do Widget

Vamos iniciar implementando nossa classe MyCepInput:

class MyCepInput extends InputWidget
{
    public $options = ['placeholder' => 'Ex: 60445-123', 'class' => 'form-control my-zip-code'];

    public function run()
    {
        $input = Html::input('text', $this->name, $this->value, $this->options);
        $label = Html::label('CEP', $this->name, ['class' => 'control-label']);

        if ($this->hasModel()) {
            $this->field->label(false);
            $this->field->template = '{label}{input}';
            
            $input = Html::activeInput('text', $this->model, $this->attribute, $this->options);
            $label = Html::activeLabel($this->model, $this->attribute, ['class' => 'control-label']);
        }

        return $this->render('template', [
            'input' => $input,
            'label' => $label
        ]);
    }
}

Vamos às explicações:

  • Estamos sobrescrevendo o atributo options para que por padrão o nosso input já tenha o atributos HTML placeholder e o class;

  • São criado 2 variáveis que armazena a string HTML do input e do label para caso o widget seja invocado de forma “standalone”:
    MyCepInput::widget([‘name’ => ‘cep’])

  • Caso seja invocado por um model (que é o nosso caso) eu estou acessando o objeto field que invocou o nosso widget para poder remover o label e aplicar um template de renderização. Fiz isso apenas para termos mais liberdade de mexer com os elementos;

  • Por fim nosso widget renderiza nosso template enviando o input e o label;

View do Widget

Depois de detalhado como funciona nossa classe vamos agora “arrancar” o trecho do template que possui o nosso campo de CEP com botão que fizemos lá no início e fazer as modificações como abaixo:

<?php
/** @var \yii\web\View $this */
/** @var string $input */
/** @var string $label */
?>
<div class="input-group">
    <?= $label ?>
    <?= $input ?>
    <span class="input-group-btn">
        <button class="btn btn-primary btn-search-zipcode" style="margin-top: 26px" type="button">
            Consultar!
        </button>
    </span>
</div>

Creio que não tem muito o que explicar aqui, é quase a mesma coisa que fizemos lá no início do tutorial com leves alterações.

Feito isso, vamos agora no nosso formulário e invocar o nosso widget desse forma:

<div class="site-index">
    <?php $form = ActiveForm::begin() ?>
        <div class="row">
            <div class="col-md-3 form-group">
                <?= $form->field($formModel, 'zipcode')->widget(MyCepInput::class) ?>
            </div>
        </div>
        <div class="row">
            <div class="col-md-4">
                <?= $form->field($formModel, 'street') ?>
            </div>
            <div class="col-md-4">
                <?= $form->field($formModel, 'neighborhood') ?>
            </div>
            <div class="col-md-3">
                <?= $form->field($formModel, 'city') ?>
            </div>
            <div class="col-md-1">
                <?= $form->field($formModel, 'state') ?>
            </div>
        </div>
    <?php ActiveForm::end() ?>
</div>

No ponto que estamos aqui o seu componente deve estar funcionando como antes, só que tem um baita de um javascript que faz todos os paranauês no nosso formulário, e agora ?!

Configurando o AssetBundle do Widget

Como foi falado anteriormente, a pasta widgets não fica dentro do document root da aplicação, e por isso, precisa de um AssetBundle para que seja publicado dentro do diretório web/assets.

NOTAClique aqui caso você não esteja familiarizado com Asset Bundles e quer saber mais sobre o seu funcionamento.

Vamos fazer agora com que esse bloco javascript faça parte do nosso widget. Pegue somente o trecho javascript que está no formulário e coloque no arquivo widgets/myCepInput/medias/myCepInput.js. Feito isso, vamos configurar o nosso MyCepInputAsset e registrar ele quando o nosso widget for invocado:

namespace app\widgets\myCepInput;

use yii\web\AssetBundle;

class MyCepInputAsset extends AssetBundle
{
    public $sourcePath = '@app/widgets/myCepInput/medias';
    public $js = ['myCepInput.js'];
}

Caso o seu widget precisasse de mais scripts javascripts, que é algo muito comum, ou de arquivos css, basta colocá-los dentro do diretório media e registrá-los no MyCepInputAsset que ele registrará tudo bonitinho.

Implementado nosso Asset Bundle, vamos agora registrá-lo logo na primeira linha do método run() do nosso widget, e logo após colocar a instanciação da classe javascript ficando dessa forma:

class MyCepInput extends InputWidget
{
    ...
    public function run()
    {
        MyCepInputAsset::register($this->view);

        $this->view->registerJs('
            const myCepInput = new MyCepInput();	 
    	');
        ...
    }
}

E voilà

Aí meu amigo, feito tudo isso é só correr pro abraço, usar, abusar do seu mais novo widget e ser feliz!

Eu vou ter que fazer Widget para tudo?!

Se você tá se perguntando isso neste momento, saiba que o Yii2 já tem VÁRIOS widgets oficiais e também feito por terceiros prontos para uso:

  • Tem um dos widgets mais utilizados que é ActiveForm, que foi o que utilizamos no nosso formulário;

  • Temos o Menu;
  • Widgets para o JQuery UI;
  • Widgets para Bootstrap;
  • E muito mais…

Parametrizando widget

Nosso widget tá funcionando legal, bonitinho e tudo mais. Porém, vocês já perceberam que o id dos campos de endereço (logradouro, bairro, cidade e estado) estão “chumbados” no nosso widget? Isso não é muito legal pois vai sempre obrigar os campos de endereço a ter sempre o mesmo id.

O Ideal seria de alguma forma ser enviado para o nosso componente uma lista com o id de cada campo de endereço. Para isso, lá na invocação do nosso widget podemos passar uma lista de atributos para fazermos o que bem entendermos.

Mas isso só faremos na parte 3 e última desse série de posts sobre Input Widgets.

Deixa nos comentários tuas dúvidas e se deu tudo certo nessa parte 2.

Forte abraço e te encontro na Parte 3.