Thursday, February 24, 2011

Dynamically Appending Elements to jQuery Mobile ListView

I've been developing with jQuery Mobile the past several weeks and the application I'm working on has a listing page where I am retrieving the results via $.ajax and then dynamically appending the results to the current page. I started out with a page very much like the following...
<div data-role="page" id="hackerNews">
    
  <div data-role="header" data-backbtn="false">
    <a id="btnRefresh" href="#" data-icon="refresh">Refresh</a>
    <h1>Hacker News  
      <span id="itemCount" class="count ui-btn-up-c ui-btn-corner-all">0</span>
    </h1>
  </div>
    

  <div id="content" data-role="content">
    <ol class="newsList" data-role="listview"></ol>
  </div>
        
</div>

<script id="newsItem" type="text/x-jquery-tmpl">
  <li data-messageId="${id}" class="newsItem">
    <h3><a href="${url}">${title}</a></h3>
    <p class="subItem"><strong>${postedAgo} by ${postedBy} </strong></p>
    <div class="ui-li-aside">
      <p><strong>${points} points</strong></p>
      <p>${commentCount} comments</p>
    </div>
  </li>
</script>
...but when I tried to dynamically render the ListView into the content area the browser ended up rendering something like the screenshot below on the left, where I had expected it to render something like the screenshot on the right.

In the following example, I will pull the most recent items from Hacker News and display them inside of a jQuery Mobile ListView.

After some digging and researching, it turns out the difference between the left screenshot and the right is just one line of code. All you have to do is to call the $.listview() widget off of your list jQuery object... so, something like $( "#myUnorderedList" ).listview();.

Make sure to notice line #61, which is the main difference between the screenshot above!
var hackerNews = (function( $, undefined ) {
  var pub = {};

  pub.init = function() {
    //Refresh news when btnRefresh is clicked
    $( "#btnRefresh" ).live( "click", 
      function() {
        pub.getAndDisplayNews();
      });
        
    //When news updated, display items in list
    amplify.subscribe( "news.updated", 
      function( news ) {
        displayNews( news );
      });

    //When news updated, then set item count
    amplify.subscribe( "news.updated", 
      function( news ) {
        $("#itemCount").text( news.items.length );
      });    
  };
    
  pub.getAndDisplayNews = function() {
    //Starting loading animation
    $.mobile.pageLoading();    

    //Get news and add success callback using then
    getNews().then( function() {
      //Stop loading animation on success
      $.mobile.pageLoading( true );    
    });    
  };
    
  function getNews() {
    //Get news via ajax and return jqXhr
    return $.ajax({
      url: "http://api.ihackernews.com/" + 
         "page?format=jsonp",
      dataType: "jsonp"
    }).then( function( data, textStatus, jqXHR ) {
      //Publish that news has been updated & allow
      //the 2 subscribers to update the UI content
      amplify.publish( "news.updated", data );
    });
  }
    
  function displayNews( news ) {
    var newsList = $( "#hackerNews" )
      .find( ".newsList" );
        
    //Empty current list
    newsList.empty();
        
    //Use template to create items & add to list
    $( "#newsItem" ).tmpl( news.items )
      .appendTo( newsList );
        
    //Call listview jQuery UI Widget after adding 
    //items to the list for correct rendering
    newsList.listview( "refresh" );    
  }
    
  return pub;
}( jQuery ));

hackerNews.init();
hackerNews.getAndDisplayNews();
I am utilizing some of the new jQuery 1.5 Deferred syntax and also the publish/subscribe methods from the Amplify Library released by appendTo recently. I'm also using the Revealing Module Pattern to protect the global scope.



View Demo Edit Demo

No comments:

Post a Comment