Miguel G.
Miguel G.

Reputation: 355

jQuery: click() triggers twice with dynamic content

I'm basically loading contents from a database over ajax into a div. Then, clicking on of these content-pieces should reload new content.

The ajax request is initialized as a method in a class which I call at the beginning:

function Request(tag1, tag2, tag3) {
    this.tag1 = tag1,
    this.tag2 = tag2,
    this.tag3 = tag3
}
Request.prototype = {
    ajaxCategoryRequest: function () {
        $.ajax({
            type: "GET",
            url: "request2.php",
            data: "tag1=" + this.tag1 + "&tag2=" + this.tag2 + "&tag3=" + this.tag3,
            success: function (data) {
                var jdata = $.parseJSON(data);
                //Frage ausgeben
                $("h1.question").html(jdata[0]);
                //Tiles ausgeben
                for (var i = 1; i < jdata.length; i++) {
                    $("#tiles").append(
                        '<div class="category" id="' + jdata[i].type + '" data-owntag="' + jdata[i].owntag + '" data-description="' + jdata[i].description + '"><img src="' + jdata[i].imageurl + '" alt="' + jdata[i].name + '"/><div class="ctitle">' + jdata[i].name + '</div></div>'
                    );
                }
            }
        });
    }
};
var searchtype = new Request("", "", "");
searchtype.ajaxCategoryRequest();

Then clicking one of the above created div's should start a new request:

    $("#tiles").on('click', '.category', function () {
        var tag1 = "newtag";
        var tag2 = "newtag";
        var tag3 = "newtag";
        //remove old content
        $('.category').remove();
        //start new request
        var nextrequest = new Request(tag1, tag2, tag3);
        nextrequest.ajaxCategoryRequest();
    });
});

Basically everything's working, the content is loaded, and if I click on a div, it does trigger a new request, but here comes the error, it's triggered twice. So, every new loaded element appears twice.

I searched for it, and I think it's due to the printing-loop in the ajax request, binding the click function every time to the div. I read a lot about .unbind("click") and .bind() it or similar( .off() ), but nothing worked for me. Maybe there is another solution for this problem?

Upvotes: 1

Views: 1645

Answers (4)

Miguel G.
Miguel G.

Reputation: 355

I solutioned it like this

$('.category').fadeOut(200).promise().done(function(){
               //start request when animation completed
               $(".category").remove();
               var nextrequest = new Request(tag1,tag2,tag3);
               nextrequest.ajaxCategoryRequest();
           });

Upvotes: 0

Bergi
Bergi

Reputation: 664930

it's triggered twice

Actually, not really. Put your console.log("Mouseclick"); in the first line of the click listener and you will see it only once per click.

$("#tiles").on('click','.category',function(e){
   // …
   //remove old elements
   $('.category').fadeOut(200,function(){
       console.log("Mouseclick");
       $(this).remove();
       //start request when animation completed
       var nextrequest = new Request(tag1,tag2,tag3);
       nextrequest.ajaxCategoryRequest();
   });
});

The animation is your problem. The callback to fadeOut is invoked for each of the .category elements - that's why your $(this).remove() works (as this is only a single <div> element).

So you are actually starting a new request for each of the categories you have removed, which is of course not what you want. What you could do is moving the request out of the animation callback, then it will start right away. There are no race conditions, but - when the ajax is faster than 200 ms - it could happen that the new categories appear while the old ones are still fading out.

If you want to prevent such a glitch, you will need to find a way to fire the ajax after all of the animation callbacks have completed. Fortunately, there is the .promise method which does exactly that - it returns a promise which is guaranteed to resolve only once, and you can add callbacks for the end of the event queue:

$("#tiles").on('click','.category',function(e){
   console.log("Mouseclick");
   // …
   // remove old elements
   $('.category').fadeOut(200,function(){
       $(this).remove();
   }).promise().done(function() {
       // start request once when all animations completed
       var nextrequest = new Request(tag1,tag2,tag3);
       nextrequest.ajaxCategoryRequest();
   });
});

If you want to shorten the time until the ajax requests is finished, with promises you even can run the animation and the request in parallel very easily, adding a callback for when both tasks are finished:

function categoryRequest(data) {
    return $.ajax({
        type: "GET",
        url: "request2.php",
        data: {tag1: tag1, tag2: tag2, tag3: tag3},
        dataType: "json" // invokes $.parseJSON automatically
    });
}
function outputData(jdata) {
    //Frage ausgeben
    $("h1.question").html(jdata[0]);
    //Tiles ausgeben
    for (var i = 1; i < jdata.length; i++) {
         $("#tiles").append(
             '<div class="category" id="' + jdata[i].type + '" data-owntag="' + jdata[i].owntag + '" data-description="' + jdata[i].description + '"><img src="' + jdata[i].imageurl + '" alt="' + jdata[i].name + '"/><div class="ctitle">' + jdata[i].name + '</div></div>'
         );
    }
}

//Erster Aufruf
categoryRequest("", "", "").done(outputData);

// Weitere Aufrufe bei click
$("#tiles").on('click','.category',function(e){
    console.log("Mouseclick");
    //define next request variables (tags)
    var stage = $(this).attr('id');
    var owntag = $(this).data("owntag");
    var tag1 = "", tag2 = "", tag3 = ""; 
    if (stage == 'searchtype')
        tag1 = owntag;
    else if (stage == 'category')
        tag2 = owntag;
    else if (stage == 'subcategory')
         tag3 = owntag;
    else
         console.log("No valid (stage)type defined");

    var removed = $('.category').fadeOut(200,function(){
        $(this).remove();
    }).promise();
    var request = categoryRequest(tag1,tag2,tag3);
    $.when(request, removed).done(function(requestResults) {
        outputData(requestResults[0]);
    });  
});

Upvotes: 1

Anh T&#250;
Anh T&#250;

Reputation: 636

$("#tiles").off('click').on('click','.category',function(){

Upvotes: 1

Bhojendra Rauniyar
Bhojendra Rauniyar

Reputation: 85575

Use one method to trigger the function only for once.

$("#tiles").one('click','.category',function(){

Upvotes: 1

Related Questions