views:

88

answers:

3

I'm no javascript wiz, but am a bit puzzled as to how this is working in three major browsers, but not Safari... is there something wrong with this code? Basically I'm just using this in conjunction with a php/mysql callback at the given url to track link clicks.

Drupal.behaviors.NodeDownloadCounter = function() {

    $('a.ndc-link').click(function() {
        $.post('http://www.pixeledmemories.com/node-download-counter/log/' + this.name);
        return true;
    });

};

Using Drupal behaviors here instead of

$(document).ready(function() {

(correct Drupal method) but I've tried it both ways and it doesn't make a difference.

I've also tried removing "return true", but with no effect.


Okay, further testing reveals that having the click trigger an alert DOES work in Safari:

$('a.ndc-link').click(function() {
    alert('testing (ignore)');
    $.post('http://www.pixeledmemories.com/node-download-counter/log/' + this.name);
    return true;
});

But still nothing being logged to mysql. Here is my callback function:

function node_download_counter_log($nid)
{
    global $user;
  $timestamp = time();
    $title = db_result(db_query("SELECT title FROM {node} WHERE nid = %d", $nid));

  db_query("INSERT INTO {node_download_counter} (nid, title, download_count, last_download, last_uid) VALUES (%d, '%s', %d, %d, %d) 
                    ON DUPLICATE KEY UPDATE download_count=download_count+1, last_download = %d, last_uid = %d", $nid, $title, 1, $timestamp, $user->uid, $timestamp, $user->uid);

  db_query("INSERT INTO {node_download_counter_log} (nid, title, uid, timestamp) VALUES (%d, '%s', %d, %d)", $nid, $title, $user->uid, $timestamp);

}
+2  A: 

Sounds like the problem is the browser is changing the page before the data post can be finished. You can try adding return false to see if it starts working then. If it does, you are going to need to add a short delay before following the link.

UPDATE: Since it works try adding the following before "return true;"

if(jQuery.browser.safari){
  setTimeout("window.location.href= '"+this.href+"'",500);
  return false;
}
Aaron Harun
Thanks so much Aaron! Adding "return false;" does indeed make the code work in Safari... except that the link is of course not actually followed. How exactly should I add a delay to make this work? Also, isn't it a bit weird that Safari seems to deal with posting the information differently than Firefox/Chrome/IE? It does seem odd to have to manually add a delay just for the one browser (when it's obviously preferable to have the page load as fast as possible).
Jordan Magnuson
You can selectively add the timeout for just Safari using jQuery.browser.safari. See my edit above. Using Reigel's solution will delay the unloading in all browsers.
Aaron Harun
@Aaron - yes `setTimeout` might work... but to be sure, the OP could use the `callback` of `$.post()`... the `callback` will run after the request of the link in `$.post()` finishes... and jQuery.browser is now somewhat deprecated ;)
Reigel
@Reigel, and strand all browsers until the post is finished instead of the one with the problem.
Aaron Harun
@Aaron - yes it will in all browsers... but it will not be that much...
Reigel
Unless there is a server error or the database is slow; in which case, it will hang a long time making it impossible for the user to move to another page.
Aaron Harun
Hm... works with the "return false" statement in place, but then the link is not followed. When I remove "return false," it stops working again (ie the setTimeout statement itself doesn't seem to be having the desired effect). Have tried with and without return true at end, but still not working.
Jordan Magnuson
Sorry, typo-d. Try it now.
Aaron Harun
Thanks Aaron, that works great! I like the the simplicity of this solution, and the fact that it deals with the problem browser specifically, without messing with the others. Will probably be my accepted answer, unless someone can explain to me why one of the other solutions is better. Thanks again!
Jordan Magnuson
Browser detection is not a good practice in JavaScript. What about other browsers using WebKit/JavaScriptCore ? What about other versions of Firefox/Chrome on other OSes ? Better stick with a generic solution that should work everywhere (because the timing issue could happen everywhere too). To shorten the load delay and ensure page load in cases of error during the POST request, you may uses $.ajax instead of $.post. This way you can setup a complete callback called whatever the request result is. And use a short timeout to force the execution even if the POST request takes too long.
mongolito404
That logic makes sense mongolito. I like your idea of "To shorten the load delay and ensure page load in cases of error during the POST request, you may uses $.ajax instead of $.post" (part of why I like Aaron's solution) but I'm afraid I am just really ignorant when it comes to javascript. If you could show me exactly how to implement what you're talking about I would really appreciate it, but you've already been a huge help.
Jordan Magnuson
+1  A: 

Okay, based on our conversation on comments above, try

$('a.ndc-link').click(function() {
    var href = this.href;
    $.post('http://www.pixeledmemories.com/node-download-counter/log/' + this.name,
       function() {
           window.location.href = href;
       }
    );
    return false;
});
Reigel
And thank you for your solution Reigel, and for all the time you've taken! It works just fine. Hard to know which solution to go with, as all three suggestions work. I think I'm going to go with Aaron's, because of the simplicity, and the fact that it deals with Safari individually, but if you can explain why your solution is better, I'm all ears. Thanks again!
Jordan Magnuson
nahh... simplicity is beauty.. you can then change to these other solutions when you met a problem on that one.. :) cheers! but I still say mine is good...
Reigel
+1  A: 

Firs,t you have to be careful not to attach your handler more than once to each 'a.ndc-link', one way to do it is to tag the elements with a custom class.

Drupal.behaviors.NodeDownloadCounter = function() {
    $('a.ndc-link:not(.node-download-counter-processed)').addClass('node-download-counter-processed').click(function(event) {
        // ...
    });
};

One reason I see for this not to work is that, because it closes the page to open the link target, Safari will cancel the $.post request before it is actually sent to the server. Returning false and calling event.preventDefault (event being the first argument of your event handler) should prevent this from happening but will also prevent the browser to actually load the link's target. One way to solve this is to defer the page change until the POST request is complete.

Drupal.behaviors.NodeDownloadCounter = function() {
    $('a.ndc-link:not(.node-download-counter-processed)').addClass('node-download-counter-processed').click(function(event) {
        var link = this;
        event.preventDefault();
        $.post('http://www.pixeledmemories.com/node-download-counter/log/' + this.name, function() {
          window.location.href = link.href;
        });
        return false;
    });
};

But this will only works if there is no error in the POST request.

A better solution would be to hijack the server-side handler for the link target to add the click logging and then call the original handler.

mongolito404
FYI, `event.preventDefault();` are just almost the same with `return false`... you don't have to do both (in this problem)...
Reigel
Thanks for making the point about attaching the handler more than once--completely forgot about that! And thank you for your solution! It works just fine. Hard to know which solution to go with, as all three suggestions work. I think I'm going to go with Aaron's, because of the simplicity, and the fact that it deals with Safari individually, but if you can explain why your solution is better, I'm all ears. Thanks again!
Jordan Magnuson