Advertisement

Introduction to webOS SDK Development: Part 3

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

This post is part of a series called Introduction to webOS SDK Development.
Introduction to webOS SDK Development: Part 2
Introduction to webOS SDK Development: Part 4

Welcome to the third part of our series on webOS SDK development. In part 2, we supplied static data to a list. This tutorial will show you how to load dynamic data into a list. We're going to use AJAX and YQL to achieve this.

Spinner and Scrim

Let's start by changing the list scene's HTML. Edit app/views/list/list-scene.html to contain the following:

<div id="myHeader" class="tuts-header palm-page-header multi-line">
		<div id="titleImage" class="title-image"></div>
</div>

<div class="palm-scrim" id="search_divScrim" style="display:none;">
  <div id="search_divSpinner" x-mojo-element="Spinner"></div>
</div>

<div id="listWrapper" class="palm-list main-list">
      <div id="MyList" x-mojo-element='List'></div>
</div>

The header and list are similar to the ones in the main scene. What we're adding is a scrim and a spinner. What is a scrim and a spinner you ask? A spinner shows a spinning (surprise!) image as an indication that an operation is in progress. It's a good choice to display a spinner for every operation that is going to take a while (and remember, since we are on a mobile device, operations that get remote data over a wireless connection might take a while). Additionally, we use a scrim (a translucent layer used to obscure the background UI) to hide the background UI while the spinner is displayed because it would make no sense to interact with the application while a operation is pending.

We also add a wrapping div around our list to push it down below the header. Define the necessary class in stylesheet/tutsplus.css for that:

.main-list {
	padding-top: 48px;
}

Next, head over to app/assistants/list-assistant.js to add the application logic. First we define the list model. Unlike last time, where the model data was static, our listmodel won't have any data in it -it will get loaded into the list later.

this.myListModel = { items : [] };

this.myListAttr = {
	itemTemplate: "list/itemTemplate",
	renderLimit: 10,
	dividerTemplate: "list/dividerTemplate",
	dividerFunction : this.whatPosition
};

List Divider

We define two new properties in our list attributes this time: dividerTemplate and dividerFunction. Let me explain dividers first. They are essentially elements put between list entries to group them. In our app we want to group the displayed articles by date. Go ahead and create the dividerFunction:

ListAssistant.prototype.whatPosition = function(listitem){
    var myDate = new Date(listitem.pubdate);
    var ds=Mojo.Format.formatDate(myDate,{date:"long",countryCode:"US"});
    return ds;
}

A listitem is passed to our function and we create a javascript date object out of its pubDate property (it refers to the publication date we get from the RSS feed). We then reformat that date with a Mojo function to a long date string (e.g September 6, 2010) and return that. The list logic will then use that date to group list items together that have the same date. The dividerTemplate defines how the actual divider looks. Edit app/views/list/dividerTemplate.html:

<table class="palm-divider labeled">
	<tr>
		<td class="left"></td>
		<td class="label">
			#{dividerLabel}
		</td>
		<td class="right"></td>
	</tr>
</table>

Each time the list renders a divider, it inserts the above HTML-code in replaces #{dividerLabel} with the date string.

Lets create the list template next, edit app/views/list/itemTemplate.html:

<div class="palm-row grid-cell" x-mojo-tap-highlight="immediate">
  <div><span class="button #{clsname}">#{category}</span><span class="creator">by #{creator}</span><div style="clear:both;"></div></div><div class="ellipsis"><b>#{data}</b></div><div class="descr">#{description}</div>
</div>

Again, we specifiy how each row of the list is laid out and what data of the model is displayed. Also add the new classes to the stylesheets/tutsplus.css:

.pubdate {
	font-size: 10px;
}

.creator {
	font-size: 12px;
	background-color: #a0a0a0;
	float: right;
	padding: 3px 3px;
	text-align: right;
	margin-right: 14px;
	margin-top: 4px;
	color: white;
}

.ellipsis {
  padding: 10px 0px;
  margin-left: 14px;
  font-size: 19px;
  width: 95%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.descr {
  font-size: 14px;
  margin-left: 14px;
  width: 95%;
}

.button {
   width: 95%;
   overflow: hidden;
   white-space: nowrap;
   text-overflow: clip;
   margin-left: 14px;
   padding: 3px 3px;
   -webkit-border-radius: 8px;
   color: white;
   font-size: 14px;
   text-decoration: none;
   vertical-align: middle;
}

.Nettuts {
	border-top: 1px solid #4a9082;
	background: #2e6a60;	
}

.Vectortuts {
	background: #19487e;
}

.Psdtuts {
	background: #a51500;
}

.Activetuts {
	background: #a5290a;
}

.Aetuts {
	background: #4a3a57;
}

.Cgtuts {
	background: #73434f;	
}

.Phototuts {
	border-top: 1px solid #3297b5;
	background: #2e92b2;
}

.Audiotuts {
	background: #3d6b00
}

.Mobiletuts {
	border-top: 1px solid #ffd200;
	background: #d19c00;	
}

At the end of the setup function, add the last missing piece, the setup of the spinner:

	this.controller.setupWidget("search_divSpinner", { spinnerSize : "large" }, { spinning: true } );
}

Note that we set it to spin already, but because the DIV containing the spinner is hidden we won't see the spinner image.

Allright, let's move on and edit the activate function:

ListAssistant.prototype.activate = function(event) {
	/* put in event handlers here that should only be in effect when this scene is active. For
	   example, key handlers that are observing the document */

	this.headerTitleElement.innerHTML="<img src="+this.titleimage+">"
	this.getData();

}

AJAX and YQL

We display the title-image and a call to getData. This will load the data we want to display in our list. Go ahead and add the function getData:

ListAssistant.prototype.getData = function() {
	$("search_divScrim").show();

Before get the data, we show the DIV containing the spinner. We'll show the spinner while the load operation is in progress. Our goal is to display the lastest posts from the selected tutsplus site into our list. Each tutsplus site exports its latest articles in a RSS feed. How do we read the RSS Feed to use in our application? We're going to use YQL, the Yahoo! Query Language is an expressive SQL-like language that lets you query, filter, and join data across Web services (http://developer.yahoo.com/yql/). I won't go into details about YQL here, you can read more about it on nettuts.

Here's how we get the data from mobiletuts with YQL:

select * from rss where url='http://feeds.feedburner.com/mobile-tuts-summary'

Use the YQL console at http://developer.yahoo.com/yql/console to try it out. Select JSON as the output format. Here is the (shortened) result:

{
 "query": {
  "count": "1",
  "created": "2010-09-07T08:41:32Z",
  "lang": "en-US",
  "results": {
   "item": [
    {
     "title": "Introduction to webOS SDK Development: Part 2",
     "link": "http://mobile.tutsplus.com/tutorials/webos/introduction-to-webos-sdk-development-part-2/",
     "comments": "http://mobile.tutsplus.com/tutorials/webos/introduction-to-webos-sdk-development-part-2/#comments",
     "pubDate": "Mon, 30 Aug 2010 12:00:40 +0000",
     "creator": "Markus Leutwyler",
     "category": [
      "webOS",
      "webOS internet",
      "webOS rss",
      "webOS SDK",
      "webOS table view"
     ],
     "guid": {
      "isPermaLink": "false",
      "content": "http://mobile.tutsplus.com/?p=2392"
     }
   ]
  }
 }
});

Looks like we can use most of that data to display in our list. How do we get it into our list, you ask? AJAX is the answer. We are going to use an AJAX request to call the YQL webservice. Since mobiletuts uses a different feed than the other sites, we need to change the feed url manually.

var feed=this.title.toLowerCase();
if (feed=='mobiletuts') {
	feed='mobile-tuts-summary';
} else {
	feed=feed+'-summary';
}
var query="Select * from rss where url='http://feeds.feedburner.com/"+feed+"'";

var url = "http://query.yahooapis.com/v1/public/yql?q="+encodeURIComponent(query)+"&format=json";

var request = new Ajax.Request(url, {
        method: 'get',
        asynchronous: true,
        evalJSON: "false",
        onSuccess: this.parseResult.bind(this),

	on0: function (ajaxResponse) {
	        // connection failed, typically because the server is overloaded or has gone down since the page loaded
	        Mojo.Log.error("Connection failed");
	        },
	onFailure: function(response) {
	        // Request failed (404, that sort of thing)
	        Mojo.Log.error("Request failed");
	        },
	onException: function(request, ex) {
	        // An exception was thrown
	        Mojo.Log.error("Exception");
	},
});
	
}

We are using Prototype's Ajax.Request function to call the Yahoo webservice. Since AJAX calls are asynchronous, we don't know when we get back the data from the webservice. We specify the function to call when the data is received in the onSuccess callback: this.parseResult.bind(this)

There's something new in how the callback is called, notice the added statement .bind(this). Let me explain what "this" and scope in javascript means: In JavaScript, functions are executed in a specific context, referred as "scope". Inside the function the this keyword becomes a reference to that scope. For example, the variable this.title that we're using in the function getData is local to that function and won't be available in another function. Enter .bind(this). "Binding" basically determines the meaning, when a function runs, of the "this" keyword. In our example, when we call this.parseResult.bind(this), it's variables that were referenced through this are available in the parseResult function.

The data returned from the webservice call ends up in the transport object that passed to the parseResult function. We're interested in the transport.reponse Text Property, which contains the output as a JSON String. We convert that to an object by calling evalJSON. We can then loop through the properties of the JSON data and collect the data that we want to fill into our list.

Parsing JSON

ListAssistant.prototype.parseResult = function(transport) {

  var newData = [];

  var data=transport.responseText;

  try {
    var json = data.evalJSON();
  }
    catch(e) {
    Mojo.Log.error(e);
  }

  k=0;

  for (j=0;j<json.query.count;j++) {
	
      var cat="";	
	
      var categories=json.query.results.item[j].category;
     
      for (i=0;i<categories.length;i++) {
  	if (i==0) { cat=categories[i]; } else { if (i<3) { cat=cat+' / '+categories[i]; } }
      }

      var descr=json.query.results.item[j].description;
      var ipos=descr.indexOf('[...]');
      if (ipos==-1) { ipos=descr.indexOf('...'); }
      if (ipos!=-1) { descr=descr.substr(0,ipos); }
      descr=descr+'...'

      newData[k] = {data: json.query.results.item[j].title, guid: json.query.results.item[j].guid.content, category: cat, pubdate: json.query.results.item[j].pubDate, creator: json.query.results.item[j].creator, clsname: this.title, description: descr };
      k++;
  }

Since the categories per article are dynamic, we are just taking the first 3 categories out of the JSON data and construct a new category string out of it (named cat). We also need to shorten the description, because the feed sometimes contains HTML-Strings that we don't want to display. Allright, we have parsed our response JSON and constructed a new array out of it. This array is the base for our list model.

  this.myListModel["items"] = newData;
  this.controller.modelChanged(this.myListModel , this);

  // hide the spinner
  $("search_divScrim").hide();  

};

First, we pass the array newData to the items of our list model and then notifiy the list that there's a new model ready to work with. The list will then render the list with the new data. At last, we hide our spinner to show the user that the loading process has ended.

Package the app, install and run it. For each tutsplus site you select, you should now see the list being populated with the latest articles.

Wrap up

Congratulations! We have read the contents of a RSS through YQL and fed that data into our list. In part 4 we're going to add the last missing piece to our application!

Advertisement