Solucionado (ver solução)

Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

Solucionado
(ver solução)
12
respostas

Refatoração do meu código sortable

Estou implementando um sorteable que apresenta três colunas e nessas colunas respectivos itens que pode ser trocado de coluna, a gosto do usuário. Esse dados pego no json via ajax que atualiza as colunas, quando é feito o sorteio a nova ordem é salva automaticamente via ajax novamente. Acontece que estou tendo o retrabalho por inexperiência de ter que tratar esses dados quando recebo o objeto via ajax inseri-los nos seus devidos lugares com seus valores, e novamente quando o sorteio é feito, dessa vez pegando os dados dos campos para tratá-los e devolvê-los quando é feito o update no sortable. Minha pergunta é será que o meu código pode ser refatorado? Será que posso trabalhar de uma forma global só com o objeto vindo do java , tratá-lo quando for inserir e fazer o update no sorteio? Segue meu código, agradeço desde já!

<script>
    var placeholder = '<li class="span2 well placeholder tile groupLi" style="display:block;">Arraste um item aqui.</li>';

    $(document).ready(function(){

        sortable();

        function formatCurrency(){
            Number.prototype.format = function(n, x, s, c) {
                var re = '\\d(?=(\\d{' + (x || 3) + '})+' + (n > 0 ? '\\D' : '$') + ')',
                    num = this.toFixed(Math.max(0, ~~n));

                return (c ? num.replace('.', c) : num).replace(new RegExp(re, 'g'), '$&' + (s || ','));
            };
        };            

        function sortable() {

                $.ajax({

                url: 'obterGrupos',
                beforeSend: function(){},
                success: function(retorno){

                    var totalEss = 0;
                    var totalImp = 0;
                    var totalSup = 0;

                    $.each(retorno, function(index, obj){

                        formatCurrency();

                        var valorFormatado = 'R$ ' + obj.valorDespesa.format(2, 3, '.', ',');

                        var text = "<li class='list-group-item bg-blue-grey-200 ui-sortable-handle' id='" + obj.id + "'>" +
                        "<i class='icon wb-move' aria-hidden='true font-size-16'></i><span class='itemDescricao'" + obj.descricaoDespesa + 
                        "</span><span class='float-right valorItem'> "  + obj.valorDespesa + " </span></li>";

                        if(obj.grupoDespesas.nomeGrupoDespesas == "Essencial"){
                            $('ul#Essencial').append(text);
                            var idEss = $('#Essencial').children().not(".ui-sortable-helper").not(".groupLi").length;
                            $('.countEss').text(idEss);

                            var valor = obj.valorDespesa;
                            totalEss = totalEss += valor;

                        } else if(obj.grupoDespesas.nomeGrupoDespesas == "Importante"){
                            $('ul#Importante').append(text);
                            var idImp = $('#Importante').children().not(".ui-sortable-helper").not(".groupLi").length;
                            $('.countImp').text(idImp);

                            var valor = obj.valorDespesa;
                            totalImp = totalImp + valor;

                        }else{
                            $('ul#Superfluo').append(text);
                            var idSup = $('#Superfluo').children().not(".ui-sortable-helper").not(".groupLi").length;
                            $('.countSup').text(idSup);

                            var valor = obj.valorDespesa;
                            totalSup = totalSup + valor;
                        }
                    });

                    $('.valorEssencial').text('R$ ' + totalEss.format(2, 3, '.', ','));
                    $('.valorImportante').text('R$ ' + totalImp.format(2, 3, '.', ','));
                    $('.valorSuperfluo').text('R$ ' + totalSup.format(2, 3, '.', ','));

                },
                error: function(){}
            });

            $( "#Essencial, #Importante, #Superfluo" ).sortable({

                items: "li:not(.placeholder)",
                connectWith: ".connectGroup",
                cursor: 'move',
                cursorAt: { left: 20 },
                tolerance: 'pointer',
                revert: 'invalid',
                placeholder: 'span2 well placeholder tile',

                start: function(event, ui) {

                },

                receive: function(event, ui) {

                    var urlData =  ui.sender[0].id + '/' +  this.id + '/' +  ui.item[0].id;

                    $.get( "despesasNomeId/" + urlData)
                    .fail(function( urlData ) {
                    })
                    .always(function( urlData ) {
                        console.log("Tudo certo");
                    });
                },

                update : function( event, ui){          
                    var idEss = $('#Essencial').children().not(".ui-sortable-helper").not(".groupLi").length;
                    $('.countEss').text(idEss);
                    var idImp = $('#Importante').children().not(".ui-sortable-helper").not(".groupLi").length;
                    $('.countImp').text(idImp);
                    var idSup = $('#Superfluo').children().not(".ui-sortable-helper").not(".groupLi").length;
                    $('.countSup').text(idSup);


                    if($('#Essencial').children().length == 0){
                        $('ul#Essencial').append(placeholder);

                    } else if ($('#Importante').children().length == 0){
                        $('ul#Importante').append(placeholder);

                    }else if ($('#Superfluo').children().length == 0){
                        $('ul#Superfluo').append(placeholder);
                    }
                },

            }).disableSelection();
       };
    })
    </script>
12 respostas

Fala Clerman,

Deixa ver se eu entendi o seu problema, você tem 3 colunas. Essas colunas o seu usuário consegue alterar a ordem dos dados nelas. Quando ele reordena, os dados dessa coluna são enviados, neste nova ordem, via AJAX para o seu servidor, para que fiquem salvos.

Até essa parte eu acho que entendi.

Agora não entendi a parte do retrabalho da sua dúvida, quando eles vem de volta do AJAX você tem que colocá-los novamente na ordem enviada ?

Você consegue colocar um exemplo, pode até ser mais simplificado do problema no https://codepen.io para ficar mais claro e o pessoal aqui da Alura tentar te ajudar?

Ola Douglas Quintanilha . O código e minha aplicação esta rodando perfeitamente, o sorteio, a ordenação o envio e recebimento do servidor estão perfeitos. Acontece que quando: 1 - recebo os dados, tenho um objeto para tratar e dispor ele nos seus devidos campos. 2 - quando o usuário reordena os dados (aqui esta o problema, pois ao invés de reutilizar os dados do objeto, estou pegando os dados nos campos do Sortable), tenho que refazer o serviço de capturas dos mesmo dados para enviá-los ao servidor com a nova ordenação.

É possível pegar o objeto e reutilizá-lo no mesmo sortable? Em 'success' do ajax eu trato os dados e 'update' capto esses dados novamente para enviá-los. Será possível dentro de 'update' reutilizar o objeto que vem por ajax?

Tentei fazer o serviço mas a aplicação quebrou! Segue código atualizado (comentado):

var placeholder = '<li class="span2 well placeholder tile groupLi" style="display:block;">Arraste um item aqui.</li>';
var erroSortableTxt = '<strong>Erro</strong> ao carregar os itens, atualize a página. [F5]';
var erroSortableTxtEnvio = '<strong>Erro</strong> ao salvar os itens, atualize a página. [F5]';
var arrastarItem = 'Arrastar e soltar um item para esse grupo';

var elementoEss = $('#Essencial');
var elementoImp = $('#Importante');
var elementoSup = $('#Superfluo');

var valorEssencial = $('.valorEssencial');
var valorImportante = $('.valorImportante');
var valorSuperfluo = $('.valorSuperfluo');

$(document).ready(function(){

    sortable();

    function formatCurrency(){
        Number.prototype.format = function(n, x, s, c) {
            var re = '\\d(?=(\\d{' + (x || 3) + '})+' + (n > 0 ? '\\D' : '$') + ')',
                num = this.toFixed(Math.max(0, ~~n));
            return (c ? num.replace('.', c) : num).replace(new RegExp(re, 'g'), '$&' + (s || ','));
        };
    };            

    function sortable() {
            $('.panel-desc').html(arrastarItem);
            $('.loaderSortableBg').show();

            $.ajax({

            // aqui capturo os dados pela url
            url: 'obterGrupos',
            beforeSend: function(){},
            success: function(retorno){
                var totalEss = 0;
                var totalImp = 0;
                var totalSup = 0;

                $.each(retorno, function(index, obj){

                    formatCurrency();

                    var valorFormatado = 'R$ ' + obj.valorDespesa.format(2, 3, '.', ',');
                    var text = "<li class='list-group-item bg-blue-grey-200 ui-sortable-handle' id='" + obj.id + "'>" +
                    "<i class='icon wb-move' aria-hidden='true font-size-16'></i> " + obj.descricaoDespesa + 
                    "<span class='float-right valorItem'> "  + valorFormatado + " </span></li>";

                    if(obj.grupoDespesas.nomeGrupoDespesas == "Essencial"){
                        $('ul#Essencial').append(text);
                        var idEss = $(elementoEss).children().not(".ui-sortable-helper").not(".groupLi").length;
                        $('.countEss').text(idEss);
                        var valor = obj.valorDespesa;
                        totalEss = totalEss += valor;

                    } else if(obj.grupoDespesas.nomeGrupoDespesas == "Importante"){
                        $('ul#Importante').append(text);
                        var idImp = $(elementoImp).children().not(".ui-sortable-helper").not(".groupLi").length;
                        $('.countImp').text(idImp);
                        var valor = obj.valorDespesa;
                        totalImp = totalImp + valor;

                    } else {
                        $('ul#Superfluo').append(text);
                        var idSup = $(elementoSup).children().not(".ui-sortable-helper").not(".groupLi").length;
                        $('.countSup').text(idSup);
                        var valor = obj.valorDespesa;
                        totalSup = totalSup + valor;
                    }
                });

                $(valorEssencial).text('R$ ' + totalEss.format(2, 3, '.', ','));
                $(valorImportante).text('R$ ' + totalImp.format(2, 3, '.', ','));
                $(valorSuperfluo).text('R$ ' + totalSup.format(2, 3, '.', ','));
                $('.loaderSortableBg').fadeToggle();
            },
            error: function(){

                $('#erroSortabletxt').html(erroSortableTxt);
                $('#erroSortable').fadeToggle();
                setTimeout(function(){

                    $('.loaderSortableBg').fadeOut();
                    $('#erroSortable').fadeOut();

                }, 4000);
            }
        });

        $( "#Essencial, #Importante, #Superfluo" ).sortable({

            items: "li:not(.placeholder)",
            connectWith: ".connectGroup",
            cursor: 'move',
            cursorAt: { left: 20 },
            tolerance: 'pointer',
            revert: 'invalid',
            placeholder: 'span2 well placeholder tile',

            start: function(event, ui) {},
            receive: function(event, ui) {

                $('.loaderSortableBg').show();

                var urlData =  ui.sender[0].id + '/' +  this.id + '/' +  ui.item[0].id;

                $.get( "despesasNomeId/" + urlData)
                .fail(function( urlData ) {

                    $('#erroSortableTxtEnvio').html(erroSortableTxtEnvio);
                    $('#erroSortable').fadeToggle();

                    setTimeout(function(){
                        $('.loaderSortableBg').fadeOut();  
                        $('#erroSortable').fadeOut();
                    }, 4000);

                })
                .always(function( urlData ) {
                });
            },

            update : function( event, ui){         

                // Aqui que gostaria de saber se posso reutililzar
                // os dados que capturo acima
                // Atualiza os valores depois do update
                setTimeout(function(){

                    var totalEssPos = 0;
                    var totalImpPos = 0;
                    var totalSupPos = 0;

                    var itemEss = elementoEss.find('.valorItem');
                    var itemImp = elementoImp.find('.valorItem');
                    var itemSup = elementoSup.find('.valorItem');

                    itemEss.each(function(){
                        var valor = $(this).text();
                        var vLimpo = valor.replace('R$', '', ' ', '');
                        totalEssPos = totalEssPos += parseFloat(vLimpo);
                    });

                    itemImp.each(function(){
                        var valor = $(this).text();
                        var vLimpo = valor.replace('R$', '', ' ', '');
                        totalImpPos = totalImpPos += parseFloat(vLimpo);
                    });

                    itemSup.each(function(){
                        var valor = $(this).text();
                        var vLimpo = valor.replace('R$', '', ' ', '');;
                        totalSupPos = totalSupPos += parseFloat(vLimpo);
                    });

                    $(valorEssencial).text('R$ ' + totalEssPos.format(2, 3, '.', ','));
                    $(valorImportante).text('R$ ' + totalImpPos.format(2, 3, '.', ','));
                    $(valorSuperfluo).text('R$ ' + totalSupPos.format(2, 3, '.', ','));

                    var idEss = $(elementoEss).children().not(".ui-sortable-helper").not(".groupLi").length;
                    $('.countEss').text(idEss);
                    var idImp = $(elementoImp).children().not(".ui-sortable-helper").not(".groupLi").length;
                    $('.countImp').text(idImp);
                    var idSup = $(elementoSup).children().not(".ui-sortable-helper").not(".groupLi").length;
                    $('.countSup').text(idSup);

                    // Add mensagem do placeholder
                    var detectaPlaceEss  = $(elementoEss).children();
                    var detectaPlaceEss2 = $(elementoEss).children('.placeholder');                    
                    var detectaPlaceImp  = $(elementoImp).children();
                    var detectaPlaceImp2 = $(elementoImp).children('.placeholder');                    
                    var detectaPlaceSup  = $(elementoSup).children();
                    var detectaPlaceSup2 = $(elementoSup).children('.placeholder');

                    if(detectaPlaceEss.text() != detectaPlaceEss2.text()){
                        detectaPlaceEss2.slideUp('fast');
                    }else{
                        detectaPlaceEss2.slideDown('fast');
                    }

                    if(detectaPlaceImp.text() != detectaPlaceImp2.text()){
                        detectaPlaceImp2.slideUp('fast');
                    }else{
                        detectaPlaceImp2.slideDown('fast');
                    }

                    if(detectaPlaceSup.text() != detectaPlaceSup2.text()){
                        detectaPlaceSup2.slideUp('fast');
                    }else{
                        detectaPlaceSup2.slideDown('fast');
                    }

                    if($(elementoEss).children().length == 0){
                        $('ul#Essencial').append(placeholder);

                    } else if ($('#Importante').children().length == 0){
                        $('ul#Importante').append(placeholder);

                    }else if ($('#Superfluo').children().length == 0){
                        $('ul#Superfluo').append(placeholder);
                    }
                }, 400);

                $('.loaderSortableBg').fadeToggle();
            },

        }).disableSelection();
   };
})

Segue o link rodando na web: https://codepen.io/klermann/pen/vmomjd

Só não esta salvando!

Agora entendi melhor Clerman,

É que a página é carregada, você trata os dados , calcula o total e exibe os campos na página. Porém quando o usuário altera alguma das colunas, você tem que refazer esta lógica de pegar os dados e somar o total, para que as colunas sejam atualizadas com o novo valor da sua lista.

Sim, é possível que você tenha um único objeto que contêm os dados da sua aplicação e que ele seja atualizado no update, mas para você chegar neste ponto, você tem que fazer uma separação melhor do código.

Por exemplo, a função que busca inicialmente os dados, a success do AJAX. Ela é uma função busca os dados, trata os dados, soma o total, atualiza a view com os novos dados . Olha só quanta coisa só quantas responsabilidades diferentes só esta função está fazendo!

O ideal seria você quebrar esta função em funções menores:

  • Uma que pega os dados e retorna os dados tratados.

  • Outra que pega esses dados tratados e coloca eles em um objeto javascript.

  • Outra que pega esse objeto Javascript e exibe ele na View

  • Outra que calcula o total a partir deste objeto javascript
  • Outra que escreve o total na view

Sacou ? Assim você vai ter uma representação do seus dados ( o objeto javascript) separado das funções que atualizam a view.

Ai quando a função update rodar, o que ela vai fazer? Chamar uma função para atualizar o objeto javascript com os novos valores e chamar a função que atualiza os totais.

Acho que você separando melhor o seu código em pequenas funções vai ser mais fácil diminuir este retrabalho que você está tendo nas duas funções.

Olá Douglas vou seguir sua orientação a risca, mas vou precisar de um força. Como posso passar o objeto recuperado dentro uma função par outra função? Por exemplo na função obterDados() pego o obj da url via ajax e na função recuperaDados(), como passo esse parâmetro?

function recuperaDados(){
        obterDados();
        console.log("========== " + obj);
    }

    function obterDados(obj){
        $.ajax({

            // aqui capturo os dados pela url
            url: 'obterGrupos',
            beforeSend: function(){},
            success: function(retorno){

                getObj = retorno;
                console.log(getObj);

                var totalEss = 0;
                var totalImp = 0;
                var totalSup = 0;

                $(valorEssencial).text('R$ ' + totalEss.format(2, 3, '.', ','));
                $(valorImportante).text('R$ ' + totalImp.format(2, 3, '.', ','));
                $(valorSuperfluo).text('R$ ' + totalSup.format(2, 3, '.', ','));
                $('.loaderSortableBg').fadeToggle();

                console.log(retorno);
                return retorno;
            },
            error: function(){                $('#erroSortabletxt').html(erroSortableTxt);
                $('#erroSortable').fadeToggle();
                setTimeout(function(){

                    $('.loaderSortableBg').fadeOut();
                    $('#erroSortable').fadeOut();

                }, 4000);
            }
        });
    }

Olá Douglas. Como posso passar o objeto recuperado dentro uma função par outra função? Por exemplo na função obterDados() pego o obj da url via ajax e na função recuperaDados(), como passo esse parâmetro?

Olá Douglas, to no aguardo, obrigado!

Oi Clerman, perdão a demora, estou testando aqui um exemplo para o seu caso e já posto aqui.

Ok Douglas, to no aguardo!

solução!

Então Clerman,

Para o seu caso, como o código de buscar os dados é assíncrono, você vai precisar entender como funciona as Promises, afinal não tem como saber se o seu servidor vai demorar 10 ms para retornar o valor ou 1s.

Além das funções de callback que o jQuery executa quando faz uma requisição com sucesso, ele também pode nos devolver uma promise.

Vou mostrar em um exemplo abaixo:

 function obtemDados(){
     var promise = $.ajax("https://api-pacientes.herokuapp.com/pacientes");    
}

Esse objeto do Javascript é especial e feito para lidar com coisas assíncronas. Ele tem uma função then() que aceita dois parâmetros, o primeiro sendo uma função de sucesso , quando a requisição ocorre tudo bem, e o segundo quando a função falha.

function obtemDados(){
    var promise = $.ajax("https://api-pacientes.herokuapp.com/pacientes");    

    //Retorno fora da função AJAX
    promise.then(function(retorno){                    
        console.log(retorno);
    }, function(){

        console.log("Deu problema na requisição");
    })        
}

Ou seja, agora você pode ter acesso ao retorno da sua função fora do $.ajax do jQuery.

Para você entender um pouco melhor sobre as Promises e até mesmo como estruturar melhor o seu código Javascript, recomendo que você faça os cursos de Javascript Avançado aqui da Alura, eles vão te dar uma boa base sobre a separação de camadas em aplicação feita em Javascript , e tudo que você precisa saber como trabalhar com assíncronismo no Javascript.

Os cursos são com o excelente Flávio Almeida e você pode assisti-los nos link abaixo:

https://www.alura.com.br/curso-online-javascript-es6-orientacao-a-objetos-parte-1

https://www.alura.com.br/curso-online-javascript-es6-orientacao-a-objetos-parte-2

https://www.alura.com.br/curso-online-javascript-es6-orientacao-a-objetos-parte-3

Ok Douglas vou seguir sua recomendação e fazer os cursos! Obrigado pela direção!

Olá Douglas, não refatorei o meu código ainda, mas agora vejo que ele esta me trazendo os valores (o total dos itens) errado quando faço o sorteio, ou seja, os dados vem do banco e são apresentados normalmente, por exemplo: Total R$ 2.400,00 e quando faço o sorteio Total R$ 2,40, pode dar o uma dica?