A manipulação do HTML de forma dinâmica no cliente, afim de prover maior interatividade entre o usuário e a aplicação e criar animações, é uma prática muito utilizada há alguns anos. São muitos os sites, portais e aplicações web em que podemos movimentar, esconder, trocar fontes e cores de elementos diversos. Com a popularização do AJAX essa prática tem crescido ainda mais. O foco deste artigo é trazer o DHTML às aplicações ASP.NET e entender como fazer a comunicação entre os dados gerados dinamicamente no cliente e o servidor.

O que é DHTML?

DHTML é a sigla de Dynamic HTML e não é uma tecnologia por si só, mas a utilização de um grupo de tecnologias, como HTML, linguagem de script (como JavaScript ou VBScript), CSS (Cascading Style Sheets) e o modelo de objetos do HTML (DOM - Document Object Model). Com DHTML pode-se modificar a visualização e o comportamento de uma página após ela ter sido carregada no navegador cliente, dando a impressão de interatividade e dinamismo. Todo o código manipulado fica no cliente e não é enviado ao servidor até que exista um postback.

A questão toda reside em como informar o servidor das mudanças feitas no cliente e como persistir esses dados. Suponha que em determinada aplicação o usuário possa trocar dinamicamente a cor de fundo de algum elemento, com uso de DHTML. No primeiro postback essa informação é perdida, já que o servidor não tem conhecimento da troca de cor que ocorreu apenas no cliente. Como resolver esse problema? Obviamente precisamos criar um canal de comunicação entre os elementos para os quais queremos persistir as mudanças, e o servidor que precisa ser informado de tal mudança.

O cenário do projeto

Para fazer a prova de conceito que integrará DHTML com comunicação das mudanças dinâmicas no cliente ao servidor, criaremos um controle web que terá três comportamentos dinâmicos disponíveis: será possível ser arrastado com drag and drop, poderá ter sua cor de fundo alterada através da mudança dos dados de um Dropdownlist e permitirá a adição dinâmica de itens ao Dropdownlist.

A solução será composta de dois projetos, um do tipo Web Site e outro do tipo Class Library. O projeto que fará a prova de conceito é o projeto do tipo Class Library que conterá uma classe que herdará de WebControl chamada DHTMLPanel. A criação desse projeto é baseada no Template Web Control Library para facilitar a criação de referências e arquivos de exemplo. O projeto Web Site contém simplesmente uma página web (default.aspx) que hospedará o controle, onde seu objetivo é simplesmente testar o controle.

Criando o projeto de controle

Inicie o Visual Studio 2005 e na página inicial clique em Create Project. Em Project Types selecione Visual Basic>Windows e escolha o item Web Control Library. Digite o nome DHTML.Library para o projeto e marque a opção Create directory for solution. Para o nome da solução digite apenas “DHTML”. Clique em OK.

Note que o Visual Studio adiciona um arquivo com controle customizado de exemplo, com o nome WebCustomControl1. Não utilizaremos esse arquivo, portanto podemos excluí-lo do projeto. Adicionaremos agora o arquivo que conterá nosso controle customizado. Vá ao menu Project>Add New Item e escolha WebCustomControl. Nomeie-o para “DHTMLPanel.vb” e clique em Add.

Exclua a propriedade Text e o método RenderContents do arquivo, já que não usaremos nenhum dos dois. O controle deverá também implementar duas interfaces importantes: IPostBackDataHandler e InamingContainer. Ao implementar as interfaces, os métodos serão criados automaticamente. Será também necessário adicionar uma declaração de Imports ao início do arquivo para o namespace System.Drawing. O código da classe gerado deve estar semelhante ao da Listagem 1.

Listagem 1. Código Inicial do controle.

Imports System

Imports System.Collections.Generic

Imports System.ComponentModel

Imports System.Text

Imports System.Web

Imports System.Web.UI

Imports System.Web.UI.WebControls

Imports System.Drawing

 

False), PersistChildren(True), _

  DefaultEvent("PosicaoAlterada"), _

  DefaultProperty("Posicao"), _

  ToolboxData("<{0}:DHTMLPanel runat=""server"">")> _

Public Class DHTMLPanel

    Inherits WebControl

    Implements IPostBackDataHandler, INamingContainer

#Region " Implementação de IPostBackDataHandler "

    Protected Function LoadPostData( _

      ByVal postDataKey As String, _

      ByVal postCollection As System.Collections. _

      Specialized.NameValueCollection) As Boolean _

      Implements System.Web.UI.IPostBackDataHandler. _

      LoadPostData

    End Function

    Protected Sub RaisePostDataChangedEvent() _

      Implements System.Web.UI.IPostBackDataHandler. _

      RaisePostDataChangedEvent

    End Sub

#End Region

End Class

Nesse momento, o editor do Visual Studio estará indicando que o evento PosicaoAlterada não existe. Vamos resolver esse problema e criar o código desse evento. O controle lançará dois eventos, o já mencionado PosicaoAlterada e também CorAlterada.

O primeiro sinalizará que houve uma mudança de posição, através de uma operação de drag and drop e o outro indicará uma mudança de cor de fundo no controle. O código a ser implementado seguirá as melhores práticas, com um método nomeado On mais o nome do evento, de forma a facilitar futuras heranças sobre o código gerado. O código deverá ficar como o da Listagem 2.

Listagem 2. Declaração de eventos do controle.

#Region " Eventos "

    Protected Sub OnCorAlterada(ByVal e As EventArgs)

        RaiseEvent CorAlterada(Me, e)

    End Sub

    Public Event CorAlterada As EventHandler

    Protected Sub OnPosicaoAlterada( _

      ByVal e As EventArgs)

        RaiseEvent PosicaoAlterada(Me, e)

    End Sub

    Public Event PosicaoAlterada As EventHandler

#End Region

Vamos então expor a propriedade padrão do controle, chamada Posicao, que conterá a posição do controle após a operação de drag and drop. A implementação é simples, conforme mostrado na Listagem 3, com utilização do viewstate para armazenar o dado, e exposta como uma interface do tipo System.Drawing.Point. A posição inicial é assumida sempre como 0,0, ou seja, no ponto inicial, sem drag and drop.

Listagem 3. Declaração da propriedade Posicao.

#Region " Propriedades "

    Public Property Posicao() As Point

        Get

            If ViewState("Posicao") Is Nothing Then

                ViewState("Posicao") = New Point(0, 0)

            End If

            Return DirectCast(ViewState("Posicao"), _

              Point)

        End Get

        Set(ByVal value As Point)

            ViewState("Posicao") = value

        End Set

    End Property

#End Region

Existem três variáveis internas apenas. Duas são booleanas, indicando se um evento deve ser lançado e inicializadas como False, a terceira diz respeito a um Dropdownlist filho que armazenará as cores de fundo do controle que é referenciado em funções diferentes do controle. O código a seguir, mostra como deve ficar o código.


#Region " Variáveis internas "

    Private _ddlCores As DropDownList

    Private _blnCorAlterada As Boolean = False

    Private _blnPosicaoAlterada As Boolean = False

#End Region

Lidando com a inicialização do controle

Implementaremos os métodos na ordem em que eles são chamados pelo engine do ASP.NET. O primeiro método a ser implementado é o que rodará na inicialização da página, o OnInit, conforme a Listagem 4. O método é sobrescrito (com Overrides) da classe base (WebControl). Chama duas funções: ConfiguraScripts, que faz a criação dos scripts que darão o comportamento dinâmico do HTML (disponível na Listagem 6), e AdicionaControles, que fará a criação dos controles filhos da página (disponível na Listagem 7).

Listagem 4. Método OnInit.

Protected Overrides Sub OnInit(

  ByVal e As System.EventArgs)

   MyBase.OnInit(e)

   ConfiguraScripts()

   AdicionaControles()

End Sub

O método ConfiguraScripts é responsável pela criação do código de script que rodará no cliente e dará o comportamento DHTML. Para criar um recurso a ser utilizado para gerar o script, clique duas vezes em My Project para abrir as propriedades do projeto. Clique então na aba Resources e clique na seta à direita de Add Resource e selecione Add New Text File. Digite “DragDrop” e clique em Add.

Isso criará o arquivo DragDrop.txt na pasta Resources que foi criada nesse momento, o abrirá para edição, e o adicionará automaticamente aos recursos do projeto. Com essa adição é criada uma chamada fortemente tipada para acessar esse arquivo, utilizando-se a palavra-chave My, da seguinte forma: My.Resources.DragDrop. Essa chamada automaticamente entrega o conteúdo do arquivo DragDrop.txt na forma de uma string.

Para facilitar a digitação e permitir a coloração do texto pelo editor do Visual Studio 2005, troque a extensão do arquivo de TXT para JS, remova a referência anterior ao arquivo, e adicione o arquivo renomeado arrastando e soltando-o sobre a janela de Resources ainda aberta. O código do arquivo está disposto na Listagem 5. Sua responsabilidade é fazer uma operação simples de drap & drop em um elemento HTML com classe de estilo configurada para o nome drag.

Listagem 5. Script para realização da operação de Drag & Drop (DragDrop.js).

var DragDrop =

{

  ultimoXDoMouse: 0,

  ultimoYDoMouse: 0,

  ultimoXDoElemento: null,

  ultimoYDoElemento: null,

  objetoParaArrastar: null,

  podeFazerDrag: false,

  inicia:function()

  {

     document.onmousedown = this.drag;

     document.onmouseup = function()

     {this.podeFazerDrag = false};

  },

  drag:function(e)

  {

     var args = window.event ? window.event : e;

     this.objetoParaArrastar = window.event ? event.srcElement : e.target;

     if (this.objetoParaArrastar.className == 'drag')

     {

       this.podeFazerDrag = true;

       if (isNaN(parseInt(

         this.objetoParaArrastar.style.left)))

         {this.objetoParaArrastar.style.left=0};

       if (isNaN(parseInt(

         this.objetoParaArrastar.style.top)))

         {this.objetoParaArrastar.style.top=0};

       this.ultimoXDoElemento =

         parseInt(this.objetoParaArrastar.style.left);

       this.ultimoYDoElemento =   

         parseInt(this.objetoParaArrastar.style.top);

       this.ultimoXDoMouse = args.clientX;

       this.ultimoYDoMouse = args.clientY;

       if (args.preventDefault)

         args.preventDefault();

       document.onmousemove = DragDrop.moveit;

     }

  },

  moveit:function(e)

  {

    var args=window.event? window.event : e

    if (this.podeFazerDrag == true)

    {

      var XAtualDoMouse = args.clientX;

      var YAtualDoMouse = args.clientY;

      if (Escreve)

      {

        Escreve

      (

      'id: ' + this.objetoParaArrastar.id + '
' +

        'ultimoXDoElemento: ' + this.ultimoXDoElemento +

        ' XAtualDoMouse: ' + XAtualDoMouse +

        ' ultimoXDoMouse: ' + this.ultimoXDoMouse +

        '
' + 'ultimoYDoElemento: ' +

        this.ultimoYDoElemento + ' YAtualDoMouse: ' +

        YAtualDoMouse + ' ultimoYDoMouse: ' +

        this.ultimoYDoMouse + '
'

      );

    }

    var novoX = this.ultimoXDoElemento +

      XAtualDoMouse - this.ultimoXDoMouse;

    var novoY = this.ultimoYDoElemento +

      YAtualDoMouse - this.ultimoYDoMouse;

    this.objetoParaArrastar.style.left = novoX + 'px';

    this.objetoParaArrastar.style.top = novoY + 'px';

    hidden.value = novoX + '|' + novoY;

    return false;

  }

  }

  }

  //descomentar para depurar

  //debugger;

  DragDrop.inicia();

  function Escreve(strTexto)

  {

    var spnOut = document.getElementById('spnOut');

    if (spnOut)

      spnOut.innerHTML = strTexto;

  }
  

Veja na Listagem 6 como o código do método ConfiguraScripts fica claro e limpo após a criação do arquivo DragDrop.js.

Função responsável por criar todo o código de script que vai rodar no cliente.

Listagem 6. Método ConfiguraScripts.

Private Sub ConfiguraScripts()

  If Not Page.ClientScript.IsStartupScriptRegistered( _

     "divdragdrop") Then

Insere os scripts de drag e drop e de obtenção do ID do elemento hidden.


  Dim strHidden As String = _

  "var hidden = document.getElementById('" & _

    Me.ClientID & "');" & Environment.NewLine

  Dim strDragDrop As String = My.Resources.DragDrop

  Page.ClientScript.RegisterStartupScript( _

    Me.GetType(), "divdragdrop", strDragDrop & _

    strHidden, True)

  End If

End Sub

Os dados do arquivo são recuperados para a variável strDragDrop e então registrados na página. O motivo da utilização da chamada RegisterStartupScript e não de RegisterClientScriptBlock é que a segunda opção coloca o código de script logo ao início da página, o que é um problema uma vez que o script referencia elementos do HTML que ainda não foram lidos pelo navegador (linguagens de script em geral não consideram código ainda não lido pelo interpretador).

RegisterStartupScript coloca o código ao final da página, quando todos os elementos já foram lidos e estão presente no modelo de objetos (DOM) do navegador no momento em que o script é lido.

O método AdicionaControles, disponível na Listagem 7, é responsável pela criação dos controles filhos. Os controles filhos relevantes são um Dropdownlist com as cores disponíveis para mudança das cores de fundo, um TextBox que aceitará uma nova cor no Dropdownlist e seleção de cor de fundo, e um botão cliente que realiza a ação de colocar a cor digitada no TextBox no Dropdownlist via script no cliente.

O Dropdownlist possui o atributo onchange para que ao fazer uma mudança no seu item selecionado seja feita também uma mudança na cor de fundo do controle que é encapsulado por uma tag div nomeada a partir do ClientID do controle mais o texto _div. O botão possui código atrelado ao evento onclick que faz a criação de um novo elemento Option no Select gerado pelo Dropdownlist, a partir do dado digitados no Textbox.

Cria os controles filhos. Como ele é chamado durante a inicialização do controle, e o viewstate ainda não foi restaurado, ainda não temos acesso às propriedades do controle. O código não tem seu fluxo normal desviado em nenhum momento e sempre roda da mesma forma, afim de garantir que a estrutura de controles filhos será sempre a mesma entre postbacks.

Listagem 7. Método AdicionaControles. Criando o texto "Cores":

Private Sub AdicionaControles()

  Dim litCores As New Literal()

  litCores.Text = Environment.NewLine & "Cores: " & _

    Environment.NewLine

  Me.Controls.AddAt(0, litCores)

Criando o Dropdownlist: O Dropdownlist é do tipo DropDownListDinamico para evitar uma ArgumentException que acontece se um Dropdownlist do .NET Framework recebe um novo item via script no cliente.


  _ddlCores = New DropDownListDinamico

  _ddlCores.ID = "ddlCores"

  _ddlCores.Items.Add(New ListItem("Azul", "blue"))

  _ddlCores.Items.Add(New ListItem("Verde", "green"))

  _ddlCores.Items.Add(New ListItem("Vermelho", "red"))

  _ddlCores.Items.Add(New ListItem("Roxo", "purple"))

  _ddlCores.Items.Add(New ListItem("Sem cores", ""))

  _ddlCores.Attributes.Add("onchange", _

    "document.getElementById('" & Me.ClientID & _  

    "_div').style.backgroundColor=options[selectedIndex].value;")

  _ddlCores.SelectedIndex = _ddlCores.Items.Count - 1

  Controls.AddAt(1, _ddlCores)

Criando algum texto descritivo:


  Dim litAdicioneCor As New Literal

  litAdicioneCor.Text = Environment.NewLine & _

Adicione uma nova cor:


& _

    Environment.NewLine

  Controls.AddAt(2, litAdicioneCor)

Criando o TextBox que receberá novas cores:


  Dim txtNovaCor As New TextBox()

  txtNovaCor.ID = "txtNovaCor"

  Controls.AddAt(3, txtNovaCor)

Criando o botão que adiciona novas cores:


  Dim butAdicionaCor As New HtmlControls.HtmlButton

  butAdicionaCor.Attributes.Add("onclick", _

    "var sel = document.getElementById('" & _

    Me.ClientID & "$ddlCores'); sel.options[sel.length] = new Option( document.getElementById('" & _

    txtNovaCor.ClientID & "').value, document.getElementById('" & _

    txtNovaCor.ClientID & "').value);sel.options[sel.length-1].selected = true;sel.onchange();")

  butAdicionaCor.InnerHtml = "Adicione!"

  butAdicionaCor.ID = "butAdicionaCor"

  Controls.AddAt(4, butAdicionaCor)

Criando a segunda quebra de linha:


  Dim litQuebraLinha As New Literal

  litQuebraLinha.Text = Environment.NewLine & _
 
    & Environment.NewLine

  Controls.AddAt(5, litQuebraLinha)

End Sub

Note que o Dropdownlist chamado através da variável _ddlCores é criado como DropDownListDinamico. Esse tipo é simplesmente um tipo interno que deve ser declarado no controle (Listagem 8).

Essa classe existe apenas para evitar a ArgumentException que ocorre caso utilizemos umDropDownList normal. Para ver a ArgumentException descomente o atributo SupportsEventValidation.

Listagem 8. Classe DropDownListDinamico.

#Region "Controle DropDownListDinamico"

   Private Class DropDownListDinamico

     Inherits DropDownList

   End Class

#End Region

O motivo da criação desse controle interno ao controle principal (ele deve ser criado dentro do código da classe DHTMLPanel) não é a adição de nenhuma funcionalidade, mas a retirada de uma, pois o Dropdownlist padrão, possui o atributo SupportsEventValidation que valida os dados recebidos pelo controle no momento do postback. Através de uma chamada a Page.ClientScript.RegisterForEventValidation o Dropdownlist padrão avisa à infraestrutura do ASP.NET quais os valores válidos para um postback.

Como o controle DHTMLPanel cria dinamicamente no cliente novos itens no Dropdownlist com código script, uma ArgumentException é lançada quando é feito o postback uma vez que o ASP.NET não reconhece os valores sendo enviados, e assume que a página foi hackeada. Para evitar isso, foi criado um controle interno (DropDownListDinamico) que herda todas as funcionalidades de Dropdownlist mas não implementa suas funcionalidades de validação de itens.

O botão possui o texto “Adicione!” e faz a criação de um novo elemento também através de uma chamada de script cliente. O código de script é atrelado ao atributo onclick do botão. Na Listagem 9 pode-se ver um exemplo do código gerado pelo controle, assumindo que a instância do controle na página tenha o nome DHTMLPanel1. Isso conclui a criação dos controles filhos e inicialização do controle.

Listagem 9. Exemplo do código HTML gerado pelo método AdicionaControles.

<select name="DHTMLPanel1$ddlCores"

id="DHTMLPanel1_ddlCores" onchange="document.getElementById('DHTMLPanel1_div').style.backgroundColor=options[selectedIndex].value;">

  <option value="blue">Azuloption>

  <option value="green">Verdeoption>

  <option value="red">Vermelhooption>

  <option value="purple">Roxooption>

  <option selected="selected" value="">Sem coresoption>

select>

<br />Adicione uma nova cor:<br />

<input name="DHTMLPanel1$txtNovaCor" type="text" id="DHTMLPanel1_txtNovaCor" /><button

id="DHTMLPanel1_butAdicionaCor" onclick="var sel = document.getElementById('DHTMLPanel1$ddlCores'); sel.options[sel.length] = new Option( document.getElementById('DHTMLPanel1_txtNovaCor').value, document.getElementById('DHTMLPanel1_txtNovaCor').value);sel.options[sel.length-1].selected = true;sel.onchange();">Adicione!button>

<br /> <br />

Recuperação de dados do postback

Em seguida veremos a implementação da interface IPostBackDataHandler. O primeiro método e mais importante é o LoadPostData, disponível na Listagem 10. Ele é responsável pela recuperação dos dados enviados via stream HTTP e colocação dos dados nas propriedades corretas do controle.

Nota: O código da Listagem 10 deve ser implementado no Region Implementação de IPostBackDataHandler.

Esse método é responsável por carregar os dados enviados via postback.

O conteúdo do postDataKey será sempre uma string com o ClientID desse controle. postCollection é a variável que conterá toda a coleção de itens enviados via HTTP pela página, criados através de tags select, input e botões. Retorna True se houve alteração de estado e False se não houver. Dependendo do retorno o ASP.NET chama o método RaisePostDataChangedEvent.

Neste exemplo, esSe é o segundo método a rodar, se houver postback, senão não roda. Se rodar, roda antes dos métodos de renderização. O método só será chamado se algum dos elementos da coleção de post recebida, tiver o mesmo ClientID do controle. No caso deste controle, existe sempre um elemento hidden com o ClientID dele (veja o código do RenderBeginTag). Isso garante que esse método será chamado em todos os postbacks da página.

Listagem 10. Implementação do método LoadPostData.

Protected Function LoadPostData( _

  ByVal postDataKey As String, _

  ByVal postCollection As _

  System.Collections.Specialized.NameValueCollection) _

    As Boolean _

    Implements System.Web.UI.IPostBackDataHandler.LoadPostData

Inicia-se com a verificação da cor, mas poderia iniciar com a verificação de posição, não faz diferença: o Dropdownlist _ddlCores sempre envia algum dado na stream HTTP, e o nome assumido é o da tag select criada por ele. Como esse controle implementa a interface INamingContainer, todos os controles filhos são nomeados a partir dele e utilizam o caracter $ como separador, dessa forma, os dados selecionados com o Dropdownlist _ddlCores são enviados sob o nome resultado dessa função: Me.ClientID & "$" & _ddlCores.ID.


Dim strCor As String = postCollection(Me.ClientID & _

  "$" & _ddlCores.ID)

  strCor = strCor.ToLower()

  Dim corNova As Color

  If strCor = String.Empty Then

    corNova = Color.Empty

  Else

A classe ColorTranslator faz a conversão entre a cor HTML e a cor do GDI+.


corNova = ColorTranslator.FromHtml(strCor)

  End If

  If Me.BackColor <> corNova Then

Se houve mudança na cor, a nova cor é configurada e a variável booleana interna _blnCorAlterada é configurada para lançar o evento de mudança de cor no momento devido (na chamada a RaisePostDataChangedEvent).


Me.BackColor = corNova

      _blnCorAlterada = True

  End If

Verifica-se se o Dropdownlist _ddlCores possui a cor selecionada entre seus itens de opção, e se não possui um novo elemento é criado, adicionado e selecionado.


Dim li As ListItem = _

    _ddlCores.Items.FindByValue( _

    ColorTranslator.ToHtml(Me.BackColor).ToLower())

  If li Is Nothing Then

    Dim liNovaCor As New ListItem( _

      ColorTranslator.ToHtml(Me.BackColor).ToLower())

    _ddlCores.Items.Add(liNovaCor)

    _ddlCores.SelectedIndex = _

      _ddlCores.Items.IndexOf(liNovaCor)

  End If

Inicia-se nesse ponto a verificação da posição enviada através do elemento hidden.O conteúdo do postDataKey é a chave para o conteúdo do elemento hidden que armazena a posição do controle no cliente após alguma movimentação com drag e drop. A recuperação é feita com uma chamada ao item específico do dicionário postCollection da seguinte forma: postCollection(postDataKey).


Dim strRetornoHidden As String = _

    postCollection(postDataKey)

  If strRetornoHidden IsNot Nothing AndAlso _

    strRetornoHidden <> String.Empty Then

Após a recuperação é feito parse dos dados com uma chamada a Split, que decompõe a string de valores do campo hidden em strings com os valores de posição. O separador "|" é utilizado pelo código de script no cliente (veja o código de DragDrop.js).


Dim strValoresRetornados As String() = _

      strRetornoHidden.Split("|"c)

    Dim strLeft As String = strValoresRetornados(0)

    Dim strTop As String = strValoresRetornados(1)

    If Me.Posicao.X.ToString() <> strLeft OrElse _

      Me.Posicao.Y.ToString() <> strTop Then

Se a posição foi alterada, a nova posição é configurada e a variável booleana interna _blnPosicaoAlterada é alterada para lançar o evento de mudança de posição no momento devido (na chamada a RaisePostDataChangedEvent).


_blnPosicaoAlterada = True

      Me.Posicao = New Point(CInt(strLeft), _

        CInt(strTop))

    End If

  End If

Se houve mudança na Posição ou na Cor, o método retorna True, indicando ao ASP.NET que deve chamar RaisePostDataChangedEvent.


Return (_blnPosicaoAlterada OrElse _blnCorAlterada)

End Function

No momento em que esse método é chamado, os controles filhos já foram criados e o controle já foi inicializado. No momento da chamada de LoadPostData o controle está configurado com suas propriedades padrão e com os dados colocados na sua declaração no código da página ASPX. Ao fim da chamada de LoadPostData os dados disponíveis no controle são os dados enviados pelo cliente.

Os dados enviados por esse controle é o item selecionado no Dropdownlist de cores e sua posição. Os dados de posição são enviados em um elemento hidden da página, criado no código do RenderBeginTag (Listagem 12). O retorno de LoadPostData é a forma de sinalizar ao ASP.NET que ele deve chamar RaisePostDataChangedEvent, o que só acontece se LoadPostData retornar True. Para isso o método está utilizando em seu retorno uma operação OR entre as variáveis _blnPosicaoAlterada e _blnCorAlterada.

Na implementação do RaisePostDataChangedEvent da interface IPostBackDataHandler (Listagem 11) são feitas as chamadas dos eventos do sinalização das mudanças, consultando as mesmas variáveis _blnPosicaoAlterada e _blnCorAlterada.

Nota: O código da Listagem 11 deve ser implementado no Region Implementação de IPostBackDataHandler.

Esse método é responsável por jogar os eventos de mudança de estado. Nele é verificado se houve mudança de posição do controle via drag e drop e mudança da cor de fundo. Neste exemplo, esse é o terceiro método a rodar se houver postback e LoadPostData retornar True. Se rodar, roda antes dos métodos de renderização e após LoadPostData.


Protected Sub RaisePostDataChangedEvent() Implements _

System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent

   If _blnPosicaoAlterada Then

     OnPosicaoAlterada(EventArgs.Empty)

   End If

   If _blnCorAlterada Then

     OnCorAlterada(EventArgs.Empty)

   End If

End Sub

Com isso encerra-se o tratamento dos dados enviados do cliente para o servidor. Resta ainda fazer a renderização final do controle, em que o código HTML é finalmente gerado para que seja enviado ao cliente.

Renderização do controle

O RenderBeginTag, da Listagem 12, é responsável pela criação da tag de abertura do controle. O ASP.NET coloca entre o código gerado por RenderBeginTag e por RenderEndTag (Listagem 13) todo o código HTML gerado pelos controles filhos, assim como o dos controles que são colocados entre as tags de abertura e fechamento na página ASPX.

Como o DTHMLPanel é capaz de ser arrastado e trocar a cor de fundo, toda a renderização do mesmo precisa ser baseada em CSS. E como é um controle capaz de hospedar outros controles, está todo encapsulado em torno de uma tag div. Note como a maior parte do esforço posto em RenderBeginTag está em torno das propriedades CSS.

Além da criação da tag div de abertura, o código de Render também renderiza o elemento Input Hidden que armazenará os dados de posicionamento do controle. Esse elemento é referenciado posteriormente no cliente através do código gerado na Listagem 6, no método ConfiguraScripts, em que uma variável de script é gerada baseada no ClientID do controle.

Essa variável é então utilizada pelo código script disponível no arquivo DragDrop.js, que é colocado no output do controle, na linha hidden.value = novoX + '|' + novoY;. Depois de enviados ao servidor, os dados desse elemento são então recuperado no LoadPostData, da Listagem 10, na linha que chama postCollection(postDataKey).

Esse método é responsável por criar a tag div de abertura do controle. Neste exemplo, é o quarto método a rodar em um postback completo. Se não houver postback é o segundo. Nesse método também é criado o elemento hidden que será responsável por armazenar os dados do posição do controle quando esse for arrastado. Nesse momento o viewstate já foi restaurado e todas as propriedades do controle já estão disponíveis.

Listagem 12. Método RenderBeginTag.

Public Overrides Sub RenderBeginTag( _

  ByVal writer As System.Web.UI.HtmlTextWriter)

Primeiro é feita a composição do estilo do div. O primeiro passo é compor os dados de posição, através da propriedade Posicao:


Dim strEstilo As String = "style="

  Dim strLeft As String = Me.Posicao.X.ToString()

  Dim strTop As String = Me.Posicao.Y.ToString()

  strEstilo &= "left:" & strLeft & "px" & ";top:" & _

    strTop & "px;"

Se existe alguma cor, então também é adicionado um estilo de background-color.


If Me.BackColor <> Color.Empty Then

A classe ColorTranslator faz a conversão entre a cor do GDI+ e a cor HTML.


strEstilo &= "background-color:" & _

    ColorTranslator.ToHtml(Me.BackColor).ToLower() & _

    ";"

  End If

Por fim, adiciona-se os outros atributos que garantirão que o elemento será capaz de ser arrastado:


strEstilo &= _

    "position:relative;cursor:hand;z-index: 100;"

  strEstilo &= ""

Primeiro escreve-se a tag do div com o estilo adicionado. Um ponto importante de ser notado é a classe do div, que está setada para a string "drag", de forma a sinalizar ao script de drag e drop que esse elemento pode ser arrastado.


writer.Write("
& Me.ClientID & _
    "_div"" class="drag" & strEstilo & ">")

Note como o elemento hidden possui o ID do controle. É esse ID que garantirá as chamadas ao método LoadPostData quando houver um postback. Note também que o controle já recebe a última informação de posição no seu atributo de valor (value). Se não for um postback, será um ponto 0x0, se for um postback, os dados já foram carregados na propriedade posição através da chamada ao método LoadPostData.


writer.Write("<input id="" span="" ?<="" hidden??=""> &amp; _<o:p></o:p> & _

    Me.ClientID & "" name="" & Me.ClientID & _

    "" value="" & strLeft & "|" & strTop & "" />")

End Sub

O RenderEndTag (Listagem 13) simplesmente fecha a tag aberta em RenderBeginTag, passando " " à stream de saída.

Esse método simplesmente renderiza a tag div de fechamento. Neste exemplo esse é o quinto método a rodar em um postback completo. Se não houver postback é o terceiro.

Listagem 13. Método RenderEndTag.

Public Overrides Sub RenderEndTag( _

  ByVal writer As System.Web.UI.HtmlTextWriter)

  writer.Write("

")
End Sub

Isso conclui a criação do controle. Criaremos agora um projeto web para testá-lo.

Criação do projeto web de teste

No Solution Explorer clique com o botão direito e selecione Add>New Web Site. Na caixa de diálogo, selecione ASP.NET Web Site. Escolha o diretório que preferir e deixe o nome do site (o último diretório) como simplesmente “DHTML”. Mantenha as opções padrão para File System e Location. Escolha Visual Basic para Language e clique em OK.

Por padrão o Visual Studio 2005 já deve ter criado na Toolbox uma nova aba chamada DHTML.Library Components e nela o DHTMLPanel. Arraste-o para a página, entre as tags div já criadas no código. Ao fazer isso automaticamente o Visual Studio 2005 adiciona a referência ao projeto DHTML.Library e também o prefixo Register no topo da página.

Para que possamos movimentar o conteúdo do nosso controle livremente, aumentemos a área da tag body para praticamente toda a área do navegador. Fazemos isso colocando um estilo na tag body aumentando sua altura para 90% da página (). Adicione também uma imagem, texto, botão e Label entre as tags do DHTMLPanel. O código deve ficar parecido com o da Listagem 14.

Listagem 14. Código da página de teste Default.aspx.

<%@ Page Language="VB" AutoEventWireup="false"

  CodeFile="Default.aspx.vb" Inherits="_Default" %>

<%@ Register Assembly="DHTML.Library"

  Namespace="DHTML.Library" TagPrefix="cc1" %>

DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

    <title>.:: DHTML ::.title>

<head>

<body style="height:90%">

    <form id="form1" runat="server">

    <div>

        <cc1:DHTMLPanel ID="DHTMLPanel1"

           runat="server">

            Algum texto.<br />

            Uma imagem:

            <img src="msdn.gif" /><br />

            <asp:Button ID="Button1" runat="server"

              Text="Apenas faz postback" />

        cc1:DHTMLPanel>

        <asp:Label ID="lblOut" runat="server"

            EnableViewState="False">asp:Label>

        <br /><span id="spnOut">span><br />

    </div>       

    </form>

</body>

</html>

Mude para o modo de design e clique duas vezes sobre o controle. O código do evento padrão PosicaoAlterada será gerado no arquivo Default.aspx.vb. Nesse evento, coloque algum código para demonstrar os dados recuperados. Configure também o evento CorAlterada. O resultado deve ficar parecido com o código da Listagem 15.

Listagem 15. Código code behind da página de teste Default.aspx (arquivo Default.aspx.vb).

Protected Sub DHTMLPanel1_PosicaoAlterada( _

  ByVal sender As Object, ByVal e As System.EventArgs) _

  Handles DHTMLPanel1.PosicaoAlterada

  lblOut.Text &= "Posição modificada:
Top atual: " & _

    DHTMLPanel1.Posicao.Y & "
" & "Left atual: " & _

    DHTMLPanel1.Posicao.X & "
"

End Sub

 

Protected Sub DHTMLPanel1_CorAlterada( _

  ByVal sender As Object, ByVal e As System.EventArgs) _

  Handles DHTMLPanel1.CorAlterada

  lblOut.Text &= "Cor modificada.
Cor atual: " & _

    DHTMLPanel1.BackColor.Name & "
"

End Sub

Para executar o projeto, no Solution Explorer clique com o botão direito sobre o web site e selecione Set as StartUp Project e execute a solução. Teste a execução arrastando e soltando o controle, mudando sua cor de fundo, acrescentando alguma cor nova (como Yellow - Amarelo) e clicando no botão Apenas faz postback, confirmando que o controle mantém a posição e cor entre postbacks, ou seja, está enviando os dados ao servidor e trabalhando o retorno do controle conforme deveria.

Os dados de movimentação em tempo real são mostrados no elemento span chamado spnOut. Isso se deve ao fato de a movimentação estar chamando a função Escreve, conforme código script do arquivo DragDrop.js. A Figura 1 mostra o resultado da movimentação e da mudança de cor do controle.

Resultado da movimentação e da mudança de cor do controle
Figura 1. Resultado da movimentação e da mudança de cor do controle.

Para fazer o envio dos dados de posição o controle utiliza o elemento Input Hidden, já os dados de cor são recuperados do Dropdownlist e adiciona-se o item de cor novo, caso ele exista.

Conclusão

Dados gerados com HTML dinâmico podem ser obtidos através da criação de um controle customizado e implementação da interface IPostBackDataHandler que garante a recuperação no servidor dos dados gerados pelo navegador cliente.

Trata-se de uma operação simples de ser realizada após algum exercício e bastante útil e diversos casos, devendo-se no entanto, preocupar-se com a validação de segurança criada pelo atributo SupportsEventValidation em diversos controles do ASP.NET.

Os exemplos de criação e manipulações dinâmicas de HTML deste artigo podem ainda ser estendidos a exemplos mais complexos que utilizam tecnologias como AJAX, por exemplo.