views:

168

answers:

1

Hello there JavaScript and Jquery gurus, I am getting and then displaying list of a facebook user's friend list by using the following code:

  <script>
function getFriends(){
var theword = '/me/friends';
FB.api(theword, function(response) {
  var divInfo = document.getElementById("divInfo");
  var friends = response.data;
  divInfo.innerHTML += '<h1 id="header">Friends/h1><ul id="list">';
  for (var i = 0; i < friends.length; i++) {
    divInfo.innerHTML += '<li>'+friends[i].name +'</li>';
   }  
  divInfo.innerHTML += '</ul></div>';  
 });
} 

</script>

<a href="#" onclick="getFriends(); return false;">graph friends</a> 
<div id = divInfo></div>

Now, in my Facebook integrated website, I would eventually like my users to choose their friends and send them gifts/facebook-punch them..or whatever. Therefore, I am trying to implement a simple Jquery filter using this piece of code that manipulates with the DOM

     <script> 

(function ($) {
  // custom css expression for a case-insensitive contains()
  jQuery.expr[':'].Contains = function(a,i,m){
      return (a.textContent || a.innerText || "").toUpperCase().indexOf(m[3].toUpperCase())>=0;
  };


  function listFilter(header, list) { // header is any element, list is an unordered list
    // create and add the filter form to the header
    var form = $("<form>").attr({"class":"filterform","action":"#"}),
        input = $("<input>").attr({"class":"filterinput","type":"text"});
    $(form).append(input).appendTo(header);

    $(input)
      .change( function () {
        var filter = $(this).val();
        if(filter) {
          // this finds all links in a list that contain the input,
          // and hide the ones not containing the input while showing the ones that do
          $(list).find("a:not(:Contains(" + filter + "))").parent().slideUp();
          $(list).find("a:Contains(" + filter + ")").parent().slideDown();
        } else {
          $(list).find("li").slideDown();
        }
        return false;
      })
    .keyup( function () {
        // fire the above change event after every letter
        $(this).change();
    });
  }


  //ondomready
  $(function () {
    listFilter($("#header"), $("#list"));
  });
}(jQuery));

  </script> 

Now, This piece of code works on normal unordered list, but when the list is rendered by JavaScript, it does not. I have a hunch that it has to do something with the innerHTML method. Also, I have tried putting the JQuery filter code within and also right before tag. Neither seemed to work.

If anyone knows how to resolve this issue, please help me out. Also, is there a better way to display the friends list from which users can choose from?

+1  A: 

The problem is here:

$(list).find("a:not(:Contains(" + filter + "))").parent().slideUp();
$(list).find("a:Contains(" + filter + ")").parent().slideDown();

Since you're rendering this:

divInfo.innerHTML += '<li>'+friends[i].name +'</li>';

There is no anchor wrapper, the text is directly in the <li> so change the first two lines to look in those elements accordingly, like this:

$(list).find("li:not(:Contains(" + filter + "))").slideUp();
$(list).find("li:Contains(" + filter + ")").slideDown();

You could also make that whole section a bit faster by running your Contains() code only once, making a big pact for long lists, like this:

$(input).bind("change keyup", function () {
   var filter = $(this).val();
   if(filter) {
     var matches = $(list).find("li:Contains(" + filter + ")").slideDown();
     $(list).find("li").not(matches).slideUp();
   } else {
     $(list).find("li").slideDown();
   }
});

And to resolve those potential (likely really) innerHTML issues, build your structure by using the DOM, like this:

function getFriends(){
  var theword = '/me/friends';
  FB.api(theword, function(response) {
    var divInfo = $("#divInfo"), friends = response.data;
    divInfo.append('<h1 id="header">Friends/h1>');
    var list = $('<ul id="list" />');
    for (var i = 0; i < friends.length; i++) {
      $('<li />', { text: friends[i].name }).appendTo(list);
    }
    divInfo.append(list);   
 });
} 

By doing it this way you're building your content all at once, the <ul> being a document fragment, then one insertion....this is also better for performance for 2 reasons. 1) You're currently adding invalid HTML with the .innerHTML calls...you should never have an unclosed element at any point, and 2) you're doing 2 DOM manipulations (1 for the header, 1 for the list) after the much faster document fragment creation, not repeated .innerHTML changes.

Nick Craver