Conciliando Janelas Modais e ASP.NET Validators
Leonel Fraga de Oliveira 28/12/2008 21:21

Não resisti e postei um artigo antes do fim do ano (viciado é F*** hehe) :P

Você, que seguiu os tutoriais de como utilizar janelas modais com ASP.NET, de como resolver alguns pepinos que acontecem quando usamos o AJAX, agora tem um outro pepininho:

Tem vezes que as janelas modais não disparam quando utilizamos Validators na página!

Acompanhe o seguinte exemplo (design e code-behind parciais):

<div>
	<asp:UpdatePanel ID="upd1" runat="server">
	   <ContentTemplate>
		   <span>Box 1</span>< br />
		   <asp:TextBox runat="server" ID="tbx1"></asp:TextBox><br />
		   <asp:RequiredFieldValidator runat="server" ControlToValidate="tbx1" ID="Val1" Text="*" ErrorMessage="O Box 1 é obrigatório"></asp:RequiredFieldValidator>
		   <span>Box 2span>< br />
		   <asp:TextBox runat="server" ID="tbx2">asp:TextBox>< br />
		  <asp:RequiredFieldValidator runat="server" ControlToValidate="tbx1" ID="Val2" Text="*" ErrorMessage="O Box 2 é obrigatório"></asp:RequiredFieldValidator>
		 <br />
		 <asp:Button runat="server" ID="btnSalvar" Text="Salvar" />
		 <asp:Button runat="server" ID="btnAbreModal" Text="Abrir Modal" OnClick="btnAbreModal_Click" />
	  </ContentTemplate>
	</asp:UpdatePanel>
</div>
<div class="jqmWindow" id="Modal">
  <asp:UpdatePanel runat="server" ID="upd2">
	<ContentTemplate>
		  <span>Box 3</span><br />
		  <asp:TextBox runat="server" ID="tbx3"></asp:TextBox>< br />
		  <asp:RequiredFieldValidator runat="server" ControlToValidate="tbx3" ID="Val3" Text="*" ErrorMessage="O Box 3 é obrigatório"></asp:RequiredFieldValidator>
		  <span>Box 4</span>< br />
		  <asp:TextBox runat="server" ID="tbx4"></asp:TextBox>< br />
		  <asp:RequiredFieldValidator runat="server" ControlToValidate="tbx4" ID="Val4" Text="*" ErrorMessage="O Box 4 é obrigatório"></asp:RequiredFieldValidator>
		  <asp:Button runat="server" ID="btnProcessaModal" Text="Processar Modal" />
		  <asp:Button runat="server" ID="btnFecharModal" Text="Fechar Modal" OnClientClick="$Modal.jqmHide();return false;" />
	</ContentTemplate>
  </asp:UpdatePanel>
</div>
<script type="text/javascript">
	//Inicializa o Modal
	var $dvUplModal = $('#Modal').jqm({modal:true,toTop:true,trigger:false});               
</script>

Code-Behind:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
 
public partial class _Default System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // :-) 
    }
 
    protected void btnAbreModal_Click(object sender, EventArgs e)
    {
        string script = "$Modal.jqmShow();";
        /*Algum processamento pode ser feito aqui, por exemplo, alimentar componentes da janela modal antes de abrir:*/
        tbx3.Text = "Um texto qualquer...";
        ScriptManager.RegisterStartupScript(this, this.GetType(), "abremodal", script, true);
    }
}

Como podemos ver acima, o código JavaScript que aciona a janela modal é disparado através de um RegisterStartupScript, via code-behind.

Temos na página quatro RequiredFieldsValidators, sendo dois dentro e outros dois fora da janela modal, porém só iremos salvar os dados (e portanto, validá-los) quando clicarmos no botão "Salvar". O botão "Abrir Modal" somente abre a janela.

Então por que quando clico no botão "Abrir Modal" não acontece nada?

Bem, não é bem "nada" que acontece. Do jeito que se encontra no código acima, os Validators utilizam rotinas JavaScript para executar sua função; estas rotinas em JS são disparadas no momento do postback da página. E é ANTES do postback que a validação é executada: Se algum validator não tiver sua condição satisfeita, a página não é postada para o servidor e o texto do validator irá aparecer.

Então, como resolver este pepino?

Vou descrever algumas das soluções que poderemos aplicar neste caso:

 

Solução 1 - Utilizar um evento HTML para abrir o modal:

Nesta solução, caso utilizemos um botão (ou um ImageButton) ASP.NET para abrir o modal, colocamos a rotina JavaScript para abrir o modal dentro da propriedade OnClientClick (não, embora se chame OnClientClick, isto NÃO é um evento de servidor :-) ), acompanhado de uma instrução "return false" após o comando. Veja como ficou o design deste botão:

<asp:Button runat="server" ID="btnAbreModal" Text="Abrir Modal" OnClick="btnAbreModal_Click" OnClientClick="$Modal.jqmShow(); return false" />

Vendo o HTML que é renderizado no navegador, temos a nossa rotina no evento HTML onClick do nosso botão. Quando clicamos neste botão, o navegador irá executar esta rotina ANTES de postar a página.

Como temos um "return false;" no final do comando, o navegador entende que a rotina deverá ser abortada, e portanto, não é executado nenhum postback. Repare que o evento onClick que codificamos não é mais executado também, exatamente por causa deste "return false" que evita o postback. Utilizamos este artifício no botão "Fechar Modal", já que ele somente fechará o modal, sem processar nada.

Mas, outra coisa: Como a validação é executada via JavaScript, o que será executado primeiro? O nosso código do OnClientClick!

Mas se o evento onClick do code-behind tem um "return false" antes, lá no navegador, que evita o postback, como irei pré-processar algo no servidor quando clico no botão e não quero disparar o validator? Neste caso, devemos abandonar a idéia do OnClientClick, setar a propriedade CausesValidation do botão em questão para false partir para a Solução 2:

Solução 2 - Colocar a propriedade CausesValidation do Botão para False:

Como descrito no final da Solução 1, caso algum processamento tenha que ser feito antes de abrir a janela modal (por exemplo, alimentar alguns controles da janela modal), disparamos a abertura do modal via code-behind e não precise validar o restante da página, colocamos a propriedade CausesValidation do botão que irá executar este código em False.

Fazendo isso, ao clicarmos no botão os validators não serão disparados e nosso código será executado. Veja como ficou:

<asp:Button runat="server" ID="btnAbreModal" Text="Abrir Modal" OnClick="btnAbreModal_Click" CausesValidation="false" />

Mas... tem vezes que o JavaScript do validator ferra com as minhas rotinas JavaScript customizadas... o que fazer?

Eu geralmente não utilizo o validator nú e cru... eu geralmente faço o que vou indicar abaixo.

 

Solução 3 - Disparando os validators via code-behind:

Para disparar a validação via code-behind, ou seja, enquanto o servidor processa a página, devemos desabilitar o JavaScript dos validators. Isto é feito colocando-se a propriedade EnableClientScript de cada validator para false, e depois utilizar a propriedade IsValid dentro do code-behind. Veja o exemplo abaixo:

<asp:UpdatePanel ID="upd1" runat="server">
	<ContentTemplate>
		<span>Box 1</span>< br />
		<asp:TextBox runat="server" ID="tbx1"></asp:TextBox>< br />
		<asp:RequiredFieldValidator runat="server" EnableClientScript="false" ControlToValidate="tbx1" ID="Val1" Text="*" ErrorMessage="O Box 1 é obrigatório"></asp:RequiredFieldValidator>
		<span>Box 2</span>< br />
		<asp:TextBox runat="server" ID="tbx2">asp:TextBox>< br />
		<asp:RequiredFieldValidator runat="server" EnableClientScript="false" ControlToValidate="tbx1" ID="Val2" Text="*" ErrorMessage="O Box 2 é obrigatório"></asp:RequiredFieldValidator>
		<br />
		<asp:Button runat="server" ID="btnSalvar" Text="Salvar" />
		<asp:Button runat="server" ID="btnAbreModal" Text="Abrir Modal" OnClick="btnAbreModal_Click" />
	</ContentTemplate> 
</asp:UpdatePanel>

Com isso, poderemos checar cada validator individualmente, validando somente o que for necessário ao processamento, como no exemplo abaixo:

if (!Val1.IsValid)
{
	string mensagem = Val1.ErrorMessage;
}

Mas... e se eu tenho vários validators na página, terei que validá-los um a um?

Não! Há um jeito mais fácil: Se formos validar todos os validators de uma página, basta varrer a coleção Validators do objeto Page, e ir verificando a propriedade IsValid de dentro de um laço foreach, como no exemplo abaixo:

protected string Validar()
{
   string msg = "";

   foreach(IValidator v in this.Validators)
   {
	   if (!v.IsValid)
	   {
		   msg += "- " + v.ErrorMessage + " n";
	  }
  }

  return msg;
}

Para começo de conversa, crio uma função especificamente para fazer validação da página, e primeiramente inicializo uma variável que irá conter as mensagens de retorno. Dentro do laço foreach, se a propriedade IsValid do validator que está na iteração for false, concateno sua ErrorMessage dentro desta variável de retorno. A mesma coisa para validações em que não são utilizados validators; se a condição não for satisfeita, concateno com uma mensagem de erro.

Como temos vários tipos de validators na página (CompareValidator, RegularExpressionValidator, RequiredFieldValidator, entre outros), o laço foreach trabalha com a interface IValidator, que todos os validators implementam e possui as propriedades que iremos trabalhar: IsValid e ErrorMessage.

Então, o retorno da função fica: string vazia = válido; string com alguma coisa = inválido.

Beleza, isto faz varrer todos os validators da página... agora, se eu quiser validar os dados ANTES de disparar o modal, porém de muitos campos, em apenas uma determinada área da página, fora do modal obviamente, pois assim ele validará o modal também, e quiser utilizar uma rotina genérica como essa?

Para isso, temos que pensar um pouco no design da página. Agrupar os dados que serão validados dentro de um mesmo contêiner. Isso mesmo, vamos colocar a área que queremos validar dentro de um componente Panel, e varreremos a coleção Controls deste Panel da seguinte forma:

protected string ValidarArea()
{
   string msg = "";

   foreach (Control c in AreaAValidar1.Controls)
   {
	   if (c is IValidator)
	   {
		   if (!(c as IValidator).IsValid)
		  {
			  msg += "- " + (c as IValidator).ErrorMessage + " n";
		  }
	  }
  }

  return msg;
}


É isso aí! Um grande abraço a todos :-)

Leonel Fraga de Oliveira Leonel Fraga de Oliveira é formado em Processamento de Dados na Faculdade de Tecnologia de São Paulo (FATEC-SP - 2002) e anteriormente em Técnico em Eletrônica, pela ETE Professor Aprígio Gonzaga (lá em 1999).
Atualmente trabalha como Analista de Sistemas na Prefeitura Municipal de São Caetano do Sul - SP
Tem como hobbies DJing (também trabalha como DJ freelancer) e ciclismo, além da manutenção dos sites NeoMatrix Light e NeoMatrix Tech.
Gosta de música eletrônica, tecnologia, cinema (super fã de Jornada nas Estrelas), gastronomia e outras coisas mais.


Compartilhe nas redes sociais

   

Deixe seu comentário

comments powered by Disqus