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!


 

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

  • http://yeastycode.com Popsana

    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.

  • http://abhijeet-muneshwar.github.io/ Abhijeet Ashok Muneshwar

    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.

  • http://blog.ekarakus.com Ergün

    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

  • http://www.vinicius-stutz.com/ Vinícius Stutz

    Nice post

  • Carlos

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

    • http://www.gajotres.net Dragan Gaić

      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));

    • http://www.gajotres.net Dragan Gaić

      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()

  • http://ahsansajjad.com Ahsan Sajjad

    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!

    • http://www.gajotres.net Dragan Gaić

      You're welcomed :)

  • 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!!

    • http://www.gajotres.net Dragan Gaić

      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