Combo aninhado em Struts 1.x com jQuery Ajax

Combos aninhados, também conhecido como combos dinâmicos, são (pelo menos) dois combos boxes onde os valores do segundo combo dependem do valor do primeiro combo. Por exemplo, imaginem um cadastro de cidade com a seguinte estrutura de banco de dados:

combo_aninhado

Onde, ao cadastrar uma cidade, deve-se selecionar o país e o estado desejado antes de inserir o nome da cidade. Sendo que, ao selecionar o país (primeiro combo), a lista de estados (segundo combo) deve ser atualizada de acordo com o país selecionado, para só então preencher o nome da cidade desejado. Mas como fazer isso de uma forma simples e elegante (tá, talvez não seja a melhor forma! rs) usando Struts 1.x e jQuery?

Bom, tomando do princípio que já tenhamos toda nossa aplicação configurada, inclusive com o banco de dados mapeado em nosso aplicativo, vamos fazer o seguinte: um cadastro rápido de cidades com os combos de países e estados aninhados.

Primeiramente, vamos abrir nosso formulário de cadastro de cidades carregando a lista de países através de um Action comum desta forma:

package com.wordpress.felipebbarbosa.comboaninhado.action;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.hibernate.Session;

import com.wordpress.felipebbarbosa.comboaninhado.dao.Dao;
import com.wordpress.felipebbarbosa.comboaninhado.modelo.Pais;
import com.wordpress.felipebbarbosa.comboaninhado.util.HibernateUtil;

public class AbreCadastroCidadeAction extends Action {

  @Override
  public ActionForward execute(ActionMapping mapping, ActionForm form,
    HttpServletRequest request, HttpServletResponse response)
    throws Exception {

    Session session = new HibernateUtil().getSession();
    List<Pais> paises = new Dao(session).listaPaises();

    HttpSession httpSession = request.getSession();
    httpSession.setAttribute("paises", paises);

    session.close();

    return mapping.findForward("ok");

  }

}

O que esse Action fez? Simplesmente buscou todos os países cadastrados no banco de dados e colocou numa lista (List<Pais> paises) e atribuiu essa lista na sessão. E seu retorno direcionará para o cadastro-cidade.jsp:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%><%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html:html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
  <title>Combo box aninhado com Struts 1.x - Cadastro de Cidade</title>
  <script type="text/javascript" src="jquery-1.7.min.js"></script>
</head>
<body>
  <html:form action="cadastraCidade">
    <table border="0" cellpadding="0" cellspacing="0">
    <tr>
      <td>País</td>
      <td>
        <html:select styleId="pais" property="paisId">
          <html:option value="-1">Selecione</html:option>
          <html:options collection="paises" property="id" labelProperty="nome"/>
        </html:select>
      </td>
    </tr>
    <tr>
      <td>Estado</td>
      <td>
        <html:select styleId="estado" property="estadoId">
          <html:option value="-1">Selecione</html:option>
        </html:select>
      </td>
    </tr>
    <tr>
      <td>Nome</td>
      <td><html:text property="nome" styleId="nome"></html:text></td>
    </tr>
    </table>
    <html:submit value="Salvar"></html:submit>
  </html:form>
</body>
</html:html>

Até agora tudo bem, temos um formulário com o primeiro combo box preenchido com todos os países cadastrados em nosso sistema. Nosso próximo passo agora é o objetivo deste post. Após seleção do país desejado, o sistema deve listar todos os estados daquele país “automaticamente”. Para isto, vamos criar uma Action um pouco diferente do normal, pois ela será requisitada via AJAX e retornará as informações no formato JSON através do jQuery desta forma:

<script type="text/javascript">
 $(function() {
   $("#pais").change(function() {
     $.getJSON('carregaEstados.do?pais=' + $(this).val(), function(data) {
       var estados_html = '<option value=-1>Selecione</option>';
       $.each(data, function(index, item) {
         estados_html += '<option value=' + item.id + '>' + item.nome + '</option>';
       });
       $("#estado").html(estados_html);
     });
   });
 });
 </script>

O que esta função está fazendo? Declaramos uma função para quando o combo de países ser alterado (change) iremos chamar a Action carregaEstados.do passando o país selecionado como parâmetro para que esta traga todos os estados daquele país no formato JSON. E com o JSON “em mãos” montamos nosso segundo combo.

Mas como passar os estados no formato JSON no Action? Desta forma:


package com.wordpress.felipebbarbosa.comboaninhado.action;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.hibernate.Session;
import org.json.JSONArray;
import org.json.JSONObject;

import com.wordpress.felipebbarbosa.comboaninhado.dao.Dao;
import com.wordpress.felipebbarbosa.comboaninhado.modelo.Estado;
import com.wordpress.felipebbarbosa.comboaninhado.modelo.Pais;
import com.wordpress.felipebbarbosa.comboaninhado.util.HibernateUtil;

public class CarregaEstadosAction extends Action {

  @Override
  public ActionForward execute(ActionMapping mapping, ActionForm form,
      HttpServletRequest request, HttpServletResponse response)
      throws Exception {

    String idPais = request.getParameter("pais");

    Session session = new HibernateUtil().getSession();
    Dao dao = new Dao(session);

    Pais pais = dao.loadPais(Long.parseLong(idPais));

    List<Estado> estados = dao.listaEstados(pais);

    JSONArray array = new JSONArray();
    for (Estado estado : estados) {
      JSONObject object = new JSONObject();

      object.put("id", estado.getId());
      object.put("nome", estado.getNome());

      array.put(object);
    }

    String jsonResult = array.toString();

    response.setContentType("text/javascript; charset=utf-8");
    response.getWriter().write(jsonResult);

    return null;
  }

}

O que o Action fez? Ele simplesmente buscou todos os estados do país selecionado e criou um objeto JSON (http://json.org/java/) com o resultado. E este Action não precisa de um forward no struts-config.xml.


<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN" "http://struts.apache.org/dtds/struts-config_1_3.dtd">

<struts-config>

  <form-beans>
    <form-bean name="CidadeForm" type="com.wordpress.felipebbarbosa.comboaninhado.form.CidadeForm"/>
  </form-beans>

  <action-mappings>
    <action path="/abreCadastroCidade" scope="request"
        type="com.wordpress.felipebbarbosa.comboaninhado.action.AbreCadastroCidadeAction">
      <forward name="ok" path="/cadastro-cidade.jsp"/>
    </action>

    <action path="/carregaEstados" scope="request"
        type="com.wordpress.felipebbarbosa.comboaninhado.action.CarregaEstadosAction"></action>

    <action path="/cadastraCidade" scope="request" name="CidadeForm" input="cadastro-cidade.jsp"
        type="com.wordpress.felipebbarbosa.comboaninhado.action.CadastraCidadeAction">
      <forward name="ok" path="/ok.jsp"/>
    </action>
  </action-mappings>
</struts-config>

Pronto! Temos um combo aninhado com Struts 1.x e jQuery! Agora é só preencher o nome da cidade e clicar em Salvar e chamar o Action padrão de cadastro! =)
Caso preferir, você pode fazer o download do exemplo completo.

Atualização Setembro/2014: Disponibilizei o código no GitHub também.

3 comentários sobre “Combo aninhado em Struts 1.x com jQuery Ajax

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s