Showing posts with label jQuery Mobile. Show all posts
Showing posts with label jQuery Mobile. Show all posts

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

Wednesday, February 09, 2011

Feature Detect Placeholder to Adjust jQuery Mobile Layout

If you take a look at most of the jQuery Mobile Documentation you will see heavy use of labels and input elements inside of a fieldcontain data-role. The great thing about this technique is that it looks good on portrait layouts (labels on top & input on bottom) and also adjusts for landscape layouts (labels on the left & input on the right).



The HTML to generate the above screenshots can be found in the following markup.

 
<div data-role="page" id="login">
    
  <div data-role="header">
    <h1>Acme Corporation</h1>
  </div>
    
  <div data-role="content">
    <form id="frmLogin" class="validate">
      <div data-role="fieldcontain">
        <label for="email">Email: </label>
        <input type="text" id="email" 
          name="email" class="required email" />
      </div>
            
      <div data-role="fieldcontain">
        <label for="password">Password: </label>
        <input type="password" id="password" 
          name="password" class="required" />
      </div>
            
      <div class="ui-body ui-body-b">
        <fieldset class="ui-grid-a">
          <div class="ui-block-a">
            <button id="btnCancel" data-theme="d" 
              data-icon="delete">Cancel</button>
          </div>
          <div class="ui-block-b">
            <button id="btnLogin" type="submit" 
              data-theme="a" data-icon="check">
                Log In
            </button>       
          </div>
        </fieldset>
      </div>
    </form>
        
  </div>
    
</div>

I recently did a mock-up for a client using this technique, but they wanted to use the HTML5 form placeholder technique instead. I told the client that this was possible, but mentioned that not all browsers support this technique.

So, I decided to use a Progressive Enhancement approach to this problem using the Modernizer JavaScript library. I wrote some JavaScript to detect if the browser supports the placeholder HTML5 attribute and if it does, then I hide and take the text from the label element and then push the value into the input element's placeholder attribute.

 
// if placeholder is supported
if ( Modernizr.input.placeholder ) {

  $( "input" ).each( function(index, element) {

    var placeholder = 
      $("label[for=" + element.id + "]")
        .hide().text();

    $(element)
      .addClass("ui-input-text-placeholder")
      .attr("placeholder", placeholder);

  });

}

You can view a running example of the above code from this jsFiddle. If you are running Google Chrome, then you'll notice the label's are embedded as placeholder's within the input elements, but if you are using Firefox or Internet Explorer 6/7/8 then you'll notice the default label and input technique that is default in most of the jQuery Mobile documentation.



I've added some screenshots of the placeholder technique in case you are reading this blog entry in a browser that doesn't support the HTML5 form placeholder attribute.



Monday, February 07, 2011

jQuery Mobile Form Validation

Note: I've updated the following post to work with jQuery Mobile 1.0+. After the beta version they deprecated the Orientation Classes that this post originally used to handle the layout of the error messages. jQuery Mobile recommends using CSS3 Media Queries instead. If you need support for older browsers then respond.js is a nice polyfill for this.

I am working on a jQuery Mobile application and one of the standard requirements when you have form elements is to provide client-side validation.

I hadn't seen an example of that yet, so my first inclination was to use the jQuery Validation plugin. As it turns out, the implementation is about the same as you would expect with a non-mobile solution.


In this case I just adding metadata validation classes to the input elements to indicate what rules (example: required, email, etc...) need to be checked when the form is submitted. You can provide these rules also programmatically, but I won't focus on that technique in this post. You can find more details about how to provide validation rules at runtime in the plugin's documentation.

In JavaScript, all you have to do is to call the validate() method off of the form element and provide a submitHandler that will perform the action once your form has passed all it's validation rules.


An interesting challenge comes on mobile devices when considering how to display the validation message in portrait versus landscape mode. I wanted the alignment of the errors to show up different depending upon the orientation.

As it turns out, the solution to this problem was a simple matter of changing my CSS. We can use CSS3 Media Queries to style the page depending on the orientation of the mobile device. By using the following CSS the validation errors will display differently depending if the mobile device is in portrait or landscape mode.


The following is an embedded working jsFiddle example using all the above HTML, CSS, JavaScript, and Resources. If you want to play and tweak with the code snippets you can click the "+" to the right of the "Resources" tab.



Since simulating portrait vs landscape mode on a desktop browser is slightly difficult I took some screenshots from my iPhone for you to see the difference.