Dúvida Json, VRaptor e Select [Resolvido]

12 respostas
osvaldogusmao

Pessoal, boa tarde.

Já tentei de diversas formas e pesquisei bastante antes de abrir o tópico, porém sem sucesso.

Tenho a seguinte situação:

1º Classe Endereco.java

@Entity
public class Endereco {
	@Id
	@GeneratedValue
	private Integer idEndereco;

	private String endereco;
	private String nroImovel;
	private String complemento;
	private String bairro;
	private String cep;
	
	@ManyToOne
	@JoinColumn(name = "idpessoa")
	private Pessoa pessoa;

	@ManyToOne
	@JoinColumn(name = "cidade_id")
	private Cidade cidade;

// get and set

Tenho um que é populado através de uma requisão ajax. Este tem como atributo nome endereco.cidade e no o atributo value sendo o id da Cidade.
Porém, ao enviar a requisição para o controller do VRpator, todos os campos do endereço são recebidos normalmente, menos o atributo cidade.

Obs.: Estou utilizando o jQuery Form plugin através do $("#idForm").ajaxSubmit();

Alguém pode me ajudar?

12 Respostas

programmer_ed

post seu select

osvaldogusmao

Boa tarde programmer_ed.

Segue o select

<select	id="enderecoCidade" name="endereco.cidade">
  <option value="">Selecione antes uma UF</option>
</select>

E a o ajax.

var idUF = $("#enderecoUF option:selected").val();
			
			$.getJSON('/suprimento/pessoas/buscaCidade.json/'+idUF, function(data){	
				$.each(data, function(key, cidade){
					$("#enderecoCidade").append('<option value="' + cidade.idCidade + '">'+ cidade.nome + '</option>');
				}); 
			});
Rafael_Guerreiro

Você faz um result.use(Results.json()).withoutRoot().from(enderecos).include(“cidade”).serialize() no seu controller?

Uma dica de performance:

$.getJSON('/suprimento/pessoas/buscaCidade.json/'+idUF, function(data){
// Ao invés de fazer em uma String e ir concatenando,
// vamos usar um array e depois fazer um join, que une todos os objetos do array com algum delimitador.
// Como não queremos sujeira, o delimitador será vazio: ''.
// Assim o js consegue gerenciar melhor a memória usada.
   var options = [];
   $.each(data, function(key, cidade){  
      options.push('&lt;option value="' + cidade.idCidade + '"&gt;'+ cidade.nome + '&lt;/option&gt;');  
   });
   $("#enderecoCidade").append(options.join('')); // o método append é um pouco lento, é ideal executá-lo 1 vez.
});

Mostra, por favor, como está o seu método do controller e o método que lista as cidades do seu DAO.

osvaldogusmao

Rafael Guerreiro, boa tarde.

Não havia me atentado ao include().

Quanto a dica de performance, agradeço também.

Muito Obrigado.

Rafael_Guerreiro

Ainda assim, eu posso ver os dois métodos?

Pois existe ainda uma possibilidade de fazer o seu JSON menor ainda. O que é mais performático…

osvaldogusmao

Metodo que lista as cidades:

@Get("/pessoas/buscaCidade.json/{idUF}")
public void buscaJson(String idUF) {
	UF uf = ufDao.carrega(UF.class, Integer.parseInt(idUF));
	result.use(json()).withoutRoot().from(cidadeDao.listaJson(uf)).serialize();
}

Método que grava o endereço

@Post("/pessoas/salvaEndereco.json")
public void salvaEndereco(Endereco endereco) {	

//Metodo que salva

result.use(json()).withoutRoot().from(inserido).include("cidade").serialize();

}
Rafael_Guerreiro

Hmmm…

Eu arrumaria um pouco o método salvaEndereco:

@Post("/pessoas/salvaEndereco.json")  
public void salvaEndereco(Endereco endereco) {    
  
   //Metodo que salva  
  // inserido é um Endereco, certo?
   result.use(json()).withoutRoot().from(inserido.getCidade()).serialize();// Assim você  vai carregar a cidade do método que foi inserido. Seu JSON fica mais leve.
}

Que tal essa ideia:
Você cria um converter do VRaptor que vai receber o id da UF e vai retornar uma UF, assim, você pode receber uma UF diretamente na sua Action.
Sem se preocupar em ficar repetindo esse código que carrega uma UF:
Esse aqui é um converter que eu fiz para uma interface minha chamada Autocomplete.
Que serve justamente para fazer o display dos dados do autocomplete.

@Convert(Autocomplete.class)
public class AutocompleteConverter implements Converter&lt;Autocomplete&gt; {

	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	private AutocompleteDAO autocompleteDAO;

	public AutocompleteConverter(AutocompleteDAO autocompleteDAO) {
		this.autocompleteDAO = autocompleteDAO;
	}

	@Override
	public Autocomplete convert(String value,
			Class&lt;? extends Autocomplete&gt; type, ResourceBundle bundle) {
		try {
			return autocompleteDAO.get(Long.parseLong(value), type); // Esse get aqui faz pelo atributo 'id'
		} catch (NumberFormatException e) {
			logger.warn("Unable to parse String value to Long. "
					+ e.getMessage());
			try {
				return autocompleteDAO.get(value, type);// Esse get aqui faz o get pelo atributo 'name'
			} catch (Exception exception) {
				logger.warn("Unable to load Autocomplete. "
						+ exception.getMessage());
				return null; // Não precisa retornar null, pode lançar uma exception também...
			}
		} catch (IllegalArgumentException e) {
			logger.warn("Unable to load Autocomplete. " + e.getMessage());
			return null; // Não precisa retornar null, pode lançar uma exception também...
		} catch (IllegalStateException e) {
			logger.warn("Unable to load Autocomplete. " + e.getMessage());
			return null; // Não precisa retornar null, pode lançar uma exception também...
		}
	}
}

Assim, na minha action, eu simplesmente faço isso:

@Get("/load/things/{obj}")
public void loadThings(Autocomplete obj){
}

E, na hora de chamar, eu faço assim:

/Projeto/load/things/2

Isso vai fazer com que o Converter carregue o autocomplete de código 2.

Com essa implementação, sua action ficaria assim:

@Get("/pessoas/buscaCidade.json/{uf}")  
public void buscaJson(UF uf) {
    result.use(json()).withoutRoot().from(cidadeDao.listaJson(uf)).serialize();  
}

Essa sua classe UF é uma Interface? Por que que você precisa passar o UF.class para o método carrega do UfDAO?

osvaldogusmao

Rafael Guerreiro, primeiramente obrigado.
Estava com uma dúvida que além de ser resolvida, tive uma aula.

Só algumas considerações.

1 - Quanto ao método salvaEndereco, eu carrego o endereço todo pois ao receber na minha jsp eu faço um append numa

.
Veja:
function showResponse(endereco, statusText, xhr, $form){

			if(statusText == 'success'){
				$("#tbEnderecos tr:last").after('<tr>'+
						'<td>'+endereco.endereco+'</td>'+
						'<td>'+endereco.nroImovel+'</td>'+
						'<td>'+endereco.complemento+'</td>'+
						'<td>'+endereco.bairro+'</td>'+
						'<td>'+endereco.cidade.nome+'</td>'+
						'<td></td>'+
					'</tr>');
			}else{
				alert("Erro ao inserir um endereço");
			}
		}

2 - Quanto ao converter, vou estuda-lo e com certeza irei implementa-lo.

3 - UF não é uma interface, eu passo UF.class pois estou utilizando um Generic DAO.
Veja

@Component
public abstract class DAOImpl<T, I extends Serializable> implements DAO<T, I> {

@Override
	public T carrega(Class<T> classe, Integer pk) {
		return (T) session.createCriteria(classe).add(Restrictions.idEq(pk)).uniqueResult();
	}

}

Tem como melhorar?

Obrigado mais uma vez.

Rafael_Guerreiro

osvaldogusmao:
Rafael Guerreiro, primeiramente obrigado.
Estava com uma dúvida que além de ser resolvida, tive uma aula.

Ah, que isso! Estamos aqui para ajudar! Eu já tirei inúmeras dúvidas aqui. E ainda tiro!

Rafael_Guerreiro

[CONTINUAÇÃO]
Esse ai é o nosso DAO Genérico, certo? Ele faz uma série de coisas e também faz o log de grande parde do que está acontecendo… (Você usa log, certo? É muito mais fácil de ver o que está acontecendo com o seu programa, sem precisar debugar. Se não estiver, use o log4j, facinho de mexer e de entender)

Bom, agora eu vou criar um UFDAO e vou extender esse GenericDAO ai, pois o código está todo nele, certo?

@Component
public class UFDAO extends GenericDAO&lt;UF&gt; {
	public UFDAO(Session session) {
		super(session, UF.class); // Assim eu garanto que aqui  vai funcionar para UFs!
	}
	
	// Todos os outros métodos estão muito bem implementados. Simples não?
}

Mas temos um problema, a lista de UF já está pré-definida… Eu não acho que ela vá ficar mudando… Mas a gente está permitindo que ela mude… Isso é um problema… Precisamos nos livrar dos métodos save, saveOrUpdate, update e merge que vieram juntos na hora de extender…
Muita gente “resolvia” esse problema dessa forma:

@Component
public class UFDAO extends GenericDAO&lt;UF&gt; {
	public UFDAO(Session session) {
		super(session, UF.class);
	}

	@Override
	public void save(UF obj) {
		throw new RuntimeException("Você não pode salvar uma UF.");
	}

	@Override
	public void saveOrUpdate(UF obj) {
		throw new RuntimeException("Você não pode salvar ou alterar uma UF.");
	}

	@Override
	public void merge(UF obj) {
		throw new RuntimeException("Você não pode alterar uma UF.");
	}

	@Override
	public void update(UF obj) {
		throw new RuntimeException("Você não pode alterar uma UF.");
	}
}

Cá entre nós, você está disponibilizando um método e, quando alguém chama, ele dá pau.
Isso ai é uma herança preguiçosa. Coisa horrível de ser feita, pois você herda TUDO da classe pai.

Então, vamos à composição:

@Component
public class UFDAO {
	private GenericDAO&lt;UF&gt; genericDAO;

	// Se você estiver usando eclipse, ele te ajuda nessa tarefa de pegar
	// somente os métodos que você quer...
	// Basta você criar o field da classe que você quer importar os métodos,
	// clicar com o botão direito,
	// Selecionar a opção 'Source' e depois 'Generate Delegate Methods...'
	// Ai vai abrir uma caixinha para você selecionar os métodos que você quer.
	// Veja a mágica acontecendo.

	public UFDAO(Session session) {
		genericDAO = new GenericDAO&lt;UF&gt;(session, UF.class);
	}

	// Agora  tem os métodos que a gente quiser.

	public UF get(Long id) { // Troquei para Long pois faz mais sentido aqui.
		//  no GenericDAO, é bom deixar Serializable, pois você pode fazer
		// alguma PK com char.
		// Que é o caso de País, ou UF mesmo
		return genericDAO.get(id);
	}

	public List&lt;UF&gt; list() {
		return genericDAO.list();
	}

	public List&lt;UF&gt; list(Order order) {
		return genericDAO.list(order);
	}

	public List&lt;UF&gt; list(String term, MatchMode matchMode) {
		return genericDAO.list(term, matchMode);
	}
}

Assim, a gente consegue receber um UFDAO tranquilamente e só passar ao método get o que realmente interessa, o ID da UF.

Assim, ainda evitamos a redundância, menor chances de erros e muito mais:
antes:

UF uf = ufDao.carrega(UF.class, 2);

agora:

UF uf = ufDao.get(2);

De nada cara! Só desejo que procure sempre melhorar, em qualquer coisa!

osvaldogusmao

Rafael Guerreiro, mais uma vez muito obrigado.

Vou começar a refatorar e fazer melhor.

Estou começando agora em java e tento me preocupar ao máximo com a qualidade do código.

Aproveitando, você acha interessante também criar uma interface UF e além de estender Generic eu também implemento UF? Ou seria código desnecessário?

Valew

Rafael_Guerreiro

Depende… Para que que serve essa Interface Generic?? Se ela tiver uma utilidade, sim.

Mas toma cuidado, você vai criar uma Interface UF que extends outra Interface… Por que você já não cria a classe UF que implementa Generic?

Um exemplo prático disso é aquela minha Interface Autocomplete:

public interface Autocomplete {
   public String getLabel();
}

Assim, eu criava as classes que eu ia usar no Autocomplete (aquele plugin para jQuery que atualmente está incorporado no jQuery UI)
Exemplos:

public class Country implements Autocomplete {
   private String code;
   private String name;

   public String getLabel() {
   // Repare que eu não preciso ter o atributo label. A label é gerada pela minha classe e acessada normalmente na JSP da seguinte forma:
   // ${country.label}
   // Dessa forma como está aqui não vai funcionar pelo result.use(Results.json())... Pois ele usa os atributos para gerar o JSON.
   // Mas nada que um setLabel(String label) na sua interface não resolva. Assim você cria o atributo label.
      return this.code + " - " + this.name;
   }

// getters e setters;
}

Outra coisa bem interessante é você sempre sobrescrever alguns métodos da classe Object:
toString(), hash() e equals().
Aqui tem um exemplo de como EU faço isso… Você pode fazer da forma que quiser.
(inclusive, o eclipse tem uma opção que eu não me lembro, que gera o hash e o equals para você…)

public class Port implements Autocomplete {

	@Id
	@Column(name = "PORT_ID", nullable = false, unique = true)
	private Long id;

	@Column(name = "NAME", unique = true, nullable = false, length = 255)
	private String name;

	@Column(name = "INLAND", nullable = false, length = 1, columnDefinition = "char")
	private String inland;

	@JoinColumn(name = "COUNTRY_CODE")
	@ManyToOne
	private Country country;

	@Transient
	private String display;

	@Override
	public boolean equals(Object obj) {
		if (obj == null)
			return false;

		return this.hashCode() == obj.hashCode();
	}

	@Override
	public int hashCode() {
		return this.toString().hashCode();
	}

	@Override
	public String toString() {// Eu formato o meu toString para a saída JSON... Me acostumei dessa forma.
		StringBuilder response = new StringBuilder("{");

		response.append("'id':'" + id + "'");
		response.append(", 'name':'" + name + "'");
		response.append(", 'inland':'" + inland + "'");

		if (country != null)
			response.append(", 'country':" + country.toString());

		if (Service.notEmpty(display))
			response.append(", 'display':'" + display + "'");

		return response.append("}").toString();
	}
// getters e setters
Criado 27 de setembro de 2012
Ultima resposta 28 de set. de 2012
Respostas 12
Participantes 3