This problem is one of the most interesting jQuery errors I know. A lot of developers don’t know this; when you combine jQuery with jQuery Mobile, all hell breaks loose. Continue reading if you want to find out more.
 
 

Note: If this tutorial was helpful, need further clarification, something is not working or do you have a request for another Ionic post? Furthermore, if you don't like something about this blog, if something is bugging you, don't like how I'm doing stuff here, again leave me a comment below. I'm here to help you, I expect the same from you. Feel free to comment below, subscribe to my blog, mail me to dragan.gaic@gmail.com, or follow and mention me on twitter (@gajotres). Thanks and have a nice day!

PS. If you want my help, if possible (even if it takes you some time to do that), create a working example I can play with. Use Plunker for AngularJS based questions or jsFiddle for jQuery/jQuery Mobile based questions.


 

Table of Contents

 

Intro

 
In recent years, jQuery developers gave us several event binding functions:
 
 
Update: This page used to have embedded working examples, unfortunately, iframes heavily prolonged page loading so I moved the here. Or you can access them directly via below examples.
 
None of these functions will check if given event is already binded. Now comes the funny part, jQuery Mobile works in a different way than classic web applications. Depending on how you managed to bind your events, each time you visit certain pages it will bind them over and over again. By all means, this is not an error; it is simply how jQuery Mobile handles page events. For example, take a look at this code snippet:
 
$(document).on('pagebeforeshow','#index' ,function(e,data){    
    $(document).on('click', '#test-button',function(e) {
        alert('Button click');
    });    
});
 
Each time you visit page #index, click event will be bound to button #test-button. You can test this problem in the example below, simply move from page 1 to page 2 and back several times in a row.
 
Error Example
 
 
Thankfully, I can show you four solutions that will prevent this from ever happening.
 
Before we continue, make sure you know how and when to bind events when working on jQuery Mobile application. It’s a crucial step, first avoid using document ready when working with jQuery Mobile. Why? As application initializes for the first time, jQuery Mobile requires some time to process initial page markup, and document ready will usually trigger before this process can end. If document ready triggers before page markup is ready there’s a good chance it will try to access HTML elements not yet present in the DOM.
 

Solution 1

 
The easiest solution would be to use pageinit for event binding. If you take a look at an official documentation, you will find out that pageinit will trigger only once, just like document ready, so multiple bindings will never occur. Unfortunately, there’s one little thing not mentioned in the official documentation.
 
Pageinit will trigger only once if used in multipage template, if multi-HTML is used pageinit will trigger every time page is loaded into the DOM (more information can be found here and here).
 
$(document).on('pageinit', '#index', function(){       
    $(document).on('click', '#test-button',function(e) {
        alert('Button click');
    }); 
});
 
Working Example 1
 
Here’s a code example if jsFiffle is unavailable:
 

HTML

<!DOCTYPE html>
<html>
<head>
    <title>jQM Complex Demo</title>
    <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; minimum-scale=1.0; user-scalable=no; target-densityDpi=device-dpi"/>
    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css" />
    <script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>    
</head>
<body>
    <div data-role="page" id="index">
        <div data-theme="a" data-role="header">
            <h3>
                First Page
            </h3>
            <a href="#second" class="ui-btn-right">Next</a>
        </div>

        <div data-role="content">
            <a data-role="button" id="test-button">Click me</a>
        </div>

        <div data-theme="a" data-role="footer" data-position="fixed">

        </div>
    </div> 
    <div data-role="page" id="second">
        <div data-theme="a" data-role="header">
            <h3>
                Second Page
            </h3>
            <a href="#index" class="ui-btn-left">Back</a>
        </div>

        <div data-role="content">

        </div>

        <div data-theme="a" data-role="footer" data-position="fixed">

        </div>
    </div>    
</body>
</html>   
 

JavaScript

$(document).on('pageinit', '#index', function(){       
    $(document).on('click', '#test-button',function(e) {
        alert('Button click');
    }); 
});
 

Solution 2

 
The second solution is also an easy one. Before we bind an event, we should try to unbind it first. Every function used for event binding has a similar function used to remove related event (you will find them enumerated at the beginning of this article, every binding function comes with a related removal function). Use this solution if the first one is not suitable, mostly because it more complicated and demanding.
 
$(document).on('pagebeforeshow', '#index', function(){       
    $(document).off('click', '#test-button').on('click', '#test-button',function(e) {
        alert('Button click');
    }); 
});
 
Working Example 2
 
Here’s a code example if jsFiffle is unavailable:
 

HTML

<!DOCTYPE html>
<html>
<head>
    <title>jQM Complex Demo</title>
    <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; minimum-scale=1.0; user-scalable=no; target-densityDpi=device-dpi"/>
    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.css" />
    <script src="http://code.jquery.com/mobile/1.2.0/jquery.mobile-1.2.0.min.js"></script>    
</head>
<body>
    <div data-role="page" id="index">
        <div data-theme="a" data-role="header">
            <h3>
                First Page
            </h3>
            <a href="#second" class="ui-btn-right">Next</a>
        </div>

        <div data-role="content">
            <a data-role="button" id="test-button">Click me</a>
        </div>

        <div data-theme="a" data-role="footer" data-position="fixed">

        </div>
    </div> 
    <div data-role="page" id="second">
        <div data-theme="a" data-role="header">
            <h3>
                Second Page
            </h3>
            <a href="#index" class="ui-btn-left">Back</a>
        </div>

        <div data-role="content">

        </div>

        <div data-theme="a" data-role="footer" data-position="fixed">

        </div>
    </div>    
</body>
</html>   
 

JavaScript

$(document).on('pagebeforeshow', '#index', function(){       
    $(document).off('click', '#test-button').on('click', '#test-button',function(e) {
        alert('Button click');
    }); 
});
 
Continue to the next page

  • Wow.. nice one.

    I’ve had this error before, don’t remember ho I handled it, but I know it was tricky.
    I even tried using the native javascript “onload” to wrap my jquery events, but It still happened anyway.

    This is a really good way to work around it.

    Thanks Dragan.

  • I tried solution 2 & it was perfect. I solved my problem.

    Thank you very much for this article.

  • Keven

    Solution 2 worked for great for me! Thank you!!

  • Kamil

    Thanks for this! I used second solution and it works great! Your post is a fantastic collction of useful solutions.

  • thank you very much, you save my day, i was looking the solution for my multiple firing problems, working great..

  • amine

    Very very useful. My day is saved. Thank you

  • Janou

    I used Solution #4 which was perfect for my needs. I couldn’t figure out for the life of me why my submit button within my partial view was executing twice. This solution solved my problem easy. Thanks!

  • Mayank

    Thanks For this ..this is really great..

  • Steve

    You are a hero!!!

  • Terence

    Excellent article. Saved my day even a year after you wrote it. Thanks

  • Terence

    Thanks for this nice article. Still saved my day even a year after you wrote it. thanks

  • Nice post

  • Carlos

    Jquery now has a solution for this. Similar to solution two.
    use :
    .one()
    http://api.jquery.com/one/

    • This is only a partial solution (thou still valid one). It is viable only if we don’t mind that handler is unbound after its first invocation.

  • Said Moshref

    Whats about $(“#button”).unbind(“click”).on(“click”,function(e){ return false));

    • off() should be used with on() :

      $(“#button”).off(“click”).on(“click”,function(e){ return false));

      Take from the official jQuery website:

      As of jQuery 1.7, use of .die() (and its complementary method, .live()) is not recommended. Instead, use .off() to remove event handlers bound with .on()

  • Thanks Man! Solution 2 worked for me! you have no idea how much time i wasted searching for a solution like this. Thanks alot man!

  • Chris

    What about event.stopImmediatePropagation()?

    From jquery site:

    Keeps the rest of the handlers from being executed and prevents the event from bubbling up the DOM tree.

    Will this solve the problem?

    BTW very nice explanations!!

    • This will indeed work but in some cases and not always. That's why I didn't include it in this article.

  • Gytis Šk

    This is crazy. I am not even using standard DOM, I am triggering my own: $menu_item.trigger(‘menuclose’) and it still triggers twice. Gonna use e.handled solution. thx

  • Jacob

    Im usually making pages with a lot of refrehing divs and reloading stuff, so i have made my own simple solution which is handling all of my needs:
    $(‘myclass:not(.initialized)’).on(‘click’, function() { do something }).addClass(‘initialized’)
    then you can call your init functions all the time and not worry about multiple event bindings

    • Do you mind if I include this example as a possible solution?

  • Juan Carlos Alpizar Chinchilla

    While I’m working with the pagecontainer widget in jQuery Mobile 1.4.5, a have a pagebeforeshow event handled this way:

    $(document).on('pagecreate', function(event, ui){

    $(":mobile-pagecontainer").on('pagecontainerbeforeshow', function(event, ui){

    if (ui.toPage[0].id == 'target_specific_page'){

    console.log(ui);

    }

    });

    });

    $(document).on('pagecreate', '#specific_pages', function(){

    //stuff here

    });

    Which works but when I’m about to open the target page, it triggers the “pagecontainerbeforeshow” once per every page that was created before reaching the target page. For example if I had to navigate 4 times to get to the desired page, the console won’t show anything until I’m about to enter the desired page, but once the event triggers it logs 4 ui objects with the same info.

    Dragan Gaić Is there anything wrong with this implementation? The pagecontainer widget usage is still not clear enough for me. If there’s nothing wrong, considering I can’t use the ones that will trigger it only once, which is better between the off.on method and the event filter plugin in terms of performance?

  • Juan Carlos Alpizar Chinchilla

    While I’m working with the pagecontainer widget in jQuery Mobile 1.4.5, a have a pagebeforeshow event handled this way:

    $(document).on('pagecreate', function(event, ui){

    $(":mobile-pagecontainer").on('pagecontainerbeforeshow', function(event, ui){

    if (ui.toPage[0].id == 'target_specific_page'){

    console.log(ui);

    }

    });

    });

    $(document).on('pagecreate', '#specific_pages', function(){

    //stuff here

    });

    Which works but when I’m about to open the target page, it triggers the “pagecontainerbeforeshow” once per every page that was created before reaching the target page. For example if I had to navigate 4 times to get to the desired page, the console won’t show anything until I’m about to enter the desired page, but once the event triggers it logs 4 ui objects with the same info.

    @dragangai:disqus Is there anything wrong with this implementation? The pagecontainer widget usage is still not clear enough for me. If there’s nothing wrong, considering I can’t use the ones that will trigger it only once, which is better between the off.on method and the event filter plugin in terms of performance?

    • pagecontainer is still work in progress or at least it was. From what I can see at jQuery Mobile GitHub repository we’re talking about a dead framework. It’s no longer in development, at least since August. My advice, switch back to classic page events. I know they’re deprecated but its’ not like we’re going to see another jQuery Mobile version.

      • Juan Carlos Alpizar Chinchilla

        oh I knew it was slowing down but I didn’t know it was this bad 🙁 so there won’t be a jQuery Mobile 1.5 or the rumors about the jQuery UI fusion?

        I should also consider to move to another framework from now on; since my main usage was phonegap + jqm, do you consider react native a good option to move in?

        Thanks for the info 🙂

        • If you have a good Phonegap experience you should try Ionic + Cordova or if you still prefer jQuery then go with KenodUI + Cordova.

          On the other hand, React Native is a good choice, but you are somewhat limited. First, you’ll get a real native app but can only choose preexistent built in/3rd party components. You can’t modify them like you would with HTML5 and CSS. Second thing, unless nothing changed last 1-2 months you will be able to use it only on MacOS.

          • Juan Carlos Alpizar Chinchilla

            React is in rc for Linux and Windows so far as I’ve read but you’re right about the limitations it has, I think I’ll try with Ionic first. Are these performance wise better than jQuery Mobile? Thanks for your advice

          • I’m not a jQuery Mobile hater, but currently any other framework is faster than jQuery Mobile. Especially Ionic and OnsenUI. Though, if you mess up your code, it can became slower 🙂

    • Mechanical Man

      you are my savior.. just don’t know why it happens that way. THe 3rd one works fine for me

  • Georgi Iliev

    May the code be with you man, it’s my first true cross platform web application with tons of jquery and this “bug” was driving me crazy. Cheers

    • Georgi Iliev

      Maybe the fact that I bought a responsive theme has something to do with it, bu never the less, THANK YOU

      • You would stumble on this error sooner or later. It’s pain in the neck if you don’t know what’s happening.

  • Ramesh

    God bless you… You saved me from going crazy… If not for your post, i would have spent days to discover a solution.