Ok, aqui fica então uma explicação do conceito e do código... algo sucinta que o tempo não estica. :) Todo o código está disponível aqui.
Portanto, a ideia inicial, tal como demonstrada no exemplo, era implementar um mecanismo que permitisse, usando "ajax" (passe a publicidade), fazer refresh assincronamente apenas a determinadas áreas da página que fossem modificadas num postback.
Além disto o ideal era ser o menos "intrusivo" possível mantendo todo o conceito asp.net e evitando ter que manipular o DOM em javascript (até pq o intellisense não é grande coisa, e aqui entre nós... eu sem intellisense não sou coisa nenhuma :) ). A complexidade é um dos grandes problemas nas implementações de Ajax que vejo normalmente, tal como mencionou,e bem, o Pedro Sousa.
A ideia que surgiu então foi aproveitar o mecanismo de rendering do asp.net de forma a que quando detectássemos um pedido ajax fosse feito apenas o render dos controlos alterados. Aqui parti do principio que tinha que nos caber a nós programadores saber quais os controlos a necessitar de refresh.... isto pq detectar isto em runtime....bem... duvido (era a cereja!).
Este mecanismo é de certa forma usado no GMail e foi já implementado em Java de forma semelhante à que aqui descrevo, In Place Update" da Simple Web Framework . O conceito é exactamente o mesmo.
Adiante, do que precisava eu então?
-
Ter uma forma de registar quais os controlos a renderizar na resposta,
-
Ter uma forma de marcar determinados controlos para fazerem o postback em modo ajax
-
Alterar o render da página de forma a gerar apenas o html dos controlos alterados,
-
Mecanismo que fizesse o post via xmlhttp do form actual (equivalendo portanto a um postback normalissimo, mas mexer na página),
-
Com base na response do xmlhttp afectar os controlos necessários, não esquecendo de actualizar o viewstate.
Com a lógica de implementar a ideia da forma mais rápida possível....afinal o que queria era ver até que ponto seria usável, usei algumas técnicas dificilmente suportadas oficialmente... invocação de métodos privados, desactivação de validações asp.net, enfim, uma desgraça! Estou já a avisar. :)
Ter uma forma de registar quais os controlos a renderizar na resposta:
Nada que um arraylist não resolvesse:
ArrayList changedControls= new ArrayList();
void AddChangedControl(Control ctl)
{
changedControls.Add(ctl);
}
Ter uma forma de marcar determinados controlos para fazerem o postback em modo ajax:
Usando o attributes, algo como:
public void EnableAjaxOnControl(LinkButton btn)
{
btn.Attributes.Add("onclick","AjaxPostBack(this);return false;");
}
Nota: No exemplo estou a ser mais drástico, altero a função __dopostback de forma a que todos os linkbutons, dropdowns,etc herdam o comportamento automaticamente, paginação e sorting de datagrids,etc.
Alterar o render da página de forma a gerar apenas o html dos controlos alterados:
Ok, aqui apanhei o 1o problema dado que por default o asp.net não permite fazer o render de um elemento se não estiver dentro de uma form (coisa que eu não queria fazer). A StackTrace apontava o método culpado de forma que fiz override do mesmo desactivando a validação:
public override void VerifyRenderingInServerForm(Control ctl)
{
//Ignore base server form validation
}
Tirando isto, a implementação foi fácil, override ao método render e:
if (EnableAjaxPostBack && IsAjaxPostBack )
{
foreach (WebControl ctl in changedControls)
{
StringWriter sw= new StringWriter();
HtmlTextWriter ht= new HtmlTextWriter(sw);
ctl.RenderControl(ht);
Response.Write(string.Format("RefreshElement('{0}','{1}');",
ctl.ClientID,EscapeJavascript(sw.ToString())));
}
}
else
base.Render(html);
Mecanismo que fizesse o post via xmlhttp do form actual (equivalendo portanto a um postback normalissimo) :
Ok, aqui confesso que desanimei dado que não me apetecia nada ter que implementar isto de raiz.... o mais certo era pegar nisto noutro dia (daqui a muitos anos). Mas recuperei os excelentes artigos de Bill Pierce (saga AJAX was here), exactamente o que precisava! As rotina ajax dele faziam ja o postback correcto do form actual via xmlhttp. Siga! :)
Com base na response do xmlhttp afectar os controlos necessários, não esquecendo de actualizar o viewstate:
Com o viewstate tive que recorrer à obtenção de um campo privado (shame on me).... dado que se o viewstate tivesse a verificação activa batia no check do hash. E a criação do losformatter com esse constructor não tinha eu acesso. Entretanto não fui reconfirmar isto... deve haver uma forma decente de o fazer, devo ter visto mal.
protected override void SavePageStateToPersistenceMedium(object viewState)
{
if (EnableAjaxPostBack && IsAjaxPostBack)
{
FieldInfo los=typeof(Page).GetField("_formatter",
BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Instance);
LosFormatter l=(LosFormatter) los.GetValue(this);
StringWriter viewStateStr= new StringWriter();
l.Serialize(viewStateStr,viewState);
Response.Write(string.Format("RefreshElementValue('__VIEWSTATE','{0}');",
viewStateStr.ToString()));
}
else
base.SavePageStateToPersistenceMedium(viewState);
}
Dito isto faltava apenas passar o viewstate e o html dos controlos do response do xmlhttp para o dom do form corrente. Estava já a desesperar novamente, a pensar em parsers e xml.... quando atalhei, "vergonhosamente", caminho .. mais uma vez. :) E se passasse javascript? Dito e feito.
Response.Write(
string.Format("RefreshElement('{0}','{1}');",
ctl.ClientID,EscapeJavascript(sw.ToString())));
para os controlos e
Response.Write(string.Format("RefreshElementValue('__VIEWSTATE','{0}');",
para o viewstate.
A versão inicial usava o document.getelementbyid directamente, a nova utiliza funções localizadas no ficheiro kissajax.js, é bem mais fácil de trabalhar e melhorar.
E prontos, acho que não me esqueci de nada. Bastou isto, o resto foram ligeiros ajustes. Toda esta implementação migrou para a classe base KissAjaxPage. A implementação da página de exemplo em si não tem quase nada que lhe diga (bastam os AddChangedControl nos sítios correctos), podendo funcionar no modo normal ou via ajax postbacks.
Algumas notas soltas:
-
Se um controlo a que queremos fazer update não existe no DOM não é possível fazer o refresh, de forma que nessas alturas uso paineis (o IPU da Simple Web Framework referida atrás usa regiões prov. pela mesma razão).
-
Penso que há ainda um problema com o formato do post em botões normais e image buttons. Não deve ser difícil de detectar e corrigir, coisa para a qual não tenho é grande paciência.
-
Validators,etc, devem trazer as suas dores de cabeça.
-
Dores de cabeça, aqui e ali, com uma mudança drástica destas é certo :)
-
Combinação com eventos onkeyup (filtragem na hora), mto fácil, mas retirei do sample online dado que a performance era sofrível
-
KISS- ora bem KISS pq enquanto ia marinando a ideia forçava-me a recordar as famosas palavras "Keep It Simple Stupid" :)
-
Só para que fique bem claro, não sou defensor do Ajax, nem opositor (obviamente). Tal como muitas outras técnicas tem as suas vantagens e desvantagens. Isto é apenas uma prova de conceito. Estamos no campo da especulação pura. O mesmo para o conceito já falado de "In Place Update".
-
Funciona.... estranho... :)
Obrigado pelo feedback, um abraço!
RQ