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.
NOTA: Clique 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.
Oi Amigo,
Fiz exatamente como esta no tutorial, inclui o widget em uma view, mas yii2 não encontrou a classe do widge ao incluir na view.
Tem que ser colocado de alguma maneira especifica?
Inclui o form e tudo que precisa corretamente, somente o inputwidget que não foi encontrado.
Fala Lenon, beleza? Estranho, se tu vez exatamente como no tutoria deveria funcionar. Ocorreu algum erro? Teria como por teu código no Gist e mandar o link aqui ?