VRaptor - partialResponse para o JQuery

6 respostas
rafaelbtz

Boa tarde,

Estou fazendo uns testes aqui utilizando JQuery para o cliente e VRaptor no servidor.
Estou tentando fazer um partialResponse para atualizar apenas pedaços da pagina semelhante a forma como o JSF faz com seu <f:ajax/>

A parte cliente está funcionando, mas não sei como fazer no VRaptor para ele retornar um XML com apenas alguns pedaços do JSP, nem sei se isso é possível.

Deixa eu explicar melhor:
Meu teste é o seguinte, em uma pagina é exibido 2 spans com o mesmo parâmetro da request, quando eu clico em um link é feita uma requisição ao VRaptor que atualiza esse parametro, porém apenas um dos Spans deve ser atualizado:

JSP (teste.jsp)

MiliSegundos (deve atualizar)  <span id="atualizar">${milisegundos}</span> 
<br/>
MiliSegundos (NÃO deve atualizar) <span id="naoAtualizar">${milisegundos}</span>
<br/>
<a href="javascrpt:;" id="linkAtualizar" >Atualizar</a>

JQuery

var partialUpdate = function (url, updateId){
	$.get(url, function (data){
		$('#'+updateId).replaceWith($(data).filter('#'+updateId)[0]);
	}, 'html');					
};

$("#linkAtualizar").click(function (evt){
	evt.preventDefault();
	partialUpdate("<c:url value='/partial/teste'/>", 'atualizar');
});

A principal linha aqui é a 3, onde a partir do retorno do VRaptor eu localizo o updateID e coloco ele no lugar do elemento que já está na tela.

VRaptor:
nada demais

public void teste() {
	result.include("milisegundos", System.currentTimeMillis());
}

Meu problema é: lógico que o VRaptor retorna sempre todo o conteúdo do teste.jsp, eu queria saber se existe alguma forma de após o jsp ter sido processado eu manipular o DOM e retornar apenas os IDS que eu quero, eu sei quais são pois vou receber via request do JQuery.

Esse exemplo é bem didático, eu sei que da pra fazer isso de outra forma com JSON ou com Pure e tal, mas se eu conseguisse fazer dessa maneira iria simplificar muito aqui pra mim. Pois temos aqui muitos formulários dinamicos que mudam conforme as configurações de cada usuário

Muito obrigado pela ajuda.

6 Respostas

Lucas_Cavalcanti

essa url sempre vai retornar o xml parcial?

se sim é só usar a jsp normal e não usar o template geral do site nele

rafaelbtz

Lucas Cavalcanti:
essa url sempre vai retornar o xml parcial?

se sim é só usar a jsp normal e não usar o template geral do site nele

Valeu Lucas mas na verdade nem sempre, imagina um form com vários inputs. Porém tem um pedacinho desse form um div por exemplo que tem ali dentro 2 inputs em particular, que no clique de um link eu preciso atualizá-los após a regra do VRaptor ser executada.

Daria para resolver criando 2 JSP’s um completo com todos os componentes e um somente com a DIV que é atualizavel, e no JSP completo eu daria um include do menor pra não ter que repetir códigos. E ai na atualização ajax eu devolvo apenas o JSP menor.

Mas no nosso caso as combinações são muitas e fica difícil separar staticamente os JSP’s, até da, mas se fosse possível fazer isso dinamicamente, fazendo algo como um $(document).filter("#xpto") no DOM antes do retorno ficaria muito mais simples e transparente.

Mas se não der jeito vai ter que ser um monte de JSPzinhos mesmo, ou então fazer via JSON aumentando a programação no JQuery.

Lucas_Cavalcanti

bom, dá pra fazer sim…

vc pode criar um Filter que usa um parâmetro pra procurar o id no html e retornar só aquilo.

algo como:

class MeuFiltro implements Filter {

   doFilter(request, response, chain) {
       if (request tem o parâmetro x) {
            chain.doFilter(request, new ResponseModificado(response));
       } else {
            chain.doFilter(request, response);
       }
   }
}

e no responseModificado, vc captura tudo que for escrito no response.getWriter() ou no response.getOutputStream() e só coloca o que tá dentro do id que vc passou como parâmetro.

talvez vc tenha que transformar o html em xml primeiro (o html deveria ser um xml válido pra não dar problemas);

mas vc vai aumentar um pouco o processamento do servidor, pra diminuir a banda gasta…

[]'s

rafaelbtz

Putz, valeu mesmo Lucas. É aquele tipo de solução “Como eu não pensei nisso antes!!!”.

Estou começando agora meus estudos/testes com o VRaptor, e fiquei preso a ele pra essa solução e esqueci do Filtro. Estava tentando fazer mais ou menos isso ai no Iterceptor do VRaptor e não estava me dando muito bem.

Mais uma vez valeu, vou testar aqui, depois coloco uma solução completa com os métodos da JQuery e Custom Tags pra galera pois acho que vai facilitar um pouco a manipulação de paginas via ajax.

rafaelbtz

Bom, voltei a mexer com esse problema de PartialResponse no VRaptor e terminei aqui, acho que ficou bom estão vou colocar a solução que deve ajudar alguém, só está faltando criar uma CustomTag do JSP (tipo <xxx:ajax update=‘compId’ process=‘comp1Id’/> ) pra deixar bem parecido com JSF e ficar transparente para os programadores (é que aqui na minha empresa estamos saindo do JSF e indo para o Jquery + VRaptor em um projeto, então eu não quero assustar muito os programadores com o JQuery já que com o JSF (primefaces) o JQuery é bem escondido)

Vamos lá, como eu fiz(Parte Html):
nada demais no html, apenas o onclick do botão que chama a function ajax

<form class="form" id="form">
	<label>Nome</label>
	<input type="text" name="nome" id="inpNome"/>
	<br/>
		
	<label id="lblMsg">${mensagem}</label>

	<input type="button" value="Ajax" id="btnAjax" onclick="ajax('<c:url value='/partial/teste/exec'/>', 'inpNome', 'lblMsg', event);"/>
</form>

JavaScript:
o sendId indica quais componentes terão os valores transmitidos na request e o updateId indica quais campos serão substituidos e também indica quais campos serão retornados pelo servidor.
Os 2 podem receber um ou mais inputs ou form, basta informar os ids separados por espaço.

function ajax (url, sendId, updateId, event){
		event.preventDefault();

		var selector = getSelectorByIds(sendId);
		var data = '';
		if(selector != null){
			data = $(selector).serialize();
		}
		data += '&updateIds='+updateId;
					
		$.get(url, data, function (html){
			var updateSelector = getSelectorByIds(updateId);
			if(updateSelector){
				$html = $(html);
				$(updateSelector).each(function (idx){
					$(this).replaceWith($html.filter('#'+this.id));
					$html.end();
				});
			}
		}, 'html');
	};

(Parte servidor)
Web.xml:
No web.xml eu mapeei o filtro para executar em requisições /partial/*, assim o programador pode escolher. Se ele colocar /partial antes da url então a resposta será parcial apenas dos componentes que ele passou o id, já se ele não colocar o /partial a resposta será completa porém o JQuery só vai substituir os componentes desejados.
Então fica na mão do programador /partial - Maior processamento no lado servidor, porém menor banda. Sem o /partial menor processamento do lado servidor porém maior uso da banda. Mas o ajax funciona normalmente com as duas opções, como depois vai ter um customTag isso vai virar um parametro boolean da tag.

<filter-mapping>
    <filter-name>partialResponse</filter-name>
    <url-pattern>/partial/*</url-pattern>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

Filtro
O filtro só troca o response tira o /partial do endereço e manda pro VRaptor processar

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
    	HttpServletRequest httpRequest = ((HttpServletRequest)request);
    	
    	if (response.getCharacterEncoding() == null) {
            response.setCharacterEncoding("UTF-8");
        }

        PartialResponseWrapper respAjax = new PartialResponseWrapper((HttpServletResponse) response, httpRequest.getParameter("updateIds"));
        
        String path = ((HttpServletRequest)request).getServletPath();
        httpRequest.getRequestDispatcher(path.replace("/partial", "")).forward(httpRequest, respAjax);
    }

O código do PartialResponseWrapper e do PartialOutputStream são padrões para se fazer um wrapper de response então eu não vou colocar aqui para não aumentar demais o post, porém no flush() do OutputStream é que está o pulo do gato, que é o seguinte:
Estou usando esse HTMLParser: http://htmlparser.sourceforge.net/ assim não precisa transformar o HTML em XML

É passado pro outputstream quais os IDS que precisam ser retornados (através do parâmetro updateIds ali na linha 9 do código javascript e na linha 8 do filtro)

private byte[] getPartialResponse() throws IOException{
		long t1 = System.currentTimeMillis();
		try {
			if(responseIds == null)return null;
			
			OrFilter filter = createHtmlFilter();
			
			byte[] bytes = getBytes();
			Parser parser = Parser.createParser(new String(bytes, Charset.forName("UTF-8")), "UTF-8");
			NodeList ex = null;
			try {
				ex = parser.extractAllNodesThatMatch(filter);
			} catch (ParserException e) {
				throw new IOException("Falha ao criar parser para a resposta AJAX", e);
			}
			
			return ex.toHtml().getBytes(Charset.forName("UTF-8"));
		}finally {
			System.out.println("TEMPO PARA GERAR RESPOSTA PARCIAL: " + (System.currentTimeMillis()-t1));
		}
	}
	private OrFilter createHtmlFilter() {
   String[] responseIds = updateIds == null || updateIds.length() == 0 ? null : updateIds.split(" ");
		NodeFilter[] filters = new NodeFilter[responseIds.length];
		for(int i = 0; i < responseIds.length; i++) {
			filters[i] = new HasAttributeFilter("id", responseIds[i]);
		}
		return new OrFilter(filters);
	}

Pronto com isso quando eu faço uma requisição para /partial/controller/metodo o método do controler é executado, porém, apenas os componentes que tenham os IDS informados na request é que serão devolvidos para o browser.

É isso, espero que ajude alguém, fiz apenas testes iniciais mas parece funcionar sem problemas, e o tempo para gerar a resposta Parcial não está passando de 20ms em paginas médias com (20 inputs).

Lucas valeu pela dica do response.

Lucas_Cavalcanti

Legal!

cria lá um gist com o código: http://gist.github.com

ou um projeto mesmo, pra gente usar como plugin.

daí dá pra gente dar uns pitacos e melhorar o código =)

[]'s

Criado 25 de janeiro de 2012
Ultima resposta 8 de fev. de 2012
Respostas 6
Participantes 2