Solucionado (ver solução)
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?