views:

102

answers:

2

I have some event handlers that work in FF and not in Safari. Simply put, I have a list of friends, some hard-coded, some pulled in from a database. Clicking on a buddy opens a chat window... this is much like the Facebook chat system.

So in Firefox, everything works normally and as expected. In Safari, clicking on buddies that are hard-coded works fine, but clicking on buddies that are pulled in from the database doesn't pull up the chat window.

<script type="text/javascript" src="js/jQuery.js"></script>
<script type="text/javascript" src="js/chat.js"></script>
<script type="text/javascript" src="js/ChatBar.js"></script>
<script type="text/javascript" src="js/settings.js"></script>
<script type="text/javascript">
    var chat = new Chat();
    var from = <?php echo "'" .$_SESSION['userid'] . "'"; ?>;
    chat.getUsers(<?php echo "'" .$_SESSION['userid'] . "'"; ?>);
</script>

So I load all my buddies with chat.getUsers. That function is:

// get list of friends 
function getBuddyList(userName) {
    userNameID = userName;

    $.ajax({
        type: "GET",
        url: "buddyList.php",
        data: {
            'userName': userName,
            'current': numOfUsers
        },
        dataType: "json",
        cache: false,
        success: function(data) {
            if (numOfUsers != data.numOfUsers) {
                numOfUsers = data.numOfUsers;
                var list = "<li><span>Agents</span></li>";
                for (var i = 0; i < data.friendlist.length; i++) {  
                    list += "<li><a class=\"buddy\" href=\"#\"><img alt=\"\" src=\"images/chat-thumb.gif\">"+ data.friendlist[i] +"</a></li>";
                }
                $('#friend-list ul').append($(list));
            }
            setTimeout('getBuddyList(userNameID)', 1000);
        }
    });
}

buddyList.php just pulls in the Users from the database and returns an array with the user names. So the jQuery for clicking a buddy is:

// click on buddy in #friends-panel
$('#friends-panel a.buddy').click(function() {
    alert("Loaded");    
    // close #friends-panel
    $('.subpanel').hide();
    $('#friends-panel a.chat').removeClass('active');

    // if a chat window is already active, close it and deactivate
    $('#mainpanel li[class="active-buddy-tab"] div').not('#chat-box').removeAttr('id');
    $('#mainpanel li[class="active-buddy-tab"]').removeClass('active-buddy-tab').addClass('buddy-tab');

    // create active buddy chat window
    $('#mainpanel').append('<li class="active-buddy-tab"><a class="buddy-tab" href="#"></a><div id="chat-window"><h3><p id="to"></p></h3></div></li>');

    // create name and close/minimize buttons
    $('.active-buddy-tab div h3 p#to').text($(this).text());
    $('.active-buddy-tab div h3').append('<span class="close"> X </span><span class="minimize"> &ndash; </span>');
    $('.active-buddy-tab').append('<span class="close"> X </span>');

    // create chat area
    $('.active-buddy-tab div').append('<div id="chat-box"></div><form id="chat-message"><textarea id="message" maxlength="100"></textarea></form>');

    // put curser in chat window
    $('.active-buddy-tab #message').focus();

    // create a chat relationship
    return false;
});

... and the basic structure of the HTML is:

<div id="footpanel">
  <ul id="mainpanel">
    <li id="friends-panel">
      <a href="#" class="chat">Friends (<strong>18</strong>) </a>
      <div id="friend-list" class="subpanel">
        <h3><span> &ndash; </span>Friends Online</h3>
        <ul>
          <li><span>Family Members</span></li>
          <!-- Hard coded buddies -->
          <li><a href="#" class="buddy"><img src="images/chat-thumb.gif" alt="" /> Your Friend 1</a></li>
          <li><a href="#" class="buddy"><img src="images/chat-thumb.gif" alt="" /> Your Friend </a></li>
          <!-- buddies will be added in dynamically here -->
        </ul>
      </div>
    </li>
  </ul>
</div>

I'm not too sure where to begin solving this issue. I thought it might be a rendering bug or something with the DOM but I've been staring at this code all day and I'm stuck. Any ideas on why it works in FF and not in Safari? btw... I'm testing on Snow Leopard.

Thanks, Hristo

EDIT: I tried creating the jQuery event with .live() and .delegate() and the same thing happens... FF breaks and Safari remains broken. So the functionality is now the same, which is good, but the click event doesn't work. Any other ideas?

+3  A: 

Try this:

$('#friends-panel').delegate( 'a.buddy', 'click', function() {
    alert("Loaded");    
    // ...your code
});

...instead of $('#friends-panel a.buddy').click(function() {...

http://api.jquery.com/delegate/

When you call .click(function...), you are actually calling .bind('click', function...).

If it is set up to run after the DOM loads, it assigns the click handler to existing elements.

Elements added dynamically after the DOM is loaded don't benefit from this.

Using .delegate() places the click handler on the #friends-panel, and listens for clicks that take place in side of it. When one occurs, it checks to see if it was agains the a.buddy, and if so, fires the handler.

Another option would be to simply bind the click() in your success callback before (or after) you append to #friends-panel.


To bind the click handler in your success callback, first move the function into a variable:

var myfunc = function() {
    alert("Loaded");    
    // close #friends-panel
    $('.subpanel').hide();
    $('#friends-panel a.chat').removeClass('active');

    // ...and so on
}

...then in place of your current click, do this instead:

$('#friends-panel a.buddy').click( myfunc );

...then in your success callback, do this:

success: function(data) {
            if (numOfUsers != data.numOfUsers) {
                numOfUsers = data.numOfUsers;
                var list = "<li><span>Agents</span></li>";
                for (var i = 0; i < data.friendlist.length; i++) {  
                    list += "<li><a class=\"buddy\" href=\"#\"><img alt=\"\" src=\"images/chat-thumb.gif\">"+ data.friendlist[i] +"</a></li>";
                }

                   // new version
                $(list).click( myfunc ).appendTo('#friend-list ul'); 

                // old version
// off-topic, no need to create jQuery object-----v
                // $('#friend-list ul').append($(list)); 

            }
            setTimeout('getBuddyList(userNameID)', 1000);
        }

EDIT: To assign a function that requires parameter to a click handler, one way is to place the function call inside the callback function.

So instead of this:

$('#friends-panel a.buddy').click( buddyClick );

you'll do this:

// Set up a handler that calls your function------v
$('#friends-panel a.buddy').click( function() { buddyClick(parameters) } );

The other method, is to have your buddyClick return a function that uses the parameters passed in, like:

// Calling buddyClick actually returns a function
//   that can use the parameters you passed in
var buddyClick = function(parameters) {
    return function() {
       alert("Loaded");
       // do something with the parameters  
       // ...and so on
    };
};

// Calling buddyClick returns the function
$('#friends-panel a.buddy').click( buddyClick('some_parameter') );

This way, you can call buddyClick, and it will return the function that accepts parameters to be used for the hander.

A little more confusing perhaps, but a valid approach. Take your pick. :o)

patrick dw
I tried it with your proposition, but it actually broke it in FF and it still doesn't work in Safari. At least the functionality is the same in both browsers, but it still doesn't work. Any other ideas?
Hristo
@Hristo - Not sure why `delegate()` wouldn't work. Did you try my last suggestion where you bind the handler in your `success` callback? If you move the function out of the `click()` into its own variable, you can reuse it in both places. I'll update my answer to give an idea of what I mean.
patrick dw
I apologize... I have not yet tried your last suggestion. I'll go code it right now. Thanks for your help!
Hristo
@Hristo - I updated my answer. I have a feeling there's something else wrong, though. You shouldn't have the different behavior between FF and Safari. I'll be back in a couple hours.
patrick dw
@patrick... Thanks for the update. It seems to work in FF now, and it makes sense in my head. However, the Safari Error Console is telling me "ReferenceError: Can't find variable: buddyClick" and buddyClick is the variable. Also... your single line `$(list).click(buddyClick).appendTo('#friend-list ul');` isn't appending the list in either browser.
Hristo
@patrick... I tried adding the function variable inside the main page inside the <script> tags instead of in the javascript file. The "can't find variable: buddyClick" error no longer comes up and it seems to work now in both browsers. But I'm not happy with moving it out of the .js file. Any ideas why this is happening?
Hristo
@Hristo - A few things to remember. Any variables instantiated inside a function body will not be available outside. For example, if you instantiate a variable inside the `$(document).ready(function(){...})`, it will be local to that function. Also, make sure that variables are instantiated *before* you use them. May be as simple as moving them to the top of the code. Or try using `function myfunc() {}` instead of `var myfunc = function(){}` as it seems to be more forgiving. Any chance you have a public link to the site?
patrick dw
...also, I assume that the `appendTo('#friend-list ul')` started working when you got the `can't find variable buddyClick` message to go away. Is that right? I'm assuming `buddyClick` is the function.
patrick dw
Ahh... right. For some reason I was assuming that `$(document).ready()` would be global to everything because it is the "document". But that totally makes sense... the variable wasn't in scope when it was being called. Thanks... and the appendTo does work :)
Hristo
@Hristo - Glad I could help. :o)
patrick dw
@patrick... So your solution worked but I've come to place where I need to add parameters to the buddyClick and now I have a problem whenever the page loads, the function executes and brings up a chat window... this should only happen when I click on a buddy, but its happening every time I refresh the page. Would you like me to post another question about this with more info? or do you kind of understand what I'm saying? If so... any ideas why this is happening? Its as if its not waiting to be clicked... it just goes.
Hristo
@Hristo - Is it because you changed `$('#friends-panel a.buddy').click( buddyClick );` to `$('#friends-panel a.buddy').click( buddyClick( someParameter ) );` ?
patrick dw
yes that is what I did... and in the function declaration I added the parameters.
Hristo
@Hristo - The `()` are an execution operator that tells a function to execute. You can actually add them to the end of a function to make it execute immediately, like: `function() {alert("hi");}()` ...so when you added those to the handler assignment, it fired right away (as you know). There are a couple of solutions. The simplest is to simply place your function call inside a callback function. I'll post an example at the bottom of my answer in a minute.
patrick dw
@ patrick... I don't know how to thank you and I wish I could up-vote your answer like 10 times. Thanks!
Hristo
@Hristo - You're welcome. :o)
patrick dw
A: 

the way I usually do this is:

$('#friends-panel a.buddy').live('click',function() { ... });

instead of:

$('#friends-panel a.buddy').click(function() { ... });

.live allows you to bind event handlers to elements that are AJAX loaded (after DOM ready).


EDIT:

example of using console.log() to debug your issue:

// get list of friends 
function getBuddyList(userName) {
    userNameID = userName;
    console.log('userNameID: '+ userNameID); //make sure a userNameID is being passed.

    $.ajax({
        type: "GET",
        url: "buddyList.php",
        data: {
            'userName': userName,
            'current': numOfUsers
        },
        dataType: "json",
        cache: false,
        success: function(data) {
            console.log(data); //dump the returned data into the console.log
            if (numOfUsers != data.numOfUsers) {
                numOfUsers = data.numOfUsers;
                var list = "<li><span>Agents</span></li>";
                for (var i = 0; i < data.friendlist.length; i++) {  
                    list += "<li><a class=\"buddy\" href=\"#\"><img alt=\"\" src=\"images/chat-thumb.gif\">"+ data.friendlist[i] +"</a></li>";
                    console.log(list); //dump each iteration to the console but also show each addition on each iteration.
                }
                $('#friend-list ul').append($(list));
            }
            setTimeout('getBuddyList(userNameID)', 1000);
        }
    });
}

And // click on buddy in #friends-panel $('#friends-panel a.buddy').click(function() { alert("Loaded");
// close #friends-panel $('.subpanel').hide(); $('#friends-panel a.chat').removeClass('active');

// if a chat window is already active, close it and deactivate
$('#mainpanel li[class="active-buddy-tab"] div').not('#chat-box').removeAttr('id');
$('#mainpanel li[class="active-buddy-tab"]').removeClass('active-buddy-tab').addClass('buddy-tab');

// create active buddy chat window
$('#mainpanel').append('<li class="active-buddy-tab"><a class="buddy-tab" href="#"></a><div id="chat-window"><h3><p id="to"></p></h3></div></li>');

// create name and close/minimize buttons
$('.active-buddy-tab div h3 p#to').text($(this).text());
$('.active-buddy-tab div h3').append('<span class="close"> X </span><span class="minimize"> &ndash; </span>');
$('.active-buddy-tab').append('<span class="close"> X </span>');

// create chat area
$('.active-buddy-tab div').append('<div id="chat-box"></div><form id="chat-message"><textarea id="message" maxlength="100"></textarea></form>');

// put curser in chat window
$('.active-buddy-tab #message').focus();

// create a chat relationship
return false;

});

This might give you some ideas as to where it's breaking and why. You should also add console.log() throughout your click event function.

JSD
I am actually using live for other click events, but when I tried it with this specific event, it didn't work in either FF or Safari.
Hristo
odd, mind posting the code? also do you have firebug installed? if not, it's a must for any and all AJAX development (among other things too but AJAX work too).
JSD
Of course I have Firebug installed :) Which code exactly would you like me to post? If you read the other answer here... I've commented about changes I've made using his code.
Hristo
all that i can think of is a bracket not being closed somewhere or <?php ?> tags inside the .js file (which won't error on firefox but will in safari). having firebug this is what i would do. test to see where the event is being broken by placing:console.log('test complete');as an example, I would first insert this right after .click(function(){and then if it works there, move it down further and keep going until you find where it's broken. also place it through out your functions as well. I religiously use console.log() with firebug, it's a life saver I tell you!
JSD
console.log() also allows you to pass practically anything to it. try it out if you haven't used it before, it is a MUST have when debugging issues.
JSD