Search code examples
javascriptjqueryruby-on-railsturbolinkscocoon-gem

How do I prevent multiple duplicate events from firing on a dynamic form?


I have a dynamic form that allows you to add more child records as needed. I'm using the cocoon ruby gem. I got auto-save working on a form using the following code:

  <script>

  $(document).on('turbolinks:load', function() {

    var $form = $('form.autosave');
    var saving = false;

    function autoSaveForm(actionPath) {
      var data = $form.serialize();
      console.log("POST: " + actionPath);
      var request = new XMLHttpRequest();
      request.open('POST', actionPath, true);
      request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
      request.send(data);
      request.onreadystatechange = function () {
        if(request.readyState === XMLHttpRequest.DONE && request.status === 200) {
          saving = false;
        }
      };
    }

    $form.on('blur', 'input', function() {
      if (saving) {
        return; // don't do the blur action
      }
      saving = true;
      var actionPath = $form.attr('action');
      autoSaveForm(actionPath);
    });

  });

  </script>

The "blur" event is being triggered two times. Here's my console output:

POST: /user/profile VM1717:9 
POST: /user/profile VM1717:9 

I've updated my code from @samanime's suggestion to add a boolean to prevent the event being triggered 4 times. Now it is only triggered twice. Progress!

It appears the issue is with turbolinks and how I setup my JS.


Solution

  • Add a boolean to track if you're currently saving, and then don't do it again if you are.

    var saving = false;
    $form.on('blur', function () {
        if (saving) {
            return; // don't do the blur action
        }
    
        saving = true;
        // do saving stuff
    });
    

    And then you'll need to watch when you're done saving, and then set saving back to false.

    function autoSaveForm(actionPath) {
        // other stuff
        request.onreadystatechange = function () {
            if(request.readyState === XMLHttpRequest.DONE && request.status === 200) {
                saving = false;
            }
        };
    }    
    

    If you wanted to, you could also take that a step farther and have another boolean (like needsToSave) which would get marked true if you tried to save while saving, and then immediately start saving the latest stuff as soon as the first saving was done.