Index identification changes before time JS

1
Hello everyone, I have a problem, next I need to create an element and send it via ajax, wait for it to perform (done / fail) and execute an action on that element, but sending and creating elements can be done several times in a row. the return of the ajax takes different times according to the size of the element sent. The error occurs when performing the action on the element after the ajax return, since the element identifier may already have changed if another element was sent then I have this problem within 3 locations, within the done and fail the solution will be the same for both and I have this error inside a setTimeOut that is within always.

Follow the code link / plugin: (note: it's the same as the code I put here below in the post) link

Code: (note: error simulation)

// elemento é um elemento do DOM

(function($){
  $.fn.plug = function(optionsPlugin) {
    var plugin = this;

    var defaultsPlugin = {
      debug	: false
      elementos : true
    };

    confPlugin = $.extend(true, defaultsPlugin, optionsPlugin);

    var defaultsEnvio = {
      ajax : {
        type		: 'post',
        url			: '',
        data		: {}				
      }
    };


    if (confPlugin.elementos) {

      var idElement = 0;
      var elementos = new Array();
      // elemento é um elemento do DOM
      var criaElemento = function() {
        elementos.push('novo elemento com id: '+ idElement );
      };
      // elemento é um elemento do DOM
      var removeElemento = function(elemento){
        elemento.fadeOut(1000, function() {
          elemento.remove();
        });
      };
      // elemento é um elemento do DOM
      var statusElemento = function(elemento, status){

        if (status)
          elemento.addClass('ele-sucesso');
        else
          elemento.addClass('ele-erro');
      };
    };

    var enviaAjax = function(){

      $.ajax(
        confEnvio.ajax
      )
      .done(function(res) {
        if (confPlugin.elementos)
          statusElemento(elementos[idElement], true);
      })
      .fail(function(res) {
        if (confPlugin.elementos)
          statusElemento(elementos[idElement], false);
      })
      .always(function() {
        if (confPlugin.elementos)
          setTimeout(
            function(){
              removeElemento(elementos[idElement]);
            }
            , 2000
          );
      });
    };

    var confEnvio = {};

    plugin.enviar = function(optionsEnvio){

      confEnvio = $.extend(true, defaultsEnvio, optionsEnvio);

      if (confPlugin.elementos)
        criaElemento();

      enviaAjax();

      idElement++;
    };

    return this.each(function() {
      return plugin;
    }); 
  }; 
})(jQuery);

var enviaLa = $.fn.plug({debug:true});

enviaLa.enviar();
enviaLa.enviar();

I want to release this plugin in my Github, when I do this I will put the link here in this post.

    
asked by anonymous 05.10.2015 / 17:21

3 answers

2

Although the answer from @Sergio points out the problem, I believe it does not solve it, after all the id used in the internal scope is the same as the external scope.

I tried to simulate your problem using a setTimeout to simulate the AJAX request.

var btAdicionar = document.getElementById("btAdicionar");
var tmplLinha = document.getElementById("tmplLinha");
var tabContent = document.getElementById("tabContent");
var indice = 1;

btAdicionar.addEventListener("click", function (event) {
  var linha = tmplLinha.content.cloneNode(true);
  var input = linha.querySelector(".input");
  var output = linha.querySelector(".output");

  input.parentNode.dataset.indice = indice;
  input.textContent = indice;
  window.setTimeout(function () {
    output.textContent = indice;
  }, 2000);   

  tabContent.appendChild(linha);
  indice++;
});
<input id="btAdicionar" type="button" value="adicionarLinha" />
<table>
  <thead>
    <tr>
      <td>Input</td>
      <td>Output</td>
    </tr>
  </thead>
  <tbody id="tabContent">

  </tbody>
</table>

<template id="tmplLinha">
  <tr>
    <td class="input"></td>
    <td class="output"></td>
  </tr>
</template>

The result we expect is that the value of the input is equal to the output, but since the index is belongs to a broader scope, it ends up being updated before the output can be set.

In this case we then create a context for each request:

var btAdicionar = document.getElementById("btAdicionar");
var tmplLinha = document.getElementById("tmplLinha");
var tabContent = document.getElementById("tabContent");
var indice = 1;

var adicionarLinha = function (indice) {
  var indice = indice;
  var linha = tmplLinha.content.cloneNode(true);
  var input = linha.querySelector(".input");
  var output = linha.querySelector(".output");

  input.parentNode.dataset.indice = indice;
  input.textContent = indice;

  window.setTimeout(function () {
    output.textContent = indice;
  }, 2000);   

  tabContent.appendChild(linha);
}

btAdicionar.addEventListener("click", function (event) {
  adicionarLinha(indice);
  indice++;
});
<input id="btAdicionar" type="button" value="adicionarLinha" />
<table>
  <thead>
    <tr>
      <td>Input</td>
      <td>Output</td>
    </tr>
  </thead>
  <tbody id="tabContent">

  </tbody>
</table>

<template id="tmplLinha">
  <tr>
    <td class="input"></td>
    <td class="output"></td>
  </tr>
</template>

Note that within the AddLine method, I am declaring the index variable again, so it now has a value other than the external index variable.

(function($){ 
  var enviarElemento = function (id) {
    var id = id;
    $.ajax(
      // configurações do ajax
    )
    .done(function(res) {
      fazAlgo(elementos[id], true);
    })
    .fail(function(res) {
      fazAlgo(elementos[id], false);
    })
    .always(function() {
      setTimeout(
        function(){
          removeElemento(elementos[id]);
        }, xTempo);
    });
  }
  
  $.fn.plug = function() {

    var elementos = new Array();
    var id = 0;
    var xTempo = 2000;

    elementos.push('novo elemento e id: ' + id);
    enviarElemento(id);
    id++; // sou obrigado a encrementar pois cada elemento tem que ter um identificador diferente

    var plugin = this;
    return this.each(function() {
      return plugin;
    }); 
  }; 
})(jQuery);
    
05.10.2015 / 18:37
1

I suggest creating an object that stores this information and is in a protected scope to store the right element with the right ID. Something like this:

(function ($) {

    $.fn.plug = (function () {
        var worker = {}; // aqui fica em memória e vai recebendo IDs
        var id = 0;      // aqui e não dentro da função para não ser reiniciado como 0
        var elementos = new Array(); // acho que até isto devia estar fora, mas nõ sei como é o resto do teu código
        return function () {

            var xTempo = 2000;

            elementos.push('novo elemento e id: ' + id);
            worker[id] = elementos[id];
            id++;
            $.ajax( /* confi.*/ ).done(function (res) {
                fazAlgo(worker[id], true);
            }).fail(function (res) {
                fazAlgo(worker[id], false);
            }).always(function () {
                setTimeout(function () {
                    removeElemento(worker[id]);
                }, xTempo);
            });

            // sou obrigado a encrementar pois cada elemento tem que ter um identificador diferente

            var plugin = this;
            return this.each(function () {
                return plugin; // o que faz esta linha?
            });
        }
    })();
})(jQuery);

jsFiddle: link

    
05.10.2015 / 18:15
0

EDIT 1

You can instead of incrementing the id, generate a random number to identify the item. See:

$.fn.plug = function() {

    /* Aqui gera o id aleatório */
    var id = String(Math.random() + 1);
    var elementos = new Array();
    elementos.push(id);

    $.ajax(
        // configurações do ajax
    ).done(function(res) {

        fazAlgo(elementos[id], true);
    }).fail(function(res) {

        fazAlgo(elementos[id], false);
    }).always(function() {
        setTimeout(
            function(){
                removeElemento(elementos[id]);
            }, xTempo);
        });

        var plugin = this;
        return this.each(function() {
            return plugin;
        }); 
    }; 
})(jQuery);

EDIT 2

Do the following then:

var ajaxManager = (function() {
     var requests = []; /* Array de requisicoes */

     return {
        addReq:  function(opt) { /* Adiciona requisicao */
            requests.push(opt);
        },
        removeReq:  function(opt) { /* Remove requisicao */
            if( $.inArray(opt, requests) > -1 )
                requests.splice($.inArray(opt, requests), 1);
        },
        run: function() {
            var self = this,
                oriSuc;

            if(requests.length) {
                oriSuc = requests[0].complete;

                requests[0].complete = function() {
                     if( typeof(oriSuc) === 'function' ) oriSuc();
                     requests.shift();
                     self.run.apply(self, []);
                };   

                $.ajax(requests[0]); /* Processa requisicao */
            } else {
              self.tid = setTimeout(function() {
                 self.run.apply(self, []);
              }, 1000);
            }
        },
        stop:  function() { /* Para requisicao */
            requests = [];
            clearTimeout(this.tid);
        }
     };
}());

In the call:

$(function() {
    ajaxManager.run(); 

    $("a.button").click(function(){ /* A cada click adiciona uma requisicao */
       ajaxManager.addReq({
           type: 'POST',
           url: 'whatever.html',
           data: params,
           success: function(data){
              // do stuff
           }
       });
    });
});

You can adapt as needed.

Source: link

EDIT 3

This example I believe is what you need:

var ajaxManager = {
    arr : [],
    add : function(param, fn){
        var id = String(Math.random() + 1);
        arr.push({run : run(param, fn, id), id : id});
    },

    run : function(param, fn, id){
        $.ajax(param).done(function (res) {
            fn(res); 
            rem(id);
        }).fail(function (res) {
            fn(res); 
            rem(id);
        });
    }

    rem : function(id){
        $.each(arr, function(index, item) {
            if(id === item.id)
                delete arr[index];
        });
    }
};

Parameter query data and finally a callback function.

    
05.10.2015 / 18:10