Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

rails jQuery mobile application submitting forms and gets twice

I am building rails 4 jQuery mobile application but I find that many times, not all, forms are being submitted twice, resulting in double insertion of records. Both in development and production.

I have tried removing UJS and turbolinks but doesn't make any difference. If I disable ajax, my application won't apply the jQuery css to the returned view unless the browser is refreshed.

My javascript files are not being called twice and no assets are installed in public/assets. I have not precompiled any assets.

I wonder if this could be a problem of using a mouse instead of touch screen on a mobile? The application, while only using JQM needs to be usable from a desktop browser as well.

The issue appears to be related to using the same web actions repeatedly, for example, adding sale items to a bill or selecting a submenu item after a menu item in separate web actions.

application.js

//= require jquery
//= require jquery_ujs
//= require clubapp
//= require jquery.mobile
//= require turbolinks

gem file

gem 'rails', '4.0.0.rc2'
gem 'jquery-rails'
gem 'jquery_mobile_rails'
like image 271
markhorrocks Avatar asked Jun 16 '13 18:06

markhorrocks


2 Answers

It could be that some of your Javascript listeners are getting attached twice. For example, let's say you have the following sale item element:

<div class="sale-item">
  <!-- inputs -->
  <button class="add">Add Sale Item</button>
</div>

And you have a dynamic list of sale items that you keep on appending dynamically via javascript. But at each addition, you do the following code:

// When adding a new sale item:
    $(".sale-item .add").click(function() {
      // ajax request
    });

So whenever you add a new sale item, a listener is created for $(".sale-item .add"), which actually includes all submit items in the page. Then what happens is:

  • 1st Time: Sale Item 1 added, listener set
  • 2nd Time: Sale Item 2 added, listener added for both Sale Item 1 and Sale Item 2. So now Sale Item 1 has two listeners, which will cause duplicate submits

Here is a demonstration.

Confirming if there are duplicate listeners:

Once you're able to reproduce the bug, note the ID or class of the button that triggers the double-submit. Then in the Chrome/Firefox console/firebug, run:

$("<element that's triggering double-submits>").data("events")
> Object {click: Array[1]}

If it shows click: Array[1], then you have only one click listener, and nothing is wrong with your code. If it shows click: Array[2] or more, then its a code issue, you're somehow having double listeners.

Causes for Double-listeners

  • You have multiple events where the same listener is getting added (i.e. the above-said example)
  • You have two different buttons using the same/similar class (say one button has class "btn submit" and another has class as just "submit"). So the listener that you add by saying $(".submit") now gets added to both "btn submit" and "submit" buttons!

Finding the place where the duplicates are present is quite difficult, you'll have to do it manually by checking your code carefully.

like image 193
Subhas Avatar answered Oct 21 '22 01:10

Subhas


I also had problems with a JQM form submission (mostly trying to prevent it). Eventually I replaced my form submit buttons with plain <input>

A button will call a handler:

<input type="button" class="fake_submit" onclick="my_handler()" data-role="button" data-theme="e" data-icon="arrow-r" />

The handler will get the form contents, modify what is needed and call my global submitter:

My handler will be something like this (you could also on('click', '.fake_submit', function...)

myHandler = function () {
    // get the form
    var form = $(this).closest('form'),
        // custom parameter, I'm creating to route on server side
        switcher = form.find('input[name="form_submitted"]').val(),
        // which url to call
        urlToCall= "../services/form_xyz.cfc",
        // custom parameter, which method to call server-side
        method = "process",
        // override default GET
        type = "post",
        // we expect JSON
        returnformat = "JSON",
        // in case we want to changePage when we have the result
        targetUrl = "",
        // serialize the form and attach custom parameters
        formdata = form.serialize()+"&method="+method+"&returnformat="+returnformat,
        // run this function on success
        successHandler = function() {
            // for example
            switch (switcher) {
                case "login":
                    // refersh application to prevent backbutton
                    window.location = 'index.html';
                    break;
                case "logout":
                    window.location = 'login.html';
                    break;
                case "forget":
                    $.mobile.changePage('lost.html', {transition: 'fade' });
                    break;
                case "message_sent":
                    $.mobile.changePage( 'thanks.html', {transition: 'pop', role: "dialog" });
                    break;
                }
            };
    // call the global ajaxhandler  
    ajaxFormSubmit( form, urlToCall, formdata, targetUrl, successHandler, null, "", returnformat, type );
    // for good measure (and because I also started to be paranoid about awol-submits...
    return false;
    });
};

And the submitter:

var ajaxFormSubmit = 
    function ( 
        form,
        urlToCall,
        formdata,       // serialized form data
        targetUrl,      // I think I'm not using this at all... 
        successHandler, //
        dataHandler,    // true/false > does the success function require the response sent back
        errorHandler,   // function to be run on server-side errors
        returnformat,
        type            // override default GET if needed
        ){

            $.ajax({
              async: false,
              type: type == "" ? "get" : type,
              url: urlToCall,
              data: formdata,
              dataType: returnformat,
              success: function( objResponse ){
                 // I'm passing back string or object from the server
                 if (objResponse.SUCCESS == true || typeof objResponse == "string" ){
                    // we need the data returned to continue?
                    dataHandler ? successHandler( objResponse ) : successHandler();
             } else {
                    // server side error
                    if ( errorHandler !== "" ){
                        errorHandler();
                    }
                }
            },
            // ajax error  
            error: function (jqXHR, XMLHttpRequest, textStatus, errorThrown) { 
                console.log(error); 
            }
        });
    }

I did a fairly complex application and I'm doing all form submits this way. Works without errors. Let me know if you have any questions regarding the above.

like image 20
frequent Avatar answered Oct 21 '22 02:10

frequent