Criando um Input Widget no Yii2 – Parte 1/3

E aew galerinha do mal, tudo sussa com vocês ?! Espero que sim !

Nesse post tenho como objetivo te ensinar a criar um Input Widget no Yii Framework 2. Caso você ainda não saiba o que diachos é um widget no Yii2, aconselho você ver este post onde explico tudo nos mííííííííííínimos detalhes o que é e como criar um.

IMPORTANTE: estou partindo do pressuposto que você já sabe pelo menos usar um widget ok ?! Beleza ?! Tudo certo !? Posso começar ?! Tem certeza ?!

Definição

Por mais que você já saiba do que se trata um widget, não custa nada eu reforçar sua definição e sua essência, vamos ver abaixo algumas definições segundo a documentação oficial do Framework:

“Os widgets são blocos de construção reutilizáveis usados nas views para criar e configurar complexos elementos de interface do usuário sob uma modelagem orientada a objetos”.

“Os widgets são uma maneira orientada a objetos de reutilizar códigos de view. Ao criar os widgets, você ainda deve seguir o padrão MVC. Em geral, você deve manter a lógica nas classes widgets e manter as apresentações nas views.”

“Os widgets devem ser projetados para serem auto suficientes. Isto é, ao utilizar um widget, você deverá ser capaz de removê-lo de uma view sem fazer qualquer outra coisa”

Fonte: Site Oficial do Yii2

Creio que a definição tenha sido bem clara, mas caso você não tenha entendido, widgets são componentes customizáveis para serem reutilizáveis nas views.

NOTA: as duas últimas frases são simplesmente lindas!!! Estou neste momento tendo orgasmos só em ter lido!

Quando vou precisar criar um widget ?

Podem até haver mais motivos do que esses citados, mas basicamente existem 2 pontos que se faz necessário (ou pelo menos deve fazer) para criar um widget:

  1. Uma “coisa” que esteja desenvolvendo exija muita complexidade para sua view, tais como: muita regra de visualização, muitos assets, interação com collections e etc;

  2. Quando essa “coisa” complexa for utilizada em outras partes da sua aplicação;

Se o que você está desenvolvendo nesse momento contempla esses 2 pontos, provavelmente você deverá criar um widget para o bem estar do seu projeto.

Diferença entre WIDGET e INPUT WIDGET

Eu não diria diferença entre um e outro, até porque a classe InputWidget é filha (extende) da class Widget.

Para deixar tudo claro na sua cabecinha, um Input Widget é um Widget com mais super poderes, mais facilidades para trabalhar com widgets para elementos de formulário.

“Como assim elementos de formulário professor?”

Tá bom, confesso que eu quis falar bonito… eu quis dizer: campos de texto, checkboxes, radio buttons e etc.

“Ah bom, agora entendi =D”.

NOTA: nada te impede de criar um widget para elementos de formulário com a classe base Widget, você só terá mais um pouquinho de trabalho, mas dá! rsrsr

Nosso exemplo prático

Nosso Input Widget será um campo onde o usuário irá informar o CEP e o próprio widget fará a “tarefa suja” de consultar esse CEP no site Via CEP via AJAX (poderia ser em qualquer outro terceiro, esse foi o primeiro que veio no google rsrsrs) e popular as informações de endereço em seus respectivos campos.

Esse widget tem que ser auto suficiente e ele poderá ser utilizado em vários lugares da nossa aplicação.

Formulário de Endereço

Vamos criar o nosso formulário AddressForm:

namespace app\models;

use yii\base\Model;

class AddressForm extends Model
{
    public $zipcode;
    public $street;
    public $neighborhood;
    public $city;
    public $state;

    public function rules()
    {
        return [
            [['zipcode', 'street', 'neighborhood', 'city', 'state'], 'required'],
            ['state', 'string', 'max' => 2],
        ];
    }

    public function attributeLabels()
    {
        return [
            'zipcode' => 'CEP',
            'street' => 'Logradouro',
            'neighborhood' => 'Bairro',
            'city' => 'Cidade',
            'state' => 'UF'
        ];
    }
}

Vamos às explicações:

  • Criei 5 atributos para nosso formulário que irá representar as informações de endereço;

  • No método rules() eu coloquei todos os campos como required, ou seja, ao submeter o formulário todos os campos terão que ser preenchidos. Não vai ter tanto efeito para nosso caso, mas deixei assim apenas como informação extra;

  • No método attributeLabels() eu estou configurando um array que representa o label de cada atributo do formulário;

NOTAClique aqui caso você queira se aprofundar um pouco mais no assunto de formulários. Eu tenho um post no blog + vídeo no nosso canal falando somente sobre formulários.

View onde será aplicado o Input Widget

Depois de criado nosso formulário, vamos agora instanciá-lo no controlador e enviar sua instância para a view, dessa forma:

$addressForm = new app\models\AddressForm;

return $this->render('address', [
    'formModel' => $addressForm
]);

Na view address, vamos montar nossa tela dessa forma:

<?php
/** @var yii\web\View $this */
/** @var \app\models\AddressForm $formModel */
use yii\widgets\ActiveForm;

$this->title = 'Address Form';
?>

<div class="site-index">
    <?php $form = ActiveForm::begin() ?>
        <div class="row">
            <div class="col-md-3 form-group">
                <div class="input-group">
                    <?= $form->field($formModel, 'zipcode', ['template' => '{label}{input}'])->textInput([
                        'placeholder' => 'Ex: 60445-123',
                        'class' => 'form-control my-zip-code'
                    ]) ?>
                    <span class="input-group-btn">
                        <button class="btn btn-primary" style="margin-top: 26px" type="button">
                            Consultar!
                        </button>
                    </span>
                </div>
            </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>

Se você fez tudo direitinho, você deve ter um resultado semelhante a imagem abaixo:

Quero que você se atente nesse momento a estrutura para montar o campo de CEP:

<div class="input-group">
    <?= $form->field($formModel, 'zipcode', ['template' => '{label}{input}'])->textInput([
    'placeholder' => 'Ex: 60445-123',
    'class' => 'form-control my-zip-code'
    ]) ?>
    <span class="input-group-btn">
        <button class="btn btn-primary btn-search-zipcode" style="margin-top: 26px" type="button">Consultar!</button>
    </span>
</div>

Perceba que por si só ele já tem uma certa complexidade em sua estrutura. Isso porque ainda nem preparamos o trecho de código javascript para fazer a requisição no site para buscar as informações de endereço.

Implementando a Requisição no Via CEP

Vamos agora adicionar a porção de código javascript que será responsável de fazer a requisição no site Via CEP e nos trazer as informações do endereço.

IMPORTANTE: não se preocupe caso você NÃO tenha conhecimento em javascript nesse momento, pois o meu objetivo aqui é que você entenda o que vai ser necessário para que se o widget funcione de forma auto suficiente, como foi discriminado lá na definição.

Vamos agora inserir um bloco de texto javascript que irá fazer toda a mágica de buscar os dados da API e preencher os campos de endereço:

$this->registerJs('
    class MyCepInput {
	constructor() {
    	this.inputCep = document.querySelector("input.my-zip-code");
    	this.searchButton = document.querySelector("button.btn-search-zipcode");
    	this.inputStreet = document.querySelector("input#addressform-street");
    	this.inputNeighborhood = document.querySelector("input#addressform-neighborhood");
    	this.inputCity = document.querySelector("input#addressform-city");
    	this.inputState = document.querySelector("input#addressform-state");

    	this.searchButton.addEventListener("click", this.searchZipCode.bind(this))
	}

	searchZipCode(e) {
    	if (this.inputCep.value === "") {
        	alert("Informe um CEP para poder consultar.");
        	this.inputCep.focus();
        	return;
    	}

    	e.target.disabled = true;

    	fetch("https://viacep.com.br/ws/"+ this.inputCep.value.replace(/[^\w\s]/gi, "") +"/json")
        	.then(response => response.json())
        	.then(data => {
            	e.target.disabled = false;

            	if (data.hasOwnProperty("erro")) {
                	alert("O CEP informado não foi encontrado.");
                	this.inputCep.focus();
                	return;
            	}

            	this.inputStreet.value = data.logradouro;
            	this.inputNeighborhood.value = data.bairro;
            	this.inputCity.value = data.localidade;
            	this.inputState.value = data.uf;
        	})
        	.catch(error => {
            	e.target.disabled = false;
            	console.log(error);
        	});
	}
}

const myCepInput = new MyCepInput();
', View::POS_END);

O Resultado deve ser algo parecido com isso:

Percepção da Complexidade

Até esse ponto aqui, eu quero que você olhe para o tamanho e a complexidade código da sua view, junto a estrutura HTML necessária + Javascript. Creio que o GIF abaixo deve representar sua reação:

No momento que você percebeu essa complexidade e teve uma reação parecida com o GIF acima, esse é o estalo para você transformar sua rotina em um widget, pois sua complexidade já é bem notória e sempre pense que a tendência de componentes assim é sempre crescer/melhorar.

Agora imagine ter que copiar e colar esse código em 6 lugares diferentes da sua aplicação para consultar endereços? Imaginou?! Sentiu a dor?!

Ótimo, pois agora some isso com solicitações de melhorias dessa rotina, seja uma mudança simples da cor do campo, do endpoint na API?! Imaginou tem que mudar em 6 lugares a mesma coisa ?! Sentiu a dor novamente ?! Deu um tremilique em você ?!

Se você sentiu a dor, você é um programador gente boa e que se preocupa com seu código e a longevidade da sua aplicação.

E aí? Vamos refatorar essa bagaça?

Vamos sim, mas somente na PARTE 2 dessa série de posts. Sei que você nesse momento deve estar puto comigo, mas eu sou desses, gosto de deixar você na curiosidade para segurar a audiência kkkkkkk.

Deixa nos comentários tuas dúvidas e se deu tudo certo até esse ponto aqui.

Forte abraço e te encontro na Parte 2.