AngularJS: Formulário de Contato
Neste tutorial iremos utilizar o angularjs juntamente com o PHP para o envio de emails de um formulário de contatos.
Requisitos para esse Tutorial
- O projeto Anterior pode ser visto aqui.
- WAMP, LAMP, MAMP e/ou Plano de Hospedagem.
- Conhecimento básico de PHP.
Instalação do Wamp
A instalação passo-à-passo pode ser vista neste link.
Desenvolvimento da Página de Contato
Primeiramente, iremos mover os arquivos do Tutorial anterior para a pasta www do seu wamp. Geralmente, localiza-se em c:\wamp\www . Em seguida, criaremos uma pasta com o nome ‘tutorial’ e a sua estrutura ficará parecida com esta:
- www
- tutorial
- css
- fonts
- img
- js
- templates
- index.html
- tutorial
Ao acessar http:\localhosttutorial em seu navegador, conforme nosso exemplo anterior, uma página semelhante à imagem abaixo deverá aparecer:
O primeiro passo será atualizar a versão do nosso angular, que referenciamos no arquivo index.html, conforme exemplo abaixo:
1 2 |
<!-- alteramos o angular da versão 1.3.14 para 1.4.1 --> <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular.min.js"></script> |
Feito isso, iremos editar o arquivo templatesheader.html, para acrescentarmos ao menu o link para contato.
1 2 3 4 5 6 7 8 |
<ul class="nav navbar-nav"> <!-- cada linha é um item do menu --> <li><a href="#/">Home</a></li> <li><a href="#/servicos">Serviços</a></li> <li><a href="#/precos">Preços</a></li> <li><a href="#/sobre">Sobre</a></li> <li><a href="#/contato">Contato</a></li> <!-- nosso novo item do menu --> </ul> |
A seguir iremos acrescentar a rota, para carregarmos tanto o modelo da página de contatos como o seu controller. Para isso iremos editar o arquivo jsmain.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
app.config(['$routeProvider', function ($routeProvider) { $routeProvider // aqui dizemos quando estivernos na url / vamos carregar o conteúdo da pagina inicila a home // no segundo parametro definimos um objeto contendo o templateUrl da nossa pagina home e o controller que irá // preparar o conteudo e processar outros eventos da página que veremos posteriormente .when("/", {templateUrl: "templates/home.html", controller: "HomeCtrl"}) // configuração das rotas bem parecidas para as outras paginas .when("/sobre", {templateUrl: "templates/sobre.html", controller: "SobreCtrl"}) .when("/servicos", {templateUrl: "templates/servicos.html", controller: "ServicosCtrl"}) .when("/precos", {templateUrl: "templates/precos.html", controller: "PrecosCtrl"}) /* aqui você pode adicionar rotas para outras paginas que desejar criar */ .when("/contato", {templateUrl: "templates/contato.html", controller: "ContatoCtrl"})// nossa nova rota // por último dizemos se nenhuma url digitada for encontrada mostramos a página 404 que não existe no nosso servidor .when('/404', {templateUrl: "templates/404.html"}) .otherwise("/404"); }]); |
Logo abaixo nesse mesmo arquivo, iremos criar o controller que será o responsável por pegar os dados preenchidos pelo usuário e enviar para o servidor, onde o mesmo encarregar-se-á de enviar o email de contato.
1 2 3 |
app.controller('ContatoCtrl', function ($scope, $location, $http) { }); |
Iremos criar o HTML da página de contatos e posteriormente atualizar este controller. Para isso vamos criar o arquivo contato.html na pasta templates.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
<div class="row" ng-if="!emailEnviado"> <div class="box"> <div class="col-lg-12 text-center"> <hr/> <h2 class="intro-text">Contato</h2> <hr/> </div> <div class="col-lg-12"> <div class="alert alert-danger" ng-if="frmContato.nome.$error && !frmContato.nome.$valid" ng-show="frmContato.nome.$dirty"> <strong ng-show="frmContato.nome.$error.required">Nome é Obrigatório!</strong> <strong ng-show="frmContato.nome.$error.minlength">Nome deve conter no minímo 3 caractéres!</strong> </div> <div class="alert alert-danger" ng-if="frmContato.email.$error && !frmContato.email.$valid" ng-show="frmContato.email.$dirty"> <strong ng-show="frmContato.email.$error.required">Email é Obrigatório!</strong> <strong ng-show="frmContato.email.$error.email">Email está Inválido!</strong> </div> <div class="alert alert-danger" ng-if="frmContato.assunto.$error && !frmContato.assunto.$valid" ng-show="frmContato.assunto.$dirty"> <strong ng-show="frmContato.assunto.$error.required">Assunto é Obrigatório!</strong> <strong ng-show="frmContato.assunto.$error.maxlength">Assunto deve conter no máximo 100 caractéres!</strong> </div> <div class="alert alert-danger" ng-if="frmContato.mensagem.$error && !frmContato.mensagem.$valid" ng-show="frmContato.mensagem.$dirty"> <strong ng-show="frmContato.mensagem.$error.required">Mensagem é Obrigatório!</strong> <strong ng-show="frmContato.mensagem.$error.minlength">Mensagem deve conter no minímo 30 caractéres!</strong> <strong ng-show="frmContato.mensagem.$error.maxlength">Mensagem deve conter no máximo 300 caractéres!</strong> </div> <div class="alert alert-danger" ng-if="emailNaoEnviado"> <strong>Opss!</strong> Houve um problema ao enviar o seu contato, tente mais tarde. </div> <form name="frmContato" class="form-horizontal" role="form"> <div class="form-group" ng-class="{ 'has-error': frmContato.nome.$invalid && frmContato.nome.$dirty}"> <label for="nome" class="col-lg-2 control-label">Nome</label> <div class="col-lg-10"> <input ng-model="formData.nome" ng-minlength="3" type="text" class="form-control" name="nome" placeholder="Seu Nome" ng-required="true"> </div> </div> <div class="form-group" ng-class="{ 'has-error': frmContato.email.$invalid && frmContato.email.$dirty}"> <label for="email" class="col-lg-2 control-label">E-Mail</label> <div class="col-lg-10"> <input ng-model="formData.email" type="email" class="form-control" name="email" placeholder="Seu E-Mail" ng-required="true"> </div> </div> <div class="form-group" ng-class="{ 'has-error': frmContato.assunto.$invalid && frmContato.assunto.$dirty}"> <label for="assunto" class="col-lg-2 control-label">Assunto</label> <div class="col-lg-10"> <input ng-model="formData.assunto" ng-maxlength="100" type="text" class="form-control" name="assunto" placeholder="Assunto" ng-required="true"> </div> </div> <div class="form-group" ng-class="{ 'has-error': frmContato.mensagem.$invalid && frmContato.mensagem.$dirty}"> <label for="mensagem" class="col-lg-2 control-label">Mensagem</label> <div class="col-lg-10"> <textarea ng-model="formData.mensagem" ng-minlength="30" ng-maxlength="300" class="form-control" rows="4" name="mensagem" placeholder="Sua mensagem..." ng-required="true"></textarea> </div> </div> <div class="form-group"> <div class="col-lg-offset-2 col-lg-10"> <button type="submit" ng-disabled="!frmContato.$valid" ng-click="enviarEmail()" class="btn btn-default" ng-disabled="submitButtonDisabled"> Enviar </button> </div> </div> </form> </div> </div> </div> <div class="row" ng-if="emailEnviado"> <div class="box"> <div class="alert alert-success"> <strong>Email foi enviado com sucesso!</strong> Entraremos em contato em breve! </div> </div> </div> |
Verificamos que neste código HTML encontram-se algumas “tags” que naturalmente não fazem parte do HTML-Standard, pois são tags especiais do AngularJS. Abaixo, uma breve descrição de cada uma delas:
ng-if
A documentação oficial <https://docs.angularjs.org/api/ng/directive/ngIf> , nos demonstra a seguinte definição:
A diretiva
ngIf
remove ou recria uma parte da árvore DOM com base em uma {expressão}. Se a expressão for atribuída angIf
e avaliada como um valor falso, então o elemento é removido do DOM, caso contrário, um clone do elemento é reinserido no DOM.
Ou seja, nas linhas 9, 14, 19, 24, estamos usando o ng-if para mostrarmos o alerta, caso cada “input” contenha erro e ele não esteja válido.
ng-show
A documentação oficial <https://docs.angularjs.org/api/ng/directive/ngShow> nos demonstra a seguinte definição:
A diretiva ngShow exibe ou oculta o elemento HTML baseado na {expressão} fornecida. O elemento é exibido ou ocultado pela remoção da classe CSS. ng-hide . Essa classe do AngularJS define “display: none !important” no elemento.
Ou seja, ao contrário do ngIf que não cria o elemento, o ngShow cria o elemento e apenas altera a visibilidade do elemento conforme as expressões nas linhas 9, 14, 19, 24 . Usamos o ngShow para exibir o erro quando o campo html for alterado;
ng-class
Na documentação oficial <https://docs.angularjs.org/api/ng/directive/ngClass> a seguinte definição é apresentada:
A diretiva
ngClass
permite definir dinamicamente classes CSS em um elemento HTML por ligação de dados (databinding). Uma expressão que representa todas as classes a serem adicionados.
Ou seja, as classes de estilo são adicionadas ou removidas do elemento conforme as expressões. Nas linhas 35, 41, 47, 53 o ngClass é utilizado para adicionar a classe css ‘has-error’ caso o input contenha algum erro.
ng-model
A documentação oficial <https://docs.angularjs.org/api/ng/directive/ngModel> oferece a seguinte definição:
A diretiva
ngModel
se liga (bind) a uminput
,select
,textarea
(ou controle de formulário personalizado), para uma propriedade no ‘scope’ usando NgModelController, que é criado e exposto por esta diretiva.
Com isso, posteriormente é através dos ngModel que poderemos pegar os dados digitados em cada input e poderemos enviar para o servidor. Posteriormente, enviará para o email de contato.
ng-click
Na documentação oficial <https://docs.angularjs.org/api/ng/directive/ngClick> encontramos a seguinte definição:
A diretiva ngClick permite que você especifique o comportamento personalizado quando um elemento é clicado.
Na linha 61, usamos o ng-click para chamarmos a função que será responsável pelo envio dos dados do formulário para o servidor.
ng-input, ng-required, ng-minlenght e ng-maxlenght
Na documentação oficial <https://docs.angularjs.org/api/ng/input/input%5Btext%5D> a seguinte definição sobre ng-input é apontada:
Entrada de texto padrão HTML com ligação de dados angular, herdados pela maioria dos
elementos input
.
ngRequired
Adiciona o atributo
required,
necessário
para restrição de validação do elemento, quando a expressão ngRequired avaliada como verdadeira. UsengRequired
em vez derequired
quando você quiser validar o required.
ngMinlenght
Atribui
minLength
(mínimo). Erro de validação se o valor for menor do que minlength.
ngMaxlenght
Seta
maxLength (máximo).
Erro de validação se o valor for maior do que maxlength.
Validação
Para podermos criar a validação o elemento <form> tem que ter o atributo name, e cada input também deve ter o atributo name. Ex: nomeForm.nomeInput
Com isso podemos acessar algumas propriedades:
- $error -> retorna true se existe erro
- $valid -> retorna true quando está válido
- $dirty -> quando o campo foi modificado (geralmente na digitação)
Na propriedade $error, podemos acessar os filhos que compõe a validação como required, minlenght, maxlenght, entre outros. Na linha 9 usamos no ngIf para verificarmos se no frmContato.nome$error existem erros. Já na linha 10 exibiremos a mensagem de obrigatório, caso o frmContato.nome.$error.required estiver vazio após a edição do campo.
Controller
Agora iremos trabalhar no controller que criamos anteriormente no main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
app.controller('ContatoCtrl', function ($scope, $location, $http) { //criando o objeto em branco, porém após o submit todos os dados estaram nessa variável $scope.formData = {}; //variavel para ebir ou não a mensagem que o email foi enviado $scope.emailEnviado = false; //variável para exibir mensagem de erro ao enviar o email $scope.emailNaoEnviado = false; //função que será usada para preparar os dados no formato que poderemos usar no servidor para envio do email var param = function(data) { var returnString = ''; for (d in data){ if (data.hasOwnProperty(d)) returnString += d + '=' + data[d] + '&'; } // Remove o último & que não é necessário return returnString.slice( 0, returnString.length - 1 ); }; $scope.enviarEmail = function() { $http({ method : 'POST', url : 'email.php', data : param($scope.formData), headers : { 'Content-Type': 'application/x-www-form-urlencoded' } }) //função de callback quando a promise for sucesso .success(function(data) { //verifica o retorno do servidor se o email foi enviado if (data.enviado) { $scope.emailEnviado = true; //ocultamos o formulário e exibimos mensagem de sucesso } else { $scope.emailNaoEnviado = true; } }) //função de callback quando a promise for erro (geralmente problema de conexão ou página não existente) .error(function(error){ $scope.emailNaoEnviado = true; }); }; }); |
No início do controller, definimos as 3 variáveis de controle de nossa página de contato, onde $scope.formData terá os dados preenchidos no formulário. Podemos perceber que no html contemos nos input formData.nome pelo fato de ser um objeto e no angular a palavra $scope não é necessária no template html. O mesmo vale para as outras duas variáveis que usamos para mostrar ou não a mensagem de envio, ou se houve algum erro de envio do formulário.
Em seguida temos 2 funções. A primeira é responsável por incluir os dados do formData no formato que iremos pegar no php. A segunda função é que vinculamos ao botão de envio, ele prepara os dados e envia para a url que definimos, e seu correspondente método, nesse caso POST. Isso porque usamos um serviço que o AngularJS possui, o $http, que permite a comunicação com algum servidor. Nesse caso estamos enviando os dados do formulário que será processado e enviado por email.
Este serviço é denominado promise ou promessa, que envia uma solicitação e aguarda este retorno para dar continuidade, neste caso, ao envio de dados. Duas funções são atribuídas a este processo: O success que significa basicamente que o processo obteve êxito em encontrar a url desejada e proceder com o envio de dados, recebendo true como resposta. Caso o retorno seja de fato um true, o email será enviado. Em caso de false, algum problema ocorreu no processo. E o error que neste caso, significa que ele não conseguiu achar essa url, ou de fato encontrou, mas retornou com um erro 404.
Parte do Servidor
E agora criaremos na raiz do projeto onde temos o arquivo index.html, criaremos o arquivo email.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
<?php header('Access-Control-Allow-Origin: *'); $remetente = $_POST['nome'] . " <" . $_POST['email'] . ">"; $nome = $_POST['nome']; $email = $_POST['email']; $assunto = $_POST['assunto']; $msg = $_POST['mensagem']; $destino = "contato@empresa.com.br"; // o email que irá receber os emails do site $html_msg = " <html> <head> <title>Contato - Site</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </head> <body> <font size="1" face="verdana"> <strong>EmpresaSA (Contato pelo Site)</strong> </font> <font face="verdana"> <p> <font size="1"> <strong>Nome:</strong> $nome <br> <strong>E-mail:</strong> $email <br> <strong>Assunto:</strong> $assunto <br> <strong>Mensagem:</strong> $msg </font> </p> </font> </body> </html> "; //MENSAGEM NO FORMATO HTML $headers = "MIME-Version: 1.0rn"; $headers .= "Content-type: text/html; charset=utf-8rn"; $headers .= "From: ". $remetente ."rn"; $headers .= "Reply-To: ". $remetente ."rn"; $headers .= "X-Priority: 1rn"; $headers .= "X-MSMail-Priority: Highrn"; $headers .= "X-Mailer: Just My Server"; if (imap_mail($destino, $assunto, ($html_msg), $headers)) { $data = array('enviado' => true ); echo json_encode($data); }else{ $data = array('enviado' => false); echo json_encode($data); } |
Na linha 10 você coloca o email que deseja receber as mensagens. Com isso poderemos testar, porém no servidor local não conseguiremos enviar email. Ao enviar os arquivos do projeto no servidor poderemos realizar este teste, e veremos a mensagem informando que o formulário foi enviado com sucesso. para saber como enviar os arquivos para o servidor veja este artigo. Até a próxima!