[Resolvido] Checkbox dinâmico com Javascript e VRaptor 3

Oi pessoal!
Tenho uma galeria de fotos com 10 fotos, em cada uma existe um checkbox para marcar se é “avatar”, só pode haver uma foto como avatar. Ao subir uma foto marcando um checkbox Avatar a foto é salva no banco com um campo boolean “avatar”. Consigo carregar esse dado da seguinte forma:

    <c:forEach var="foto" items="${fotos}">
<c:choose>
		<c:when test="${foto.avatar == true}">
			<input type="checkbox" name="foto.avatar" id="avatar" checked />Avatar
		</c:when>
	<c:otherwise>
			<input type="checkbox" name="foto.avatar" id="avatar" onclick="setAvatar();">Avatar 	
	</c:otherwise>
	</c:choose> 
	   	    
    </c:forEach>  

O meu problema está sendo em ao clicar numa outra foto, teria que desmarcar o checkbox da anterior e enviar a id da nova foto por parâmetro e assim atualizar o avatar. Fiz o seguinte:

<script>
function setAvatar() {
	var boxes = document.getElementByName("foto.avatar");
	var foto = document.getElementByName("foto.avatar").value;
	 for(i=0;i<boxes.length;i++) {
			
	      if(boxes[i].checked) {
			boxes.checked=false;
			new Ajax.Request('atualizaAvatar',{
			            method: 'post',
			            parameters: foto,
			            onComplete: function(){
			    			alert('O avatar foi alterado com sucesso!');
			    			
			  			}						            
			        	}
			        );
			    } 					
							
	      }
}
</script>

Pesquisando no Google achei o Prototype: http://www.prototypejs.org/learn/introduction-to-ajax
Estou seguindo o manual dele com outras dicas da internet, mas não está dando certo, ao marcar outra foto a anterior não é desmarcada e tb não passa nada por parâmetro pro Controller:

public void atualizaAvatar(Foto foto) {
		fotoDAO.atualiza(foto);
	}

Na DAO está:

public void atualiza(Foto foto) {
		Transaction tx = session.beginTransaction();
		session.update(foto);
		tx.commit();
	}

Alguém consegue dar alguma dica de como resolver? =)
Abraço!!

Quando você possui apenas UMA opção o correto é usar RADIO. Checkbox é apenas quando você pode ter mais de uma opção.

e pro parameters vc tem que colocar algo do tipo:

{ 'foto.avatar' : foto }

ou

'foto.avatar=' + foto

recomendo vc usar o JQuery… acho mais fácil que o prototype:

http://api.jquery.com/jQuery.ajax/

Eu só não entendi porque usar uma gambiarra dessas de usar javascript ao invés de usar o elemento correto. Não assassinem o HTML, por favor :lol:

Obrigado pelas dicas!
Fiz as mudanças, está com radio button, código até agora está assim:

<script>
function setAvatar() {
	var foto = document.elementByName="foto.avatar";
	if (foto.checked=true) {
		
		$.ajax({
		      url: "atualizaAvatar",
		      global: false,
		      type: "POST",
		      data: ({'foto.avatar' : foto}),
		      async:false,
		      success: function(){
		         alert('Avatar alterado com sucesso!');
		      }
		   }
		).responseText;
			
	}		
}
</script>

A DAO ficou desta forma, mas ainda tô raciocinando em cima do método:

public void atualiza(Foto foto) {
		Transaction tx = session.beginTransaction();
		if(foto.getAvatar() == true) {			
		Criteria c  = session.createCriteria(Foto.class);
		Foto fot = (Foto) c.add(Restrictions.eq("idFoto", foto.getIdFoto()))
		.add(Restrictions.eq("avatar", true)).uniqueResult();		
		fot.setAvatar(false);  
		session.merge(fot); 
		tx.commit();
		} else {
		Criteria c  = session.createCriteria(Foto.class);
		Foto fot = (Foto) c.add(Restrictions.eq("idFoto", foto.getIdFoto()));
		fot.setAvatar(true);  
		session.merge(fot);			
		tx.commit();
		}
	}

Tb acho que está bem complicada essa tática, sugerem uma outra forma de setar um avatar para um imóvel sem ser desse jeito?
Abraço!

obs: o GUJ bugou, tá sem o botão de “Responder”, eu respondi na opção “resposta rápida” e depois editei o post.

Eu não sei como está sua entidade, mas eu faria diferente. Você tem N fotos, e uma delas será o avatar, correto?

Penso que ao invés da foto ter uma propriedade que diz se ela é avatar ou não, o correto é o Imovel saber quem é o avatar dele. Ou seja, mudando a entidade:

public class Imovel { private List<Foto> fotos; private Foto avatar; }

Opa! Valeu Garcia!
Se eu entendi direito, ao fazer upload de uma foto e clicasse no checkbox para marcá-la como avatar o atributo avatar na classe Imovel guardaria a foto. Mas ai teria que alterar os métodos de upload no Controller e salvar no component né?
Abraço!

Não precisa ser exatamente no upload. Você pode fazer o upload normalmente e adicionar as fotos como você estava fazendo. A minha sugestão altera apenas a escolha do avatar, que ao invés de vocẽ colocar um isAvatar=true você indica no imovel qual a foto é um avatar.

Entendi a idéia, mas lá na JSP vou ter que continuar usando ajax pra mandar os dados da foto selecionada pro Controller e depois dar um update nos dados?

obs: kd o botão de responder do fórum?

Guevara, para mim o botão de responder está aparecendo normalmente. Tente limpar o cache, ou algo assim.

Me tira uma dúvida, você tem um imóvel com uma lista de fotos, certo? E apenas uma dessas fotos pode ser o avatar do imovel, é isso? Na entidade Imovel você tem uma coleção de fotos?

Isso mesmo Garcia, assim está a tabela Foto no banco:

Fiz um teste com sql direto no banco só pra testar, rodei dois updates, um para setar com false e outro pra setar com true.

Na classe Imóvel tenho:

@OneToMany(targetEntity=Foto.class, mappedBy = "imovel",orphanRemoval=true)	
	public List<Foto> fotos; 

obs: o botão “Responder” voltou. O.o

Acho que então fica melhor você remover o atributo avatar da classe Foto. Após isso adicione o atributo avatar do tipo Foto na classe Imovel:

public class Imovel { private List<Foto> fotos; private Foto avatar; }

Quase nada muda nas outras classes. Você continua normalmente adicionando e excluindo fotos. O que muda agora é na hora de você escolher quem é o avatar. Dessa forma nova você tem uma lista de fotos, então você marca qual será o avatar.

public void salvarComoAvatar(Foto avatar, Imovel imovel) { [...] imovel.setAvatar(avatar); [...] }

E no seu JSP:

<c:forEach var="foto" items="${fotos}"> <c:choose> <c:when test="${foto.avatar == true}"> <input type="radio" name="avatar.id" id="avatar" checked="checked" value="${foto.id}" onclick="setAvatar();">Avatar </c:when> <c:otherwise> <input type="radio" name="avatar.id" id="avatar" value="${foto.id}" onclick="setAvatar();">Avatar </c:otherwise> </c:choose> </c:forEach>

O Ajax eu não sei como fica porque conheço pouco o jquery. Você basicamente precisa apenas o valor da foto selecionada e também o id do imóvel. Aliás você nem precisa usar Ajax nisso, você pode fazer por formulário normal fazendo submit para o vraptor.

Oi Garcia!
Estou com um probleminha aqui na hora de chamar a aplicação. Fiz a alteração que vc sugeriu:

	@OneToMany(targetEntity=Foto.class, mappedBy = "imovel",orphanRemoval=true)	
	public List<Foto> fotos; 
	private Foto avatar;

//getter and setter para avatar

E a stacktrace acusa:

root cause

org.hibernate.MappingException: Could not determine type for: br.com.imobiliaria.bean.Foto, at table: Imovel, for columns: [org.hibernate.mapping.Column(avatar)]
	org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:290)
	org.hibernate.mapping.SimpleValue.isValid(SimpleValue.java:274)
	org.hibernate.mapping.Property.isValid(Property.java:217)
	org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:464)
	org.hibernate.mapping.RootClass.validate(RootClass.java:236)
	org.hibernate.cfg.Configuration.validate(Configuration.java:1193)
	org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1378)
	org.hibernate.cfg.AnnotationConfiguration.buildSessionFactory(AnnotationConfiguration.java:954)
	org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:883)
	org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:56)
	javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:48)
	javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:32)
	br.com.caelum.vraptor.util.jpa.EntityManagerFactoryCreator.create(EntityManagerFactoryCreator.java:39)
	sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	java.lang.reflect.Method.invoke(Method.java:597)
	org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:340)
	org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:293)
	org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:130)
	org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:394)

Não está aceitando o atributo do tipo Foto na classe Imovel, precisa anotar algo lá?
Abraço!

Sim amigo!

Você precisa colocar a anotação @OneToOne, pois segundo a tua necessidade, o imóvel só deverá ter um avatar.

     @OneToMany(targetEntity=Foto.class, mappedBy = "imovel",orphanRemoval=true)   
     public List&lt;Foto&gt; fotos;

     @OneToOne
     @Column(name="id_foto")   
     private Foto avatar;  
  
     //getter and setter para avatar  

Com isso o Hibernate irá criar um atributo chamado id_foto na tua entidade Imovel.

Obrigado softwork.
Vou tentar aqui.

Oi pessoal…
Aproveitando o topico… alguém sabe como gero um formulario dinamico com o vraptor3… estava pensando em algo do tipo campos basicos como Nome, Endereço, Data de Nascimento, Sexo, e mais alguns serem fixos, e outros campos serem dinamicos no formulario. Como por exemplo buscar os campos dinamicamente no Mysql e gerar a tela de formulario. Estou querendo a cabeça aqui e não consigo. Apenas queria que facilitasse na hora de incluir mais um campo no formulario era só inserir mais uma linha numa tabela do mysql.
Alguem pode me ajudar?
Aguardo resposta.
Obrigado.
Leandro.

Leandro, tem um gerador de código feito por um usuário do Vraptor: http://guj.com.br/posts/list/199727.java

Oi pessoal!
Já entendi como fazer pra salvar esse avatar na tabela Imovel, mas estou com dúvidas na hora de implementar isso.
O problema é que apenas um imovel.setAvatar(avatar); no método de upload salva() não funciona, precisaria de um método de salvarAvatar() lá na DAO pra concluir o processo.
Algo do tipo:

public void salvarAvatar(Imovel imovel, Foto avatar) {
		Transaction tx = session.beginTransaction();	
		session.save(objeto); 
                //session.update(imovel); 
		tx.commit();
	}

No método salva() esta assim:

public void salva(UploadedFile imagem, Imovel imovel, Foto foto, Foto avatar ) {
		
                      //codigo de upload

			//ImovelDAO imovelDao = new ImovelDAO();
			imovel.setAvatar(avatar); //este cara sozinho não salva o avatar na tabela imovel
			//imovelDao.salvaAvatar(imovel, avatar);	
		
			//codigo para salvar a foto na tabela Foto	    
		} catch (IOException e) {
			throw new RuntimeException("Erro ao copiar imagem", e);
		}		
	}

Precisaria de um metodo em ImovelDAO para salvar essa foto lá não?
Outra coisa, no meu componente Imagens, que é o código acima, a DAO injetada no construtor da classe não funciona, nem FotoDAO, nem ImovelDAO, preciso instanciar cada um deles no método salva(). O estranho é que é só na classe Imagens, que é um @Component. Preciso anotá-la com @RequestScoped que nem as DAO’s?

Abraço!!

pra salvar um Imovel vc precisa salvar a Foto ANTES…
ou configurar o Cascade para PERSIST, aí ele faz automático…

qto ao problema dos daos, se a sua classe Imoveis é Session ou ApplicationScoped vc não consegue fazer um DAO, que é RequestScoped, funcionar…
isso pq assim que a Imoveis é criada, um DAO é criado e passado pra ele… mas o dao depende da requisição, então na primeira requisição a session (do hibernate) dele é fechada, e nas próximas ele vai ficar com um DAO inválido…

uma solução:

  • receba uma SessionFactory no construtor do Imoveis, e controle a sua sessão na mão, instanciando os DAOs na mão (mas uma vez só);
    vc pode abrir a sessão já no início da classe (se o Imoveis for application scoped) e controlar as transações manualmente…

Oi Lucas!
Obrigado pelas dicas. Tentei seguir a dica do Garcia mas não dá, pq se eu salvo a foto, no imovel não salva nada, pois até então nada em Foto foi gravado. Estou pensando em criar uma classe Avatar só pra isso, ai a url do avatar não será salva na tabela Foto.

public void salva(UploadedFile imagem, Imovel imovel, Foto foto, Foto avatar) {
//outros codigos...
String nome = foto.getNome();
			foto.setNome(nome);
			String caminho = destino.getName();
			foto.setUrlFoto(caminho);
			foto.setImovel(imovel);				
		    fotoDAO.adiciona(foto);	// Foto salva sem problemas
		    imovel.setAvatar(avatar); // objeto null

Tô quebrando a cabeça pra não criar código que cause dados duplicados de fotos no banco. =/
Acha a opção de criar uma classe Avatar especifica para isso uma opção ruim?
Abraço!!