Em
nosso último encontro demos um baile no problema de formatação de máscaras para valores especiais, aprendendo como interagir com o usuário no momento em que este está digitando um valor. Agora é chegada a hora de realizarmos a última parte da validação no lado cliente (via JavaScript) para que possamos enviar os dados para o servidor (PHP):
Validação de Formulários - Parte 04: A fonteira cliente/servidor
Como previamente definimos, é chegada a hora de testarmos os quatro primeiros caracteres do atributo 'name' de nossos campos de formulário de forma à verificar:
01. Se o campo é obrigatório (R) ou não (qualquer outro caractere) .
02. Se este campo aceitará apenas letras (L), apenas números (N) ou ambos (A).
03. A quantidade mínima de caracteres que o valor deste campo deve possuir - dois dígitos.
04. Se este campo possui um tipo especial de validação (validação de máscaras e/ou características especiais como o caso das datas - futuro ou passado - por exemplo)
Nosso código JavaScript ainda fará uma requisição AJAX de forma à consultar a data atual do servidor. Isto é feito porque a data da máquina do usuário não pode ser considerada confiável, já que ela é facilmente editável.
Veremos agora como ficará o código JavaScript completo da Validação - com exceção dos códigos de máscaras vistos em nosso último encontro. Comentaremos linha à linha como fizemos anteriormente, mas por se tratar de um código bem extenso deixei os comentários originais para auxiliar. Depois daremos uma primeira espiada em PHP para tratarmos do arquivo que responderá à nossa requisição AJAX.
1 // Var global
2 var fPreVal='';
3 var fVal;
4
5 var http_request = new Array();
6 var serverDate;
7
8 arrMonths = new Array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
9
10 // RegExs para campos alfa, numéricos e e-mail
11 rxLet = /\D/i;
12 rxNum = /\d/;
13 rxMail = /.+\@{1}.+/;
14
15 // Funções AJAX
16 function retrieveDate(requestURL, requestMethod, requestAsynchronous, requestData)
17 {
18 http_request = false;
19
20 if (window.XMLHttpRequest)
21 {
22 // Mozilla Firefox, Opera
23
24 http_request = new XMLHttpRequest();
25
26 if (http_request.overrideMimeType)
27 {
28 http_request.overrideMimeType('text/xml');
29 }
30 }
31 else if (window.ActiveXObject)
32 {
33 try
34 {
35 http_request = new ActiveXObject("Msxml2.XMLHTTP");
36
37 // Microsoft Internet Explorer 6+
38 }
39 catch(e)
40 {
41 try
42 {
43 http_request = new ActiveXObject("Microsoft.XMLHTTP");
44
45 // Microsoft Internet Explorer 5.5
46 }
47 catch(e2)
48 {
49 alert('Impossível criar a requisição.');
50 }
51 }
52 }
53
54 if (!http_request)
55 {
56 alert('Impossível criar a requisição.');
57 }
58
59 http_request.onreadystatechange = function ()
60 {
61 if (http_request.readyState == 4)
62 {
63 if (http_request.status == 200)
64 {
65 ret = http_request.responseXML;
66 dateTag = ret.getElementsByTagName('data');
67 serverDate = Date.parse(dateTag[0].firstChild.nodeValue);
68 }
69 }
70 }
71
72 http_request.open('GET', 'http://www.meuservidor.com.br/echoDate.php', true);
73 http_request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
74 http_request.send(null);
75 }
76
77 function validateForm()
78 {
79 // Var que representa o objeto DOM HTML form
80 f = document.forms[0];
81
82 // String que conterá a(s) mensagem(ens) de erro
83 errMsg = '';
84
85 // Contagem de erros
86 errCnt = 0;
87
88 // Campos que não devem ser checados
89 dontCheck = new Array('hidden', 'submit', 'reset', 'button', 'image', 'radio', 'select-one', 'select-multiple', 'checkbox');
90
91 for (e = 0; e < f.elements.length; e++)
92 {
93 skip = false;
94 fType = f.elements[e].type;
95
96 for (d = 0; d < dontCheck.length; d++)
97 {
98 if (fType == dontCheck[d])
99 {
100 skip = true;
101 }
102 }
103
104 if (fType && !skip)
105 {
106 fLabel = f.elements[e].name.substr(8);
107 fValida = f.elements[e].name.substr(0,8);
108
109 // Caractere de controle se é ou não campo requerido (R)
110 isReq = fValida.substr(0,1);
111
112 // Tipo do valor esperado: (L)etras, (N)úmeros ou (A)mbos
113 vTypeExpected = fValida.substr(1,1);
114
115 // Número mínimo de caracteres
116 rLen = Number(fValida.substr(2,2));
117
118 // Tipo de validação
119 vType = fValida.substr(4,4);
120
121 // Valor do campo
122 fVal = f.elements[e].value;
123
124 // Número de caracteres do valor digitado pelo usuário
125 fLen = fVal.length;
126
127 // Define as mensagens de erro
128 reqMsg = "> O campo " + fLabel + " é obrigatório.\n";
129 minMsg = "> O campo " + fLabel + " deve conter no mínimo " + rLen + " caractere(s).\n";
130 invMsg = "> O campo " + fLabel + " não contém um valor válido.\n";
131 alfMsg = "> O campo " + fLabel + " deve conter apenas letras.\n";
132 numMsg = "> O campo " + fLabel + " deve conter apenas números.\n";
133 pncMsg = "> As senhas digitadas não conferem.\n";
134
135 // Armazena a primeira senha digitada
136 if (e > 0 && f.elements[(e - 1)].type == 'password' && fType == 'password' && (fLen > 0 || f.elements[(e - 1)].value.length > 0))
137 {
138 fPreVal = f.elements[(e - 1)].value;
139 }
140
141 // Campo de preenchimento obrigatório
142 if (isReq == 'R')
143 {
144 if (fType == 'text' || fType == 'textarea' || fType == 'file' || fType == 'password')
145 {
146 if (fLen < rLen)
147 {
148 errMsg += reqMsg;
149 errCnt++;
150 }
151 }
152 }
153
154 if (fLen > 0)
155 {
156 // Faz a checagem de validade do valor
157 resCheckVal = checkVal(vType);
158
159 if (resCheckVal != 'Ok')
160 {
161 eval("errMsg += " + resCheckVal + "Msg;");
162 errCnt++;
163 }
164
165 // É campo somente letras
166 if (vTypeExpected == 'L')
167 {
168 if (fVal.match(rxNum))
169 {
170 errMsg += alfMsg;
171 errCnt++;
172 }
173 }
174 else if (vTypeExpected == 'N') // É campo somente números
175 {
176 if (fVal.match(rxLet))
177 {
178 errMsg += numMsg;
179 errCnt++;
180 }
181 }
182 }
183 }
184 }
185
186 if (errCnt > 0)
187 {
188 for (f = 0; f < document.forms[0].elements.length; f++)
189 {
190 document.forms[0].elements[f].disabled = false;
191 }
192
193 if (errCnt > 1)
194 {
195 strPlural = 's';
196 }
197 else
198 {
199 strPlural = '';
200 }
201
202 alert(errCnt + " erro" + strPlural + " encontrado" + strPlural +":\n\n" + errMsg);
203 }
204 else
205 {
206 document.forms[0].submit();
207
208 for (f = 0; f < document.forms[0].elements.length; f++)
209 {
210 document.forms[0].elements[f].disabled = true;
211 }
212 }
213 }
214
215 function checkVal(cType)
216 {
217 vPart1 = cType.substr(0,2);
218 vPart2 = cType.substr(2,2);
219
220 if (vPart1 == 'DT')
221 {
222 arrDate = fVal.split('/');
223 clientDate = Date.parse(arrMonths[(Number(arrDate[1]) - 1)] + ', ' + arrDate[0] + ' ' + arrDate[2]);
224
225 if (vPart2 == 'NS')
226 {
227 if (clientDate >= serverDate)
228 {
229 return 'inv';
230 }
231 }
232 else if (vPart2 == 'FT')
233 {
234 if (clientDate <= serverDate)
235 {
236 return 'inv';
237 }
238 }
239 }
240 else if (vPart1 == 'EM')
241 {
242 if (!fVal.match(rxMail))
243 {
244 return 'inv';
245 }
246 }
247 else if (vPart1 == 'PW')
248 {
249 if (fPreVal != '' && fVal != fPreVal)
250 {
251 return 'pnc';
252 }
253 }
254 else if (vPart1 == 'TE')
255 {
256 if (!fVal.match(/\d{4}\-\d{4}/))
257 {
258 return 'inv';
259 }
260 }
261
262 return 'Ok';
263 }
|
Linhas 1 à 8: Definimos as variáveis globais de nosso código javascript. Elas precisam ser globais porque serão utilizadas por mais de uma função. Dentre estas linhas destacaremos duas:
Linha 5: Definimos uma variável que será responsável por guardar nossa requisição HTTP e inicializamos ela propositalmente como false.
Linha 8: Criamos um array para armazenar os meses do ano, no formato "3 primeiras letras do mês". Observem que este array contém valores em inglês e isso é proposital. Falaremos mais disso a seguir.
Linhas 11 - 13: Criamos expressões regulares para nossas validações especiais. A primeira procura por letras (D, maiúsculo), não importando se estão em caixa alta ou baixa (a letra i no final). A segunda por números (d, minúsculo). A terceira por um "endereço de e-mail válido", ou seja: Quaisquer caracteres, uma e apenas uma arroba e quaisquer caracteres.
Linhas 16 - 94: Aqui temos nossa função AJAX. Para maiores informações sobre AJAX postarei à seguir meu artigo "RetrieveData, uma função AJAX genérica", pois trataremos apenas de uma parte desta:
Linhas 59 - 70: Definimos a função de tratamento do retorno de nosso script PHP, que veremos mais abaixo. Basicamente o que fazemos é pegar o conteúdo XML retornado por este script, que se encontra cercado por uma tag "data". Utilizamos o método parse do objeto Date do JavaScript à fim de convertermos a data retornada pelo script no número de milisegundos desde a meia-noite de 1º de Janeiro de 1970 - o que chamamos de timestamp - e armazenamos este valor na variável global serverDate.
Linhas 77 - 213: Aqui está a função de validação do formulário, que comentaremos em maiores detalhes à seguir:
Linha 80: Para economizar caracteres e prevenir possíveis erros de digitação criamos uma variável f que guardará o objeto DOM HTML de nosso formulário.
Linha 83: Inicializamos como vazia uma variável que armazenará as mensagens de erro de validação, caso existam.
Linha 86: Inicializamos como 0 (zero) uma variável contadora que armazenará a quantidade de mensagens de erro de validação, caso existam.
Linha 89: Criamos um array contendo os tipos de elementos de formulário que nossa função NÃO verificará.
Linha 91: Iniciamos um loop no array elements do DOM HTML. Este array pertence ao formulário (variável f) e contém todos os elementos presentes nele.
Linha 93: Definimos uma variável booleana que informará ao loop se o elemento deve ser ignorado (true) ou validado (false).
Linha 94: Armazenamos o tipo do elemento em questão (propriedade 'type' do objeto DOM do elemento) em uma variável.
Linhas 96 - 102: Lemos o array de tipos que não serão validados para verificarmos se o tipo do elemento em questão está presente. Em caso positivo, definimos a variável booleana como true (pular validação).
Linha 104: Verificamos se o tipo do elemento está definido e se a variável boolena é falsa (não ignorar o elemento, mas validá-lo).
Linha 106: Armazenamos o rótulo (nome para exibição) do elemento em uma variável. Observem que para isto pegamos uma substring do atributo 'name' que iniciará no nono caractere (após os 8 caracteres que definimos como código para informar os tipos de validação).
Linha 107: Fazemos o inverso: pegamos uma substring dos primeiros 8 caracteres do atributo 'name' do elemento à fim de capturarmos o código de validação.
Linha 110: Armazenamos em uma variável o primeiro caractere deste código: Se é campo obrigatório.
Linha 113: Armazenamos em uma variável o segundo caractere: O tipo de dado esperado.
Linha 116: Armazenamos em uma variável o terceiro e o quarto caractere: O número mínimo de caracteres.
Linha 119: Armazenamos em uma variável os quatro caracteres restantes: O tipo de validação especial.
Linha 122: Armazenamos em uma variável o valor digitado pelo usuário.
Linha 125: Armazenamos em uma variável o comprimento (número de caracteres) deste valor.
Linhas 128 - 133: Definimos os tipos de mensagens de erro que poderão ser retornados para cada validação de campo. Observem que as duas primeiras são excludentes: usaremos a primeira (apenas informar que o campo é requerido) ou a segunda ("abriremos o jogo" e informaremos exatamente quantos caracteres estamos esperando encontrar).
Linha 136: Testamos se estamos verificando pelo menos o segundo elemento, se o elemento anterior à este era do tipo password, se o atual também é do tipo password e se um dos dois possui algum valor digitado. Em caso positivo:
Linha 138: Guardamos o valor digitado no elemento anterior (primeira senha).
Linha 142: O campo é (R)equerido? Em caso positivo:
Linhas 144 - 151: Caso ele seja do tipo text, textarea, file ou password, testamos se o comprimento do valor digitado pelo usuário é menor do que o esperado. Em caso positivo, alimentamos nossa variável de mensagens de erro e incrementamos a contagem de erros.
Linha 154: Aqui se encontra um ponto crucial e frequentemente ignorado: o fato de um campo ser obrigatório ou não e o fato de seu valor ser válido são coisas absolutamente diferentes. É por isso que esta linha testa se algum valor foi digitado, checando o comprimento deste, para que, em caso positivo, possamos:
Linha 157: Chamar uma função que fará a validação deste valor. Não se preocupem, já falaremos dela.
Linha 159: Se o resultado desta função for algo diferente de "Ok":
Linha 161: Usamos um comando eval de forma a unir este resultado, que será "inv" ou "pnc" com a string "Msg" de forma à invocarmos a mensagem de erro padrão e adicioná-la em nossa variável de mensagens.
Linhas 166 - 181: Aqui testamos pelo tipo de valor esperado. Caso seja apenas (L)etras, usamos nossa expressão regular para testar pela presença de caracteres numéricos. Caso seja apenas (N)úmeros, através de nossa outra expressão regular testamos pela presença de caracteres não-numéricos.
Linhas 186: Finalmente faremos a checagem final: se o número de erros cometidos pelo usuário é maior do que 0 (zero). Em caso positivo:
Linhas 188 - 191: Percorremos todos os elementos do formulário definindo a propriedade 'disabled' de cada um para false. Será melhor explicarmos o que esta propriedade faz mais adiante.
Linhas 193 - 200: Testamos se o usuário cometeu mais de um erro. Em caso positivo armazenaremos um 's' à uma variável de forma à usarmos o português correto e colocarmos os termos no plural :)
Linha 202: Exibimos a mensagem completa de erro para nosso usuário.
Linha 204: Caso o número de erros não seja maior do que zero:
Linha 206: Acionamos o método 'submit' que é o responsável , como vocês já devem ter adivinhado, por enviar o formulário.
Linhas 208 - 211: Percorremos todos os elementos do formulário definindo a propriedade 'disabled' de cada um para true. Esta propriedade faz com que os elementos de um formulário sejam desabilitados (disabled), ou seja, eles não podem mais sofrer qualquer tipo de interação por parte do usuário, como uma mudança de valor por exemplo.
Linhas 215 - 263: Esta é nossa função de validação, aquela que dirá se os valores digitados em cada campo correspondem com as expectativas. Vejamos mais detalhes:
Linhas 217 e 218: "Quebramos" o tipo de validação em dois pares de dois caracteres.
Linha 220: Se os dois primeiros caracteres forem 'DT' significa que estamos tratando de uma data.
Linha 222: "Quebramos" a data em dia com dois caracteres, mês com dois caracteres e ano com quatro caracteres.
Linha 223: Reconstruímos a data no formato: mês com 3 iniciais, vírgula, espaço em branco, dia com dois dígitos, espaço em branco e ano com quatro dígitos (exemplo: Sep, 29 2006) e então usamos o método parse para convertermos isto em um timestamp (número de milisegundos desde a meia-noite de 1º de janeiro de 1970) e armazenamos isto em uma variável.
Linha 225: Se os dois últimos caracteres da validação forem 'NS', é uma data de nascimento, sendo obrigatoriamente uma data no passado. É aí que:
Linhas 227 - 230: Testamos se o timestamp do cliente é maior (futuro) ou igual (presente) ao do servidor. Em caso positivo, retornamos os 3 caracteres que, concatenados com a string 'Msg' dentro de nosso eval resultarão na mensagem de valor inválido.
Linhas 232 - 238: Fazemos exatamente a mesma coisa, apenas testando pela não validade de uma data no passado, visto que os dois últimos caracteres de nossa validação são 'FT', significando uma data futura.
Linhas 240 - 246: Caso os dois primeiros caracteres de nossa validação sejam 'EM', estamos tratando de um e-mail. Sendo assim, usaremos nossa expressão regular para validá-lo.
Linhas 247 - 253: Caso os dois primeiros caracteres de nossa validação sejam 'PW', estamos tratando da senha. Caso a primeira senha (variável global que usamos anteriormente) seja diferente da segunda, retornamos os caracteres que dispararão a mensagem de "senhas não conferem".
Linhas 254 - 260: Caso os dois primeiros caracteres de nossa validação sejam 'TE', estamos tratando de um telefone. Usamos uma outra expressão regular para validarmos seu valor.
Linha 262: Se nossa função de validação chegou até aqui é porque o valor estava correto e é hora de retornar a string 'Ok'.
Agora vejamos o arquivo "echoDate.php", responsável por retornar a data do servidor para nossa requisição AJAX:
1 <?php
2 $dt = '<?xml version="1.0" encoding="iso-8859-1"?>';
3 $dt .= '<data>';
4 $dt .= date('M d, Y');
5 $dt .= '</data>';
6 header('Content-type: text/xml; charset=iso-8859-1');
7 echo $dt;
8 ?>
|
Linha 2: Criamos e inicializamos nossa variável de retorno com a tag de declaração XML.
Linha 3: Concatenamos à esta variável nossa tag root <data>
Linha 4: Concatenamos a data no formato previamente mencionado em nosso código JavaScript
Linha 5: Fechamos nossa variável, que nada mais é do que a representação fiel de um documento XML bem formado.
Linha 6: Enviamos um cabeçalho HTTP de forma à definirmos que o conteúdo que está sendo retornado é XML.
Linha 7: Damos um echo neste conteúdo, que será, por sua vez, capturado por nossa função AJAX.
E assim concluímos a parte de validação no lado cliente. Para que nosso código funcione corretamente, duas coisas devem ser observadas:
01. Nossa função AJAX deve ser chamada no gatilho onLoad do documento HTML, como no exemplo:
<body onLoad="retrieveData();">
02. Não devemos utilizar uma tag do tipo submit para criar o botão de envio, mas uma tag do tipo button e associar nossa função de validação através do gatilho onClick:
<input type="button" value="Enviar" onClick="validateForm();" />
Até a próxima!