Back-End

19 set, 2013

Spring MVC, Ajax e JSON – Parte 03: o código do lado do cliente

Publicidade

Se você tem acompanhado esta pequena série de artigos sobre Spring, Ajax e JSON, se recordará que fui até a criação de um aplicativo web em Spring MVC que exibe um formulário, o qual permite que o usuário selecione um monte de itens e submeta uma requisição ao servidor para comprá-los. O servidor então responde com um JSON, permitindo que o usuário confirme a sua compra. Se estiver se perguntando sobre o que estou falando, então dê uma olhada nos dois artigos anteriores desta série:

Terminado o código do lado do servidor, a próxima coisa a fazer é o código do lado do cliente, o que envolve escrever algum JavaScript. Agora, cá entre nós, não sou expert em JavaScript, apesar de saber – como a maior parte dos desenvolvedores “server side” – “quebrar um galho”.

A boa notícia para pessoas como eu é que o JavaScript cresceu rápido nos últimos anos, com muitas ferramentas e bibliotecas para aliviar a dificuldade no desenvolvimento e, entre todas elas, o jQuery parece ser o padrão de fato, com toda a sua cadeia e filosofia baseada em “escreva menos e faça mais”.

Tendo afirmado que estou utilizando jQuery, o próximo passo é configurar o JSP para que eu possa começar a escrever o código do cliente. Se você olhar o elemento <head> do shopping.jsp do HTML, verá que contém os seguintes links:

<link rel="stylesheet" href="<c:url value='/resources/blueprint/screen.css'/>" type="text/css" media="screen, projection"/>
<link rel="stylesheet" href="<c:url value='/resources/blueprint/print.css'/>" type="text/css" media="print" />
<!--[if lt IE 8]>
<link rel="stylesheet" href="<c:url value='/resources/blueprint/ie.css' />" type="text/css" media="screen, projection" />
<![endif]-->  

<link rel="stylesheet" href="<c:url value='/resources/style.css'/>" type="text/css" media="screen, projection"

Os primeiros três links são os includes para o Blueprint, o qual é, conforme já expliquei no primeiro post, para tornar a minha vida mais fácil no que diz respeito a formatação da página. O link final, para o style.css, é interessante. Ele contém dois valores CSS que eu emprestei sem vergonha alguma do código de exemplo do Spring MVC AJAX de Keith Donald. Esses valores CSS são #mask e #popup, que são aplicados as seguintes divs escondidas que eu acrescentei ao meu JSP:

     <div id="mask" style="display: none;"></div>
     <div id="popup" style="display: none;">
          <div class="container" id="insertHere">
               <div class="span-1 append-23 last">
                    <p><a href="#" onclick="closePopup();">Close</a></p>
               </div>
          </div>
     </div>

A máscara div é usada para esconder os conteúdos do navegador, enquanto isso a div popup é usada  para exibir uma caixa popu na qual vou escrever os resultados em JSON da minha chamada AJAX ao servidor. Note que o id insertHere será importante mais tarde.

As duas últimas linhas da tag head do JSP incluem os arquivos JavaScript para esta página:

     <script type="text/javascript" src="<c:url value='/resources/jQuery-1.9.1.js' /> "></script>
     <script type="text/javascript" src="<c:url value='/resources/shopping.js' /> "></script>

O primeiro desses imports é a versão 1.9.1 do jQuery. O número da versão é importante aqui. Se você utilizar a versão 1.7.x ou inferior, então o JavaScript abaixo não funcionará, uma vez que o pessoal do jQuery modificou a forma como as chamadas ao AJAX funcionam na versão 1.8 do jQuery. Contudo, o código JavaScript pode ser facilmente modificado, caso necessário.

O segundo import Javascript é o shopping.js, que inclui todo o código necessário para esse aplicativo, cuja parte mais importante é:

$(document).ready(
    function() {
      $('form').submit(
          function() {

            $('.tmp').remove();    // Remove any divs added by the last request

            if (request) {
              request.abort();  // abort any pending request
            }

            var $form = $(this);
            // let's select and cache all the fields
            var $inputs = $form.find("input");
            // serialize the data in the form
            var serializedData = $form.serialize();

            // let's disable the inputs for the duration of the ajax request
            $inputs.prop("disabled", true);

            // fire off the request to OrderController
            var request = $.ajax({
              url : "http://localhost:8080/store/confirm",
              type : "post",
              data : serializedData
            });

            // This is jQuery 1.8+
            // callback handler that will be called on success
            request.done(function(data) {

              console.log("Resulting UUID: " + data.uuid);
              $("<div class='span-16 append-8 tmp'><p>You have confirmed the following purchases:</p></div>")
              .appendTo('#insertHere');

              var items = data.items;
              // Add the data from the request as <div>s to the pop up <div>
              for ( var i = 0; i < items.length; i++) {
                var item = items[i];

                var newDiv = "<div class='span-4  tmp'><p>" + item.name + "</p></div>";
                $(newDiv).appendTo('#insertHere');

                newDiv = "<div class='span-6 tmp'><p>" + item.description + "</p></div>";
                $(newDiv).appendTo('#insertHere');

                newDiv = "<div class='span-4 append-10 last tmp'><p>£" + item.price + "</p></div>";
                $(newDiv).appendTo('#insertHere');

                console.log("Item: " + item.name + "  Description: " + item.description + " Price: "
                    + item.price);
              }

            });

            // callback handler that will be called on failure
            request.fail(function(jqXHR, textStatus, errorThrown) {
              // log the error to the console
              alert("The following error occured: " + textStatus, errorThrown);
            });

            // callback handler that will be called regardless if the request failed or succeeded
            request.always(function() {
              $inputs.prop("disabled", false);  // re-enable the inputs
            });

            event.preventDefault();   // prevent default posting of form

            showPopup();
          });
    });

Toda a ação acontece dentro das funções que foram submetidas pela função ready() do jQuery e, como é comum em JavaScript, há funções passadas para funções que passam para outras funções – a cadeia da qual eu estava falando anteriormente. Lembre-se de que a função ready() será chamada quando o documento estiver “pronto” para a interação.

A primeira função interna é $(‘form’).submit(…). Se você não sabe jQuery, então $ é o principal ponto de entrada para a biblioteca jQuery e é apenas um atalho para escrever jQuery. Nessa chamada, estou selecionando todos os formulários no documento (e há apenas um) e passando um argumento ao método submit(…).

Você pode usar jQuery para selecionar objetos dentro de seu modelo de documento e então fazer algo com eles. O jQuery possui seu próprio método de seleção que utiliza strings que são passadas para o método jQuery(…). As strings possuem o seguinte formato básico: elementos HTML tais como ‘form’, ‘a’ ‘div’ etc., que são escritos em Inglês e então passados individualmente para o jQuery selecionar todas as instâncias do tipo HTML dentro de um documento. Palavras acrescidas com um “.” (ponto) são valores CSS, e palavras acrescidas com “#” (sustenido) são atributos de id HTML. Portanto, como exemplo, se eu escrever $(‘form#bill’).submit(…), então selecionarei todos os formulários com um id de bill; ou se eu escrever $(‘.fred’).submit(…), então selecionarei todos os objetos do documento com um atributo de classe de fred. Uma vez que estiver acostumado com essa sintaxe, o resto é fácil.

Quando se trata de jQuery, acho o livro jQuery Cookbook da O’Reilly muito útil.

A função que é passada para o método $(‘form’).submit é onde todo o trabalho acontece. Antes de fazer uma requisição Ajax, há algumas tarefas de organização para completar. Isso inclui remover qualquer objeto do documento com uma classe tmp (a primeira vez que ela for chamada, não fará coisa alguma, mas confie em mim aqui); abortar qualquer requisições ao servidor (isso não fará nada na maior parte do tempo); desabilitar qualquer formulário de entrada e serializar os dados que a requisição Ajax postará no servidor. O trecho “chave” do código JavaScript é a requisição jQuery Ajax:

            // fire off the request to OrderController
            var request = $.ajax({
              url : "http://localhost:8080/store/confirm",
              type : "post",
              data : serializedData
            });

O formato dessa função é normalmente: ajax, url, settings. A URL que estou usando é http://localhost:8080/store/confirm, que corresponde ao RequestMapping do Spring que eu descrevi no artigo anterior. As configurações que você pode usar são pares de chave-valore opcionais e totalmente descritos na documentação do Ajax jQuery. Nessa instância, estou enviando o formulário de dados serializado utilizando a requisição post.

Tendo feito a requisição, há algumas tarefas para cuidar. Elas evitam que o formulário HTML submeta qualquer coisa ao servidor e que uma ‘div’ apareça nos resultados a partir dos quais a requisição Ajax é criada. Isso usa as duas divs previamente mencionadas com os ids popup e mask.

function showPopup() {
  $('body').css('overflow', 'hidden');
  $('#popup').fadeIn('fast');
  $('#mask').fadeIn('fast');
}

De volta à requisição Ajax…. A função $.ajax(…) retorna um objeto nomeado request que é do tipo jqXHR, cuja confusa sigla XHR significa XML HTTP Request. O objeto jqXHR é um número de métodos callback projetados para permitir que o seu código lide com certos eventos. Nesse caso, implementei: fail(…), always(…) e done(…). No caso de uma falha de requisição, o navegador chamará fail(…) para exibir um simples alert(…). always(…) é um método adequadamente nomeado que é sempre chamado, independentemente de a requisição falhar ou ser bem-sucedida. Nesse caso, ela reabilita todos os tipos de formulários de entrada. Finalmente, há o método done(…), que é chamado quando a requisição Ajax foi bem-sucedida.

request.done(function(data) {

              console.log("Resulting UUID: " + data.uuid);
              $("<div class='span-16 append-8 tmp'><p>You have confirmed the following purchases:</p></div>")
              .appendTo('#insertHere');

              var items = data.items;
              // Add the data from the request as <div>s to the pop up <div>
              for ( var i = 0; i < items.length; i++) {
                var item = items[i];

                var newDiv = "<div class='span-4  tmp'><p>" + item.name + "</p></div>";
                $(newDiv).appendTo('#insertHere');

                newDiv = "<div class='span-6 tmp'><p>" + item.description + "</p></div>";
                $(newDiv).appendTo('#insertHere');

                newDiv = "<div class='span-4 append-10 last tmp'><p>£" + item.price + "</p></div>";
                $(newDiv).appendTo('#insertHere');

                console.log("Item: " + item.name + "  Description: " + item.description + " Price: "
                    + item.price);
              }

            });

O método done(…) é o mais importante aqui. Como em seu próprio argumento, ele é passado para uma função, e o argumento dessa função são os dados JSON nos quais estamos interessados. Não se trata de velhas strings JSON brutas, pois o jQuery converteu o JSON em um objeto que possui um uuid e os atributos items. Uma duplicidade do código do objeto OrderForm do servidor que falei no meu último artigo. Usando esse objeto data, tudo que restou para eu fazer é exibir os resultados na tela. Isso significa um loop pelos dados e criar uma nova variável newDiv para cada um dos atributos do OrderForm e convertê-los em HTML. Trata-se de formatação básica de strings, como por exemplo:

"<div class='span-4  tmp'><p>" + item.name + "</p></div> "

 

torna-se:

<div class='span-4  tmp'><p>Socks</p></div>

Essa div contém alguns atributos de classe úteis. Esses atributos são o atributo de exibição do Blueprint e um outro chamado tmp. O atributo de classe tmp está associado à chamada $(‘.tmp’).remove();,  mencionada anteriormente. Isso é usado para apagar as seleções anteriores do usuário das divs que apareceram quando o usuário faz múltiplas submissões de seleção.

Tendo criado uma nova variável newDiv, o passo final é acrescentar o conteúdo a div usando a função appendTo(…) do jQuery com um argumento #insertHere:

$(newDiv).appendTo('#insertHere');

Se você executar o aplicativo, terá o seguinte formulário de compras, no qual é possível selecionar os itens que você deseja adquirir:

spring 1

Pressionar Confirmar a compra irá agora requisitar o JSON para o servidor, formatá-lo e exibir a seguinte div:

spring 2

A não ser que eu tenha esquecido alguma coisa, acho que é isso. Primeiramente, não sou um expert em JavaScript ou em client side, portanto, se há algum expert por aqui que percebeu algum erro, então, estarei ansioso para saber… Em segundo, esqueci de mencionar que a parte JSON desse projeto é RESTFull, por isso agradeço a Josh Long e sua menção a This Week in Spring por me recordarem isso. Eu acho que não me ocorreu mencionar isso porque, como regra geral, todo app deveria ser o mais RESTFull possível.

Para visualizar o código fonte completo deste artigo, acesse o GitHub – https://github.com/roghughe/captaindebug/tree/master/ajax-json

***

Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em http://www.captaindebug.com/2013/05/spring-mvc-ajax-and-json-part-3-client.html#.UdR8WPlwrng