Quantcast
Channel: Loiane Groner » Spring MVC
Viewing all articles
Browse latest Browse all 2

Tutorial: Combo Boxes Aninhados com ExtJS, Spring MVC 3 e Hibernate 3.5

$
0
0

Este é um tutorial passo a passo de como implementar combo boxes aninhados usando ExtJS (no lado cliente) e Spring MVC 3 e Hibernate 3.5 (no lado servidor).

Vou usar o exemplo clássico de comboboxs: estados e cidades. neste exemplo, vou usar os estados e cidades do Brasil.

extjs linked comboboxes spring loiane02 Tutorial: Combo Boxes Aninhados com ExtJS, Spring MVC 3 e Hibernate 3.5

Qual é o objetivo final? Quando o usuário selecionar um estado no primeiro combo box, a aplicação irá carregar o segundo combo box com as cidades que pertencem ao estado selecionado – sem recarregar a página.

No ExtJS, existem duas maneiras de implentar.

A primeira é carregar o conteúdo dos dois combo boxes, e quando o usuário selecionar um estados, a aplicação irá filtrar os dados do combo box de cidades para mostrar apenas as cidades que pertencem ao estado selecionado.

A segunda forma é carregar apenas as informações necessárias para popular o combo box dos estados. Quando o usuário selecionar um estado, a aplicação irá fazer uma requisição para carregar as informações das cidades do estado escolhido.

Qual é a melhor maneira? Depende da quantidade de dados que será necessário buscar no banco de dados. Por exemplo: você tem um combo box que lista todos os países do mundo. E o segundo combo box representa todas as cidades do mundo (ou cidades de cada país). Neste caso, o cenário número 2 é a melhor opção, porque no cenário 1 seria necessário carregar todas as cidades de uma só vez. Imagina a quantidade enorme de dados que iria carregar do banco de dados? É necessário analisar.

Ok. Vamos ao código fonte. Vou mostrar como implementar ambos os cenários.

Mas primeiro, vou mostrar como o projeto está organizado:

extjs linked comboboxes spring loiane01 Tutorial: Combo Boxes Aninhados com ExtJS, Spring MVC 3 e Hibernate 3.5

Vamos dar uma olhada no código Java.

BaseDAO:

Contém o hibernate template usado por CityDAO e StateDAO.

package com.loiane.dao;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.stereotype.Repository;

@Repository
public abstract class BaseDAO {

	private HibernateTemplate hibernateTemplate;

	public HibernateTemplate getHibernateTemplate() {
		return hibernateTemplate;
	}

	@Autowired
	public void setSessionFactory(SessionFactory sessionFactory) {
		hibernateTemplate = new HibernateTemplate(sessionFactory);
	}
}

CityDAO:

Contém dois métodos: um para carregar todas as cidades do banco (usado no cenário #1), e outro método para carregar todas as cidades que pertencem a um determinado estado (usado no cenário #2).

package com.loiane.dao;

import java.util.List;

import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Repository;

import com.loiane.model.City;

@Repository
public class CityDAO extends BaseDAO{

	public List<City> getCityListByState(int stateId) {

		DetachedCriteria criteria = DetachedCriteria.forClass(City.class);
		criteria.add(Restrictions.eq("stateId", stateId));

		return this.getHibernateTemplate().findByCriteria(criteria);

	}

	public List<City> getCityList() {

		DetachedCriteria criteria = DetachedCriteria.forClass(City.class);

		return this.getHibernateTemplate().findByCriteria(criteria);

	}
}

StateDAO:

Contém apenas um método para carregar todos os estados do banco.

package com.loiane.dao;

import java.util.List;

import org.hibernate.criterion.DetachedCriteria;
import org.springframework.stereotype.Repository;

import com.loiane.model.State;

@Repository
public class StateDAO extends BaseDAO{

	public List<State> getStateList() {

		DetachedCriteria criteria = DetachedCriteria.forClass(State.class);

		return this.getHibernateTemplate().findByCriteria(criteria);

	}
}

City:

Representa o POJO Cidade/City; representa a tabela Cidade/City.

package com.loiane.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import org.codehaus.jackson.annotate.JsonAutoDetect;

@JsonAutoDetect
@Entity
@Table(name="CITY")
public class City {

	private int id;
	private int stateId;
	private String name;

	//getters and setters
}

State:

Representa o POJO Estado/State; represeta a cidade Estado/State.

package com.loiane.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import org.codehaus.jackson.annotate.JsonAutoDetect;

@JsonAutoDetect
@Entity
@Table(name="STATE")
public class State {

	private int id;
	private int countryId;
	private String code;
	private String name;

	//getters and setters
}

CityService:

Contém dois métodos: um para carregar todas as cidades do banco (usado no cenário #1), e outro método para carregar todas as cidades que pertencem a um determinado estado (usado no cenário #2).

Faz apenas chamada para a classe CityDAO.

package com.loiane.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.loiane.dao.CityDAO;
import com.loiane.model.City;

@Service
public class CityService {

	private CityDAO cityDAO;

	public List<City> getCityListByState(int stateId) {
		return cityDAO.getCityListByState(stateId);
	}

	public List<City> getCityList() {
		return cityDAO.getCityList();
	}

	@Autowired
	public void setCityDAO(CityDAO cityDAO) {
		this.cityDAO = cityDAO;
	}
}

StateService:

Contém apenas um método para carregar todos os estados do banco. Faz apenas uma chamada para a classe StateDAO.

package com.loiane.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.loiane.dao.StateDAO;
import com.loiane.model.State;

@Service
public class StateService {

	private StateDAO stateDAO;

	public List<State> getStateList() {
		return stateDAO.getStateList();
	}

	@Autowired
	public void setStateDAO(StateDAO stateDAO) {
		this.stateDAO = stateDAO;
	}
}

CityController:

Contém dois métodos: um para carregar todas as cidades do banco (usado no cenário #1), e outro método para carregar todas as cidades que pertencem a um determinado estado (usado no cenário #2). Faz apenas chamada para a classe CityService. Ambos os métodos retornam um objeto JSON no seguinte formato:

{"data":[
         {"stateId":1,"name":"Acrelândia","id":1},
         {"stateId":1,"name":"Assis Brasil","id":2},
         {"stateId":1,"name":"Brasiléia","id":3},
         {"stateId":1,"name":"Bujari","id":4},
         {"stateId":1,"name":"Capixaba","id":5},
         {"stateId":1,"name":"Cruzeiro do Sul","id":6},
         {"stateId":1,"name":"Epitaciolândia","id":7},
         {"stateId":1,"name":"Feijó","id":8},
         {"stateId":1,"name":"Jordão","id":9},
         {"stateId":1,"name":"Mâncio Lima","id":10},
]}

Classe:

package com.loiane.web;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.loiane.service.CityService;

@Controller
@RequestMapping(value="/city")
public class CityController {

	private CityService cityService;

	@RequestMapping(value="/getCitiesByState.action")
	public @ResponseBody Map<String,? extends Object> getCitiesByState(@RequestParam int stateId) throws Exception {

		Map<String,Object> modelMap = new HashMap<String,Object>(3);

		try{

			modelMap.put("data", cityService.getCityListByState(stateId));

			return modelMap;

		} catch (Exception e) {

			e.printStackTrace();

			modelMap.put("success", false);

			return modelMap;
		}
	}

	@RequestMapping(value="/getAllCities.action")
	public @ResponseBody Map<String,? extends Object> getAllCities() throws Exception {

		Map<String,Object> modelMap = new HashMap<String,Object>(3);

		try{

			modelMap.put("data", cityService.getCityList());

			return modelMap;

		} catch (Exception e) {

			e.printStackTrace();

			modelMap.put("success", false);

			return modelMap;
		}
	}

	@Autowired
	public void setCityService(CityService cityService) {
		this.cityService = cityService;
	}
}

StateController:

Contém apenas um método para carregar todos os estados do banco. Faz apenas uma chamada para a classe StateService. O método retorna um objeto JSON no seguinte formato:

{"data":[
         {"countryId":1,"name":"Acre","id":1,"code":"AC"},
         {"countryId":1,"name":"Alagoas","id":2,"code":"AL"},
         {"countryId":1,"name":"Amapá","id":3,"code":"AP"},
         {"countryId":1,"name":"Amazonas","id":4,"code":"AM"},
         {"countryId":1,"name":"Bahia","id":5,"code":"BA"},
         {"countryId":1,"name":"Ceará","id":6,"code":"CE"},
         {"countryId":1,"name":"Distrito Federal","id":7,"code":"DF"},
         {"countryId":1,"name":"Espírito Santo","id":8,"code":"ES"},
         {"countryId":1,"name":"Goiás","id":9,"code":"GO"},
         {"countryId":1,"name":"Maranhão","id":10,"code":"MA"},
         {"countryId":1,"name":"Mato Grosso","id":11,"code":"MT"},
         {"countryId":1,"name":"Mato Grosso do Sul","id":12,"code":"MS"},
         {"countryId":1,"name":"Minas Gerais","id":13,"code":"MG"},
         {"countryId":1,"name":"Pará","id":14,"code":"PA"},
]}

Classe:

package com.loiane.web;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.loiane.service.StateService;

@Controller
@RequestMapping(value="/state")
public class StateController {

	private StateService stateService;

	@RequestMapping(value="/view.action")
	public @ResponseBody Map<String,? extends Object> view() throws Exception {

		Map<String,Object> modelMap = new HashMap<String,Object>(3);

		try{

			modelMap.put("data", stateService.getStateList());

			return modelMap;

		} catch (Exception e) {

			e.printStackTrace();

			modelMap.put("success", false);

			return modelMap;
		}
	}

	@Autowired
	public void setStateService(StateService stateService) {
		this.stateService = stateService;
	}
}

Dentro da pasta WebContent temos:

  • ext-3.2.1 – contém todos os arquivos ExtJS;
  • js – contém todos os arquivos javascript que foram implementados para este exemplo. liked-comboboxes-local.js comtém o código fonte do combo box para o cenário #1; liked-comboboxes-remote.js contém o combo box para o cenário #2; linked-comboboxes.js contém um tab panel para exemplificar os dois cenários.

Vamos dar uma olhada no código ExtJS.

Cenário Numero 1:

Carregar todos os dados disponíveis do banco de dados para popular os dois combo boxes. Usa um filtro no combo box das cidades.

liked-comboboxes-local.js:

	var localForm = new Ext.FormPanel({
       width: 400
       ,height: 300
       ,style:'margin:16px'
       ,bodyStyle:'padding:10px'
       ,title:'Linked Combos - Local Filtering'
       ,defaults: {xtype:'combo'}
       ,items:[{
            fieldLabel:'Select State'
           ,displayField:'name'
           ,valueField:'id'
           ,store: new Ext.data.JsonStore({
	       		url: 'state/view.action',
	            remoteSort: false,
	            autoLoad:true,
	            idProperty: 'id',
	            root: 'data',
	            totalProperty: 'total',
	            fields: ['id','name']
	        })
           ,triggerAction:'all'
           ,mode:'local'
           ,listeners:{select:{fn:function(combo, value) {
               var comboCity = Ext.getCmp('combo-city-local');
               comboCity.clearValue();
               comboCity.store.filter('stateId', combo.getValue());
               }}
           }

       },{
            fieldLabel:'Select City'
           ,displayField:'name'
           ,valueField:'id'
           ,id:'combo-city-local'
           ,store: new Ext.data.JsonStore({
       		   url: 'city/getAllCities.action',
               remoteSort: false,
               autoLoad:true,
               idProperty: 'id',
               root: 'data',
               totalProperty: 'total',
               fields: ['id','stateId','name']
           })
           ,triggerAction:'all'
           ,mode:'local'
           ,lastQuery:''
       }]
   });

O combo box que representa os estados (state) é declarado nas linhas  9 a 28.

O combo box que representa das cidades (city) é declarado nas linhas 31 a 46.

Repare que ambos os combo boxes são carregados quando fazemos o load da página, como pode ser visto nas linhas 15 e 38 (autoload:true).

O combo box que representa os estados possui um select event listener que quando executado, filtra o combo box que representa das cidades baseado na seleção atual do estado. Pode ser visto nas linhas 23 a 28.

O combo box que representa as cidades possui um atributo lastQuery:”". Isso é para “enganar” o combo box quando é feito o load da página. Assim, o combo box pensa que já foi feito um filtro.

Scenario Number 2:

Carrega apenas os dados dos estados do banco de dados. Quando o usuário seleciona um estado, a aplicação irá buscar todas as cidades relacionadas a este estado no banco de dados – sem fazer refresh da página.

liked-comboboxes-remote.js:

var dataBaseForm = new Ext.FormPanel({
       width: 400
       ,height: 200
       ,style:'margin:16px'
       ,bodyStyle:'padding:10px'
       ,title:'Linked Combos - Database'
       ,defaults: {xtype:'combo'}
       ,items:[{
            fieldLabel:'Select State'
           ,displayField:'name'
           ,valueField:'id'
           ,store: new Ext.data.JsonStore({
	       		url: 'state/view.action',
	            remoteSort: false,
	            autoLoad:true,
	            idProperty: 'id',
	            root: 'data',
	            totalProperty: 'total',
	            fields: ['id','name']
	        })
           ,triggerAction:'all'
           ,mode:'local'
           ,listeners: {
               select: {
                   fn:function(combo, value) {
                       var comboCity = Ext.getCmp('combo-city');
                       //set and disable cities
                       comboCity.setDisabled(true);
                       comboCity.setValue('');
                       comboCity.store.removeAll();
                       //reload city store and enable city combobox
                       comboCity.store.reload({
                           params: { stateId: combo.getValue() }
                       });
                       comboCity.setDisabled(false);
       			  }
               }
       		}
       },{
            fieldLabel:'Select City'
           ,displayField:'name'
           ,valueField:'id'
           ,disabled:true
           ,id:'combo-city'
           ,store: new Ext.data.JsonStore({
       		   url: 'city/getCitiesByState.action',
               remoteSort: false,
               idProperty: 'id',
               root: 'data',
               totalProperty: 'total',
               fields: ['id','stateId','name']
           })
           ,triggerAction:'all'
           ,mode:'local'
           ,lastQuery:''
       }]
});

O combo box que representa os estados (state) é declarado nas linhas  9 a 38.

O combo box que representa das cidades (city) é declarado nas linhas 40 a 55.

Repare que apenas o combo box dos estados é carregado quando fazemos o load da página, como pode ser visto na linha 15 (autoload:true).

O combo box que representa os estados possui um select event listener que quando executado, carrega os dados para a store das cidades (passa stateId como parâmetro) baseado no estado selectionado. Pode ser vista nas linhas 24 a 38.

O combo box que representa as cidades possui um atributo lastQuery:”". Isso é para “enganar” o combo box quando é feito o load da página. Assim, o combo box pensa que já foi feito um filtro.

Se desejar, pode fazer o download do projeto completo no meu repositório GitHub: http://github.com/loiane/extjs-linked-combox

Usei Eclipse IDE + TomCat 7 para desenvolver este projeto de exemplo.

Referência: http://www.sencha.com/learn/Tutorial:Linked_Combos_Tutorial_for_Ext_2

Bons códigos! icon smile Tutorial: Combo Boxes Aninhados com ExtJS, Spring MVC 3 e Hibernate 3.5


Viewing all articles
Browse latest Browse all 2

Latest Images

Trending Articles





Latest Images