Passar um lista (javascript) para um controller (spring)

Bom dia amigos, ontem passei o dia tentando resolver um problema e não consegui solução. Eu tenho o seguinte codigo HTML:

<form action="/pagina/teste">
    <ul class="opcoes">
        <li><a id="1" href="">Opcao 1</a></li>
        <li><a id="2" href="">Opcao 2</a></li>
        <li><a id="3" href="">Opcao 3</a></li>
        <li><a id="4" href="">Opcao 4</a></li>
        <li><a id="5" href="">Opcao 5</a></li>
    </ul>
    <button type="submit">Enviar</button>
</form>

Quando o usuário clicar sobre uma opção, ele acaba disparando uma função jquery que atribui uma classe de estilo ao link chamada “checked”. Isto irá simular um comportamento de checkbox.

Meu arquivo javascript ficou assim:

$(document).ready(function() {
    $('opcoes a').click(function() {
        $(this).addClass('checked');
    });

    $('form').submit(function() {
        var json = '{"itens": [';
        var itens = '';
       
        $('.opcoes a.checked').each(function() {
            var item = '{"id": '+ $(this).attr('id') +'}';           
            itens += itens == ''? item : ','+ item;   
        });
        json += itens +']}';

        $.post($(this).attr('action'), jQuery.parseJSON(json));
    });
});

Como pode ser percebido, foi utilizado JSON para tentar enviar. Digo tentar, pois me faltou idéias de como enviar esta lista de itens selecionados. O meu controlador, ficou da seguinte forma:

@Controller
@RequestMapping(value = "/pagina/**")
public class PaginaController {
    @RequestMapping(value = "/pagina/teste", method = RequestMethod.POST)
    public String teste(@RequestParam Object data) {
        //Faz qqr coisa aqui
    }
}

Quando é clicado no botão Enviar, não chega nada no Controller (coloquei um breakpoint para ver se estava dando certo) e no inspetor de objetos do chrome, apareceu este erro:

POST http://localhost:8084/Projeto/pagina/teste 400 (Bad Request) jquery.js:4
f.support.ajax.f.ajaxTransport.send jquery.js:4
f.extend.ajax jquery.js:4
f.each.f.(anonymous function) jquery.js:4
(anonymous function) functions.wizard.js:42
f.event.dispatch jquery.js:3
f.event.add.h.handle.i

Sei que estou fazendo algo errado, mas preciso de ajuda de como resolver este problema.

Obrigado!

Primeira dica: não monte o json na mão, é muito propenso a erros. No jquery, faça algo desta maneira:

$('form').submit(function() {
   var ids = $('.opcoes a.checked').map(function(element, index) {
      return $(this).attr('id');
   });

   var data = { ids: ids };
   
   $.post($(this).attr('action'), data);

A função map retorna um novo array de acordo com os retornos da função de callback (no caso retornando os ids). No spring, pra receber, faça algo assim:

@RequestMapping(value = "/pagina/teste", method = RequestMethod.POST)
@ResponseBody
public void teste(@RequestParam("ids[]") List<String> ids) {
   System.out.println(ids);
}

Veja outras possibilidades que comentamos neste tópico:

Bom dia Wagner, obrigado pela atenção… Fiz conforme sugerido e ocorreu algo muito estranho: o browser travou e mostrou a “caretinha” de erro do Chrome. Não gerou nada no console do chrome e tbm nem entrou na minha função (estava depurando).

Tentei de outra forma, como sugerido no link. Utilizei a solução utilizando o $.ajaxSettings.traditional, mas tbm não deu certo. Claro que fiz meu jquery diferente, ficou assim:

$('.wizard-conteudo form.step4').live('submit', function() {
        $.ajaxSettings.traditional = true;  
        var projeto = {
            team: [{'id': '1'},{'id': '3'}]
        };
        $.post($(this).attr('action'), projeto).success(function(data) {  
            console.log(data);  
        });
}

Neste segundo caso não deu erro, até entrou no meu controller, mas o objeto que veio por parêmetro veio com todos os atributos nulll, sendo que pelo menos o atributo “team” deveria vir com valores.

Minha classe Projeto está assim:

public class ProjetoVO implements Serializable {
    private static long serialVersionUID = 4552851541035770203L;
    
    @Id @GeneratedValue
    private Integer id;
    @Column(length=40)
    private String nome;    
    @Temporal(TemporalType.DATE)
    private Date inicio; 
    @Temporal(TemporalType.DATE)
    private Date fim;
    
    @ManyToOne @JoinColumn(name="gerente", referencedColumnName="id", nullable=false)
    private UserVO scrumMaster;
    
    @ManyToOne @JoinColumn(name="patrocinador", referencedColumnName="id", nullable=false)
    private UserVO productOwner;
    
    @ManyToMany(fetch= FetchType.EAGER)
    private List<UserVO> team;

    //Setter & getters
}

Meu controler ficou assim:

@Controller
@RequestMapping(value = "/projeto/**")
public class ProjetoController {
    @ResponseBody 
    @RequestMapping(value = "/projeto/step5", method = RequestMethod.POST)
    public String step5(@ModelAttribute ProjetoVO contato) {
        System.out.println("Entrei aqui");
    }
}

Ainda não saí do chão… Alguma sugestão para resolver este empasse?

[quote=libajunior]Bom dia Wagner, obrigado pela atenção… Fiz conforme sugerido e ocorreu algo muito estranho: o browser travou e mostrou a “caretinha” de erro do Chrome. Não gerou nada no console do chrome e tbm nem entrou na minha função (estava depurando).
[/quote]

Simulei um exemplo rapidamente com a função que lhe passei e obtive uma lista com todos os ids. Você tentou mais de uma vez? Tenta investigar melhor o que houve pra saber pq travou.

Como que a requisição está indo pro servidor (os parâmetros principalmente)? Pode colar ela aqui?

Olá Wagner, vamos por partes:

Arquivo JSP:

    ...
    <script type="text/javascript" src="<c:url value="/js/jquery.js" />"></script>
    <script type="text/javascript" src="<c:url value="/js/functions.wizard.js" />"></script>      
<bodY>
<section class="conteudo">
    <h2 class="wizard-titulo">Definição do Time</h2>
    <p class="wizard-titulo"><small>Selecione os integrantes do Time deste projeto.</small></p>
    <section class="wizard-conteudo">
        <c:url var="url" value="/projeto/step5" />
        <form id="formulario" action="${url}" class="step4" method="POST">
            <c:forEach items="${membros}" var="membro">
                <div class="wizard-containermembro">
                    <c:set var="temPapel" value="${(projeto.productOwner.id == membro.id) || (projeto.scrumMaster.id == membro.id)}"/>
                    <c:if test="${!temPapel}">
                    <a id="<c:out value="${membro.id}" />" class="team" href="#">Marcar</a>
                    </c:if>
                    <c:if test="${temPapel}">
                    <a id="<c:out value="${membro.id}" />" class="disabled" href="#">Marcar</a>
                    </c:if>
                    <img src="<c:url value="/images/icon-profile.png" />" width="48" height="48" />
                    <div>
                        <h3><c:out value="${membro.nome}" /></h3>
                        <p><c:out value="${membro.username}" /></p>
                    </div> 
                    
                </div>
            </c:forEach>
        </form>
    </section>
    <section class="wizard-barrabotao">
        <button id="bt-next" form="formulario" type="submit">Próximo Passo ?</button>
    </section>
</section>

O Arquivo JavaScript:

$(function() {
    $('.wizard-conteudo form.step4').live('submit', function(e) {
        var ids = $('a.checked').map(function(element, index) {  
            return $(this).attr('id');  
        });  
  
        var data = { ids: ids };  
     
        $.post($(this).attr('action'), data);
    });
});

O arquivo Controller:

@Controller
@RequestMapping(value = "/projeto/**")
public class ProjetoController {
    @RequestMapping(value = "/projeto/step5", method = RequestMethod.POST)
    @ResponseBody
    public void step5(@RequestParam("ids[]") List<String> ids) {
        System.out.println(ids);    
    }
}

Ao executar, recebi o seguinte erro no browser:

HTTP Status 400 -

type Status report

message

description The request sent by the client was syntactically incorrect ().

Apache Tomcat/7.0.14

Já no console do Chrome, deu os seguintes erros:

Uncaught TypeError: Illegal invocation jquery.js:4
f.extend.param.e jquery.js:4
cc jquery.js:2
cc jquery.js:2
cc jquery.js:2
cc jquery.js:2
cc jquery.js:2
cc jquery.js:2
f.extend.param jquery.js:4
f.extend.ajax jquery.js:4
f.each.f.(anonymous function) jquery.js:4
(anonymous function) functions.wizard.js:53
f.event.dispatch jquery.js:3
f.event.add.h.handle.i jquery.js:3
POST http://localhost:8084/ScrumManager/projeto/step5 400 (Bad Request) 

Olhando por cima eu não vejo nada errado. A lista de ids no javascript está correta? Você consegue imprimi-lá normalmente?

Poste o conteúdo da requisição, você pega ele na aba Network do console de depuração do chrome (a request ficará em vermelho), é o parâmetro Request URL.

Bom dia, fiz o teste e o que tem na guia Network é:

Request URL:http://localhost:8084/ScrumManager/projeto/step5
Request Method:POST
Status Code:400 Bad Request
Request Headersview source
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:pt-BR,pt;q=0.8,en-US;q=0.6,en;q=0.4
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:0
Content-Type:application/x-www-form-urlencoded
Cookie:JSESSIONID=D920AC84AF70EF99DAA0BADBAF73B304
Host:localhost:8084
Origin:http://localhost:8084
Referer:http://localhost:8084/ScrumManager/projeto/step4
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11
Response Headersview source
Connection:close
Content-Length:971
Content-Type:text/html;charset=utf-8
Date:Wed, 15 Aug 2012 11:47:50 GMT
Server:Apache-Coyote/1.1

Logo abaixo, em Form Data, não aparece nada?

Começou a andar o negócio… Ainda estou pipocando, mas começou a andar… Tirei o esquema de formulário com submissao (submit) e passei a fazer funcionar com o click no botão. Acredito que não era isto que estava fazendo dar erro 400, mas mudei opara fazer testes…

Bom, fiz no meu teste, eu troquei meu codigo JavaScritp De:

var ids = $('a.checked').map(function(element, index) {    
    return $(this).attr('id');
});    
    
var params = { team : ids };    
       
$.post('step5', params)
            .success( function() {
                alert('SUCCESS');
            })
            .error( function(error) {
                alert('ERROR');
            });
});

Para:

var ids = $('a.checked').map(function(element, index) {    
    return $(this).attr('id');
});    
    
var params = { team : ['1','2'] };    
       
$.post('step5', params)
            .success( function() {
                alert('SUCCESS');
            })
            .error( function(error) {
                alert('ERROR');
            });
});

E deu certo! Meu controller recebeu os dois ids. FIquei empolgado e tentei passar assim:

var params = { team : [{id: 1}, {id: 2}] }; 

Mudei os parametros do meu controller de @RequestParam(“team[]”) List team para:

@RequestParam("team[]") List<UserVO> team

Só que daí voltou a dar erro 400.

Como eu devo fazer para passar um List de objetos corretamente? Ou terei de pegar os id’s na forma string e ficar criando na mão os objetos?

Pra meapear pra um objeto use o ModelAttribute como tu faz lá em cima (acredito que com o ProjetoVO).

Não entendi direito Wagner… Mudei meu Javascript para isto:

var params = 
            { 
                projeto: {
                    team : [{'id': 1},{'id':2}]
                }
            }

E meu COntroller para isto:

@RequestMapping(value = "/projeto/step5", method = RequestMethod.POST) 
    public String step5(@ModelAttribute("projeto") ProjetoVO projeto, ModelMap modelMap) {  
        System.out.println("-- > ");
        modelMap.addAttribute("sessao", new SessaoManagedBean());    
        return "projeto/step5";
    }

É isto ? Se for isto, eu testei e chega um objeto com todos os atributos nulos.