jqueryui - VWJavascript/Alurapic GitHub Wiki

JqueryUI

Criamos BotaoComponent que pode ou não confirmar uma operação. Contudo, essa confirmação é feita pelo confirm padrão do JavaScript. Não seria muito mais bonito se pudéssemos realizar essa confirmação em um formulário modal? No entanto, nem todo desenvolver sabe construir um formulário modal e talvez seja por isso que o jQueryUI, um conjunto de plugins do jQuery tenha se tornado tão popular.

jQueryUI e diálogos modais

Um dos plugins do jQueryUI é o Dialog, que permite criarmos caixas de diálogos. Já aprendemos a integrar nossa aplicação com o jQuery, que tal fazemos a mesma coisa com o jQueryUI? A ideia é criarmos um componente chamado modal que encapsula o jQueryUI Dialog para nós.

Primeiro vamos baixar o jQueryUI através do npm. Dentro da pasta alurapic/client vamos executar o comando:

npm install [email protected] --save
npm install [email protected] --save

O jquery-migrate é necessário, pois estamos usando a versão do jQuery 3.0. Sem ele, o jQueryUI apresentará vários problemas.

Sabemos que isso não é suficiente, pois o TypeScript não saberá indentificar as novas funções adicionadas no jQuery através do plugin impedindo a compilação dos arquivos .ts que chamarem essas funções. Precisamos baixar o arquivo tsd com as definições do jQueryUI.

Como o jQuery e o jQueryUI não usam o sistema de módulos do ES2015, precisamos importar seus scripts em alurapic/client/index.html. O jQuery já importamos, falta o jQueryUI que, além de importarmos seu script, precisamos importar seus arquivos css:

<!-- alurapic/client/index.html -->
<!doctype html>
<html>
    <head>
        <base href="/">
        <title>Alurapic</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">

        <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
        <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap-theme.min.css">

        <!-- importando o estilo do jQueryUI -->

        <link rel="stylesheet" href="node_modules/jqueryui-browser/themes/base/jquery-ui.css">

        <script src="node_modules/jquery/dist/jquery.min.js"></script>      

        <!-- tem que ser importado depois do jQuery -->
        <script src="node_modules/jquery-migrate/dist/jquery-migrate.min.js"></script>
        <script src="node_modules/jqueryui-browser/ui/jquery-ui.js"></script>

        <!-- scritps do Angular e o System.js omitidos  -->
    </head>
    <!-- código posterior omitido -->

Ainda no terminal e dentro da pasta alurapic/client vamos fazer:

node ./node_modules/typings/dist/bin install dt~jqueryui --global --save

Construindo nosso ModalComponent e encapsulando o jQueryUI

Pronto, agora vamos criar nosso ModalComponent em alurapic/client/app/modal/modal.component.ts:

// alurapic/client/app/modal/modal.component.ts

import {Component, ElementRef, Output, Input, EventEmitter} from '@angular/core';


@Component({
    moduleId: module.id,
    selector: 'modal',
    templateUrl: './modal.component.html'
})
export class ModalComponent { 

    private _element: ElementRef;

    @Input() private titulo: string = 'Tem certeza?';
    @Input() private frase: string;
    @Output() confirma = new EventEmitter();

    constructor(private element: ElementRef) {
        this._element = element;
    }

    show() {    

       // quando chamado, exibe o modal!
    }
}

Como já fizemos antes, recebemos no construtor da classe ModalComponent o ElementRef que encapsula o elemento do DOM no qual nosso ModalComponent está associado. Além disso, podemos passar como configuração para nosso modal um título e uma frase. Por fim, quando o usuário clicar no botão de confirmação do nosso painel emitiremos o evento confirma. Mas onde está o template do nosso modal? Vamos criá-lo:

<!-- alurapic/client/app/modal/modal.component.html -->

<div>
    <p>{{frase}}<p>    
</div>

Você deve estar se perguntando onde está o botão "Confirmar" e o botão "Cancelar", certo? Esses botões são criados automaticamente pelo plugin Dialog do jQueryUI. Sendo assim, vale a pena recapitular mais uma vez que, ao ser clicado, o botão "Confirma" disparará o evento confirma. Quem ouvir a este evento executará um determinado código.

Precisamos agora configurar nosso modal utilizando o jQueryUI. Faremos isso no construtor de ModalComponent. Daí, teremos a garantia de que essa configuração será executada toda vez que um novo componente for criado:

// alurapic/client/app/modal/modal.component.ts

import {Component, ElementRef, Output, Input, EventEmitter} from '@angular/core';


@Component({
    moduleId: module.id,
    selector: 'modal',
    templateUrl: './modal.component.html'
})
export class ModalComponent { 

    private _element: ElementRef;

    @Input() private titulo: string = 'Tem certeza?';
    @Input() private frase: string;
    @Output() confirma = new EventEmitter();

    constructor(private element: ElementRef) {
        this._element = element;

        $(this.element.nativeElement).dialog({
            title: this.titulo,
            autoOpen: false,
            resizable: false,
            modal: true,
            buttons: {
                Cancelar: ()=> {
                    $(this.element.nativeElement).dialog( "close" );
                },
                Confirmar: ()=> {
                    $(this.element.nativeElement).dialog( "close" );
                    this.confirma.emit();
                }
            }
        });
    }

    show() {    

       // quando chamado, exibe o modal!
    }
}

Veja que o botão "Cancelar", ao ser clicado, fecha o modal invocando a função dialog com o parâmetro "close" no elemento do DOM que representa nosso modal. Um pouco parecido é o "Confimar", contudo ele além de fechar o modal dispara o evento confirma através de this.confirma.emit(). É esse evento quando disparado que executará a logica de um componente condicionada à confirmação.

No entanto, você deve estar pensando o motivo da criação do método show. Esse método é importante para nós, porque só faz sentido exibir nosso modal quando o usuário, por exemplo, clicar em um botão. Ele clica no botão, e o modal é exibido com as duas opções: "Confirmar" e "Cancelar". Precisamos completar esse método:

// alurapic/client/app/modal/modal.component.ts

import {Component, ElementRef, Output, Input, EventEmitter} from '@angular/core';


@Component({
    moduleId: module.id,
    selector: 'modal',
    templateUrl: './modal.component.html'
})
export class ModalComponent { 

    private _element: ElementRef;

    @Input() private titulo: string = 'Tem certeza?';
    @Input() private frase: string;
    @Output() confirma = new EventEmitter();

    constructor(private element: ElementRef) {
        this._element = element;

        $(this.element.nativeElement).dialog({
            title: this.titulo,
            autoOpen: false,
            resizable: false,
            modal: true,
            buttons: {
                Cancelar: ()=> {
                    $(this.element.nativeElement).dialog( "close" );
                },
                Confirmar: ()=> {
                    $(this.element.nativeElement).dialog( "close" );
                    this.confirma.emit(null);
                }
            }
        });
    }

    show() {    

        $(this._element.nativeElement).dialog('open');
    }
}

Vamos criar o módulo ModalModule e importar o recém criado ModalComponent:

// alurapic/client/app/modal/modal.module.ts

import { Component, NgModule } from '@angular/core';
import { ModalComponent } from './modal.component';

@NgModule({

    declarations: [ ModalComponent ],
    exports: [ ModalComponent ]
})
export class ModalModule { }

Para que nosso componente seja acessível em nossa aplicação, precisamos importar ModalModule em AppModule:

// alurapic/client/app/app.module.ts 

import { NgModule }  from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { FotoModule } from './foto/foto.module';
import { HttpModule } from '@angular/http';
import 'rxjs/add/operator/map';
import { PainelModule } from './painel/painel.module';
import { CadastroComponent } from './cadastro/cadastro.component';
import { ListagemComponent } from './listagem/listagem.component';
import { routing }  from './app.routes';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BotaoModule } from './botao/botao.module';

// nova importação! Não esqueça de adicionar no array da propriedade imports
import { ModalModule } from './modal/modal.module';

@NgModule({
    imports: [ 
        BrowserModule, 
        FotoModule, 
        HttpModule, 
        PainelModule, 
        routing, 
        FormsModule, 
        ReactiveFormsModule,
        BotaoModule, 
        ModalModule
    ],
    declarations: [ AppComponent, CadastroComponent, ListagemComponent ],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

Agora que terminamos nosso componente, vamos utilizá-lo no template de ListagemComponent. Primeiro, vamos desligar a confirmação de BotaoComponent pois ela será feita pelo nosso modal. Em seguida, imediatamente abaixo de BotaoComponent, vamos adicionar ModalComponent:

Vamos adicionar ModalComponent logo abaixo dele:

<!-- alurapic/client/app/listagem/listagem.component.html -->

<!-- atenção, botao não pode mais executar seu código de confirmação, por isso [confirmacao]="false" -->

<botao nome="Remover" estilo="btn-danger btn-block" (acao)="remove(foto, painel)" [confirmacao]="false"></botao>
<modal frase="Tem certeza que deseja remover esta foto?" (confirma)="??????????????"></modal>

Veja que o ModalComponent recebe como parâmetro a frase que desejamos exibir dentro do nosso diálogo, contudo veja que nosso event binding (confirma) não sabe o que executar quando o usuário clica no botão "Confirmar" do modal. Quando a confirmação for realizada, requeremos remover a foto, sendo assim, vamos transferir a expressão remove(foto, painel) de BotaoComponent para nosso modal. Fica assim:

Utilizando nosso novo componente

<!-- alurapic/client/app/listagem/listagem.component.html -->
<!-- código anterior omitido -->

<botao nome="Remover" estilo="btn-danger btn-block" (acao)="???" [confirmacao]="false"></botao>
<modal frase="Tem certeza que deseja remover esta foto?" (confirma)="remove(foto, painel)"></modal>

<!-- código posterior omitido -->

Movemos a expressão de BotaoComponent para ModalComponent, no entanto sabemos que nosso BotaComponent deve chamar nosso modal. Também sabemos que ao ser clicado, BotaoComponent dispara o evento acao que executa uma expressão, mas essa expressão foi movida para nosso modal. Qual expressão usaremos agora? Precisamos usar uma expressão que permita chamar o método show do nosso ModalComponent para que ele seja exibido, permitindo assim que o usuário clique em "Confirmar" ou "Cancelar".

Aprendemos a criar variáveis de template, vamos criar a variável #modal em nosso ModalComponent:

<!-- alurapic/client/app/listagem/listagem.component.html -->
<!-- código anterior omitido -->

<botao nome="Remover" estilo="btn-danger btn-block" (acao)="???" [confirmacao]="false"></botao>
<modal #modal frase="Tem certeza que deseja remover esta foto?" (confirma)="remove(foto, painel)"></modal>

<!-- código posterior omitido -->

Agora, em BotaoComponent vamos chamar modal.show() toda vez que ele for clicado. Relembrando mais uma vez que, ao ser clicado, nosso BotaoComponent dispará o evento acao e toda expressão associada à este evento é chamada:

<!-- alurapic/client/app/listagem/listagem.component.html -->
<!-- código anterior omitido -->

<botao nome="Remover" estilo="btn-danger btn-block" (acao)="modal.show()" [confirmacao]="false"></botao>
<modal #modal frase="Tem certeza que deseja remover esta foto?" (confirma)="remove(foto, painel)"></modal>

<!-- código posterior omitido -->

Excelente, que tal testarmos?

Algum erro acontece, pois o conteúdo no modal está sendo exibido. Parece que ele não está sendo escondido, muito menos estilizado pelo jQueryUI. Como isso pode acontecer se colocarmos nosso código no construtor do nosso componente?

Isso acontece porque quando o construtor do nosso componente é chamado, o seu template ainda não foi renderizado. O jQueryUI precisa do DOM do template do nosso componente para realizar a transformação. E agora?

Ciclo de vida de um componente, agora com AfterViewInit Talvez você lembre do ciclo de vida de um componente, mais propriamente do método ngOnInit() chamado logo após do construtor da classe do componente ter sido invocado. No entanto, ele não é suficiente, porque nesta fase o template também não foi processado. É por isso que precisamos colocar a inicialização do nosso modal na fase ngAfterViewInit.

Vamos importar a interface AfterViewInit para garantir que estamos implementando o nome do método corretamente e mover o código que inicializa nosso modal para dentro dele:

import {Component, ElementRef, Output, Input, EventEmitter, AfterViewInit} from '@angular/core';


@Component({
    moduleId: module.id,
    selector: 'modal',
    templateUrl: './modal.component.html'
})
export class ModalComponent implements AfterViewInit{ 

    private _element: ElementRef;

    @Input() private titulo: string = 'Tem certeza?';
    @Input() private frase: string;
    @Output() confirma = new EventEmitter();

    constructor(private element: ElementRef) {

        this._element = element;
    }

    ngAfterViewInit() {

        $(this.element.nativeElement).dialog({
                title: this.titulo,
                autoOpen: false,
                resizable: false,
                modal: true,
                buttons: {
                    Cancelar: ()=> {
                        $(this.element.nativeElement).dialog( "close" );
                    },
                    Confirmar: ()=> {
                        $(this.element.nativeElement).dialog( "close" );
                        this.confirma.emit(null);
                    }
                }
        });

    }

    show() {    

        $(this._element.nativeElement).dialog('open');
    }
}

Perfeito agora! Nosso modal é exibido e funciona corretamente!

⚠️ **GitHub.com Fallback** ⚠️