Developing Weather Application using Dojo Mobile

Abstract

Dojo mobile (dojox/mobile) is one of the most powerful JavaScript frameworks that enable you to build cross mobiles web applications. It does not only contain mobile components but also it mimics the interface of the different mobile devices. Adding to this it responds to the mobile orientations. In this article, I will illustrate how to use the framework for building a weather application on the iPhone mobile.

Application Story

The application story is simple. In the weather mobile application, the user can get the current weather forecasting of a specific location. The user can do this by selecting one city from a list of available cities in the drop down and then click the “Get Information” button to get the weather information as shown in the screenshot below.

Building the weather application

In order to create the weather forecasting page, we need to know the main parts of the Dojo mobile page:

  • The Doc Type and META tags.
  • The mobile script and style includes.
  • The application pages.

The Doc Type and META Tags

The Doc type that is recommended to be used is the HTML5 doctype as shown below:

<!DOCTYPE html>

For the META tags, it is important to use the "viewport" META tag to setup cross-device layout as shown below:

<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no"/>

The other important META tag is an iPhone specific one which is the “apple-mobile-web-app-capable”. It is recommend to be set to “yes” to make the web application runs in full-screen mode as shown below:

<meta name="apple-mobile-web-app-capable" content="yes"/>

The script and style includes

Now, we come to the script and style includes. I’m using Dojo 1.7.1 for this application; in order to include the script and the style files; this can be done as shown below:

<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/mobile/themes/iphone/iphone.css" rel="stylesheet"></link>

<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojo/dojo.js" data-dojo-config="async: true"></script>

I only included the iPhone CSS file, and the dojo.js file which is loaded asynchronously for including only the resources we need to keep the application optimized.

After including the script and the style, we need to use the require statements for including the Dojo modules. I use the new Asynchronous Modules (AMD) convention for making asynchronous loading of the modules as shown below:

require(["dojox/mobile/parser", "dojo/_base/xhr", "dojox/mobile", "dojox/mobile/deviceTheme",
         "dojox/mobile/TextBox", "dojox/mobile/Button", "dojox/mobile/compat", 
	 "dijit/form/DataList", "dojox/mobile/ComboBox",
         "dojo/domReady!"], function(parser) {
			
		//...			
		parser.parse();
	});

The following modules are loaded:

  • The Dojo mobile parser "dojox/mobile/parser" for converting the HTML elements to Dojo mobile components.
  • The Dojo XHR module "dojo/_base/xhr" for allowing performing Ajax requests.
  • The Dojo mobile device theme "dojox/mobile/deviceTheme", for allowing detecting the mobile type and loading the proper theme correctly.
  • "dojox/mobile/TextBox", "dojox/mobile/Button", "dijit/form/DataList", "dojox/mobile/ComboBox", for loading the mentioned components.
  • The "dojox/mobile/compat" module is required for allowing having cross mobiles CSS3 animations.
  • "dojo/domReady!" module is a replacement for the old dojo.ready API. The exclamation mark is required to tell Dojo not to execute the callback until the DOM is ready.

Finally in the callback, I’m using only the first parameter which is the parser object. The parser.parse statement is called for allowing the Dojo mobile parser to convert the HTML elements to Dojo mobile components.

The Application Pages

In our weather application, we have mainly two pages; the weather forecasting page and the about page. In order to make the pages scroll to the left and the right while navigation, we need to use the Dojo mobile views. The Dojo mobile views are normal HTML elements with the dojo type "dojox.mobile.View". To define the two views, we can do this as shown below:

<div id="weather" dojoType="dojox.mobile.View" selected="true">
	...
</div>
			
<div id="about" dojoType="dojox.mobile.View">
	...
</div>		

The main view should be marked as selected by setting the selected attribute to "true".

In the weather forecasting page, there are three main elements:

  • Heading element.
  • Combo Box of the available cities.
  • The “Get Information” button.

In order to create the heading element, you can mark an HTML element h1 with the “dojox.mobile.Heading” dojo type as follows:

<h1 dojoType="dojox.mobile.Heading">Weather
	<div dojoType="dojox.mobile.ToolBarButton" moveTo="about">About</div>		
</h1>

I included a toolbar button for moving to the other view. The moveTo attribute can have the id of the view to switch to, in our case it is the about view id.

In order to create the combo box element, we need to use two tags, one for the data which is the dijit.form.DataList, and the other for the widget itself which is the dojox.mobile.ComboBox as follows:

<select data-dojo-type="dijit.form.DataList" data-dojo-props="id:'locationList'" >
  <option selected>Cairo, Egypt</option>
  <option>Stockholm, Sweden</option>	
  <option>Vienna, Austria</option>
  <option>Madrid, Spain</option>	
  <option>Paris, France</option>
  <option>New York, USA</option>
  <option>Lima, Peru</option>
</select>

<input id="location" class="ninety" type="text" data-dojo-type="dojox.mobile.ComboBox" data-dojo-props="list:'locationList'" />

The “location” combo box is referring to the “locationList” using the list attribute.

The “Get Information” button calls the getWeatherInformation function to get the weather information as follows:

<button class="mblBlueButton full" dojoType="dojox.mobile.Button" onClick="getWeatherInformation();">Get Information</button>

The getWeatherInformation JavaScript function is calling a proxy Java Servlet that interacts with the Yahoo weather service API using dojo xhrGet.

function getWeatherInformation() {
	var selectedCode = cityMap[dijit.byId("location").value];
	
	dojo.xhrGet({
		url: "/mobileSamples/weatherProxyServlet?w=" + selectedCode,
		handleAs: "text",
		load: function(data) {
			document.getElementById("weatherResult").innerHTML = data;
		}
	});
}

The location value is read from the “location” combo box and the code of the location is retrieved from a custom JavaScript Map that I created for mapping the WOEID (Where on earth identification) to the locations.

When the HTML response is returned from the server, it is displayed directly in the "weatherResult" span.

Finally in the weather forecasting view, I embed the combo box and the button in the "dojox.mobile.RoundRect" div as follows:

<div dojoType="dojox.mobile.RoundRect" shadow="true">
	<label for="txtLocation">Select location</label> <br/>
	
	<select data-dojo-type="dijit.form.DataList" data-dojo-props="id:'locationList'" >
	  <option selected>Cairo, Egypt</option>
	  <option>Stockholm, Sweden</option>	
	  <option>Vienna, Austria</option>
	  <option>Madrid, Spain</option>	
	  <option>Paris, France</option>
	  <option>New York, USA</option>
	  <option>Lima, Peru</option>
	</select>
	
	<input id="location" class="ninety" type="text" data-dojo-type="dojox.mobile.ComboBox" data-dojo-props="list:'locationList'" /><br/><br/>
	
	<button class="mblBlueButton full" dojoType="dojox.mobile.Button" onClick="getWeatherInformation();">Get Information</button><br/><br/>
	
	<span id="weatherResult"/>
</div>

The other “about” view element is a simple one that contains description for the sample as follows:

<div id="about" dojoType="dojox.mobile.View">
	<h1 dojoType="dojox.mobile.Heading" back="back" moveTo="weather">About</h1>
	<div dojoType="dojox.mobile.RoundRect">This sample is developed by TechnicalAdvices.COM. It is powered by Dojo Mobile.</div>
</div>

It contains a heading that includes a back button using the back attribute. The back attribute should be set to id of the view element that the application navigates to when the back button is clicked.

The next code listing shows the complete code of the weather forecasting page:

<!DOCTYPE html>
<html>

<head>
	<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no"/>
	<meta name="apple-mobile-web-app-capable" content="yes"/>
	<title>Welcome to the weather application</title> 
	
	<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/mobile/themes/iphone/iphone.css" rel="stylesheet"></link>
	<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojo/dojo.js" data-dojo-config="async: true"></script>
	
	<script type="text/javascript">
		var cityMap = {};
		
		require(["dojox/mobile/parser", "dojo/_base/xhr", "dojox/mobile", "dojox/mobile/deviceTheme",
		         "dojox/mobile/TextBox", "dojox/mobile/Button", "dojox/mobile/compat", 
		         "dijit/form/DataList", "dojox/mobile/ComboBox",
		         "dojo/domReady!"], function(parser) {
			
					// initialize WOIDs ...
					cityMap["Cairo, Egypt"] = "1521894";
					cityMap["Stockholm, Sweden"] = "906057";
					cityMap["Vienna, Austria"] = "551801"
					cityMap["Madrid, Spain"] = "766273";
					cityMap["Paris, France"] = "615702";
					cityMap["New York, USA"] = "2459115";
					cityMap["Lima, Peru"] = "418440";
			
					parser.parse();
				});


		function getWeatherInformation() {
			var selectedCode = cityMap[dijit.byId("location").value];
			
			dojo.xhrGet({
			    url: "/mobileSamples/weatherProxyServlet?w=" + selectedCode,
			    handleAs: "text",
			    load: function(data) {
			    	document.getElementById("weatherResult").innerHTML = data;
			    }
			});
		}
	</script> 
	
	<style>
		.full {
			width:100%
		}
		
		.ninety {
			width:90%
		}		
	</style>
</head>

<body>
	<div id="weather" dojoType="dojox.mobile.View" selected="true">
		<h1 dojoType="dojox.mobile.Heading">Weather
			<div dojoType="dojox.mobile.ToolBarButton" moveTo="about">About</div>		
		</h1>
		
		<div dojoType="dojox.mobile.RoundRect" shadow="true">
			<label for="txtLocation">Select location</label> <br/>
			
			<select data-dojo-type="dijit.form.DataList" data-dojo-props="id:'locationList'" >
			  <option selected>Cairo, Egypt</option>
			  <option>Stockholm, Sweden</option>	
			  <option>Vienna, Austria</option>
			  <option>Madrid, Spain</option>	
			  <option>Paris, France</option>
			  <option>New York, USA</option>
			  <option>Lima, Peru</option>
			</select>
			
			<input id="location" class="ninety" type="text" data-dojo-type="dojox.mobile.ComboBox" data-dojo-props="list:'locationList'" /><br/><br/>
			
			<button class="mblBlueButton full" dojoType="dojox.mobile.Button" onClick="getWeatherInformation();">Get Information</button><br/><br/>
			
			<span id="weatherResult"/>
		</div>
	</div>
				
	<div id="about" dojoType="dojox.mobile.View">
		<h1 dojoType="dojox.mobile.Heading" back="back" moveTo="weather">About</h1>
		<div dojoType="dojox.mobile.RoundRect">This sample is developed by TechnicalAdvices.COM. It is powered by Dojo Mobile.</div>
	</div>					

</body>
</html>

The Backend Java Servlet

Finally, the backend Java Servlet reads the WOEID parameter and opens a URL connection to the yahoo API weather forecasting URL for getting the weather forecasting of the location as shown in the code listing below.

package servlets;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.SyndFeedInput;

/**
 * <code>WeatherProxyServlet</code> is a proxy servlet that wraps Yahoo weather REST service.
 * @author hazems
 *
 */
public class WeatherProxyServlet extends HttpServlet {
	private static final long serialVersionUID = 8732370454506907957L;

	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		
		String WOEID = request.getParameter("w");
		HttpURLConnection connection = null;
		
		response.setContentType("text/html");

		PrintWriter writer = null;
		
		try {
			URL url = new URL("http://weather.yahooapis.com/forecastrss?u=c&w=" + WOEID);
			
			connection = createHTTPConnection(url);
			
			String output = getHTTPOutputResponse(connection);
			
			SyndFeedInput input = new SyndFeedInput();
			SyndFeed feed = input.build(new InputStreamReader(new ByteArrayInputStream(output.getBytes("UTF-8"))));  		
			
		    if (feed.getEntries() != null && feed.getEntries().size() > 0) {
	            SyndEntry entry = ((SyndEntry) feed.getEntries().get(0));
	                        
	            String title = entry.getTitle();
	            String description = entry.getDescription().getValue();
	            
	            if (description.contains("Invalid")) {
	            	throw new Exception(description);
	            }
	    		
	    		writer = response.getWriter();	            
	            writer.print(title + "<br/>" + description);
	        }	
		} catch (Exception exception) {
			throw new ServletException(exception.getMessage());
		} finally {
			if (connection != null) {
				connection.disconnect();
			}
			
			if (writer != null) {
				writer.flush();
	        	writer.close();		
			}
		}
	}
	
	private static HttpURLConnection createHTTPConnection(URL url) throws IOException, ProtocolException {
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		
		conn.setDoOutput(true);
		conn.setDoInput(true);
		conn.setRequestMethod("GET"); 
		conn.setRequestProperty("Accept", "text/html"); 
		conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
		
		return conn;
	}

	private static String getHTTPOutputResponse(HttpURLConnection conn) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())));

		String httpOutputResponse = ""; 
		String outputLine;
		
		while ((outputLine = br.readLine()) != null) {
			httpOutputResponse += outputLine;
		}
		
		return httpOutputResponse;
	}
}

You can try the application from your iPhone by going to the following URL:
http://www.mashups4jsf.com/mobileSamples/weather.jsp

You can download the complete Eclipse project from the following URL:
https://www.ibm.com/developerworks/mydeveloperworks/blogs/hazem/resource/29052012/mobileSamples.zip

Conclusion

In this article, you learned how to build a mobile application using the Dojo mobile APIs. You know how to use the framework views, headings, and components for building the weather forecasting application.

How to Create a Custom Dojo Component

I published an article about how to create custom Dojo components in Wazi. This article shows you how to create a custom Dojo component from scratch. It illustrates very important concepts like the dojo attach point, the widget structure, and the widget template. I wish you will enjoy this article: http://olex.openlogic.com/wazi/2012/how-to-create-a-custom-dojo-component/.

Refreshing Dijit tree in Dojo (code that works)

Refreshing the Dijit tree in Dojo with a new data store does not have a ready-made API in Dojo (Iam talking about Dojo 1.4). In this post, I will show a technique with example that will illustrate how to refresh the Dijit tree with a new data store.

The solution is about extending the dijit.Tree to support this feature as shown in the refreshModel method.

<script type="text/javascript">// <![CDATA[
	dojo.extend(dijit.Tree, {
		refreshModel: function () {

			// reset the itemNodes Map
			this._itemNodesMap = {};

			// reset the state of the rootNode
			this.rootNode.state = "UNCHECKED";

			// Nullify the tree.model's root-children
			this.model.root.children = null;

			// remove the rootNode
			if (this.rootNode) {
				this.rootNode.destroyRecursive();
			}

			// reload the tree
			this._load();
		}
	});
// ]]></script>

Let’s see now an example of refreshing the tree using this new introduced API.

	

<button id="refreshTreeButton" class="buttonClass" onclick="refreshTreeInformation();" type="button">Refresh Tree</button><script type="text/javascript">// <![CDATA[
	dojo.addOnLoad(function() {	
		var store = new dojo.data.ItemFileWriteStore({
			url: "sampleData.json"
		});				

		var treeModel = new dijit.tree.ForestStoreModel({
			store: store,
			query: {"type": "parent"},
			rootId: "root",
			rootLabel: "Groups",
			childrenAttrs: ["children"]
		});

		var treeObject = new dijit.Tree({
			model: treeModel
		}, "treeDiv");
	});

	function refreshTreeInformation() {		
		var treeObject = dijit.byId("treeDiv");
		var newStore = new dojo.data.ItemFileWriteStore({
			url: "sampleData2.json"
		});						

		treeObject.model.store = newStore;		
		treeObject.model.query = {"type": "parent"};	
		treeObject.rootId = "root";	
		treeObject.rootLabel = "Groups";	
		treeObject.childrenAttrs = ["children"];						

		treeObject.refreshModel();
	}			
// ]]></script>

In the refreshTreeInformation function, the tree object is retrieved, a new store is loaded, and assigned to the tree model object. Finally calling the refreshModel method will make the displayed tree insync with the underlying model. you can download the code from here.

I wish this tip can help.

Getting the Selected Dijit Tree node in Dojo

This post shows you how to get the selected node in a Dijit tree.

Let’s assume we have the following dijit tree data store.

{
"identifier":"id",
"label":"name",
"items":[
{
	"type": "parent",
	"name": "Countries",
	"id": "countries",
	"children":[
		{"type":"Leaf", "name":"Country 1", "id":"country1"},
		{"type":"Leaf", "name":"Country 2", "id":"country2"},
		{"type":"Leaf", "name":"Country 3", "id":"country3"},
		{"type":"Leaf", "name":"Country 4", "id":"country4"},
		{"type":"Leaf", "name":"Country 5", "id":"country5"},
		{"type":"Leaf", "name":"Country 6", "id":"country6"}
	]
}]}

Using the following JavaScript code the tree is loaded with the desired hierarchy.

<script type="text/javascript">// <![CDATA[
dojo.require("dojo.data.ItemFileWriteStore");
dojo.require("dijit.Tree");
dojo.require("dijit.tree.ForestStoreModel");				

var store = new dojo.data.ItemFileWriteStore({
	url: "sampleData.JSON"
});

var treeModel = new dijit.tree.ForestStoreModel({
	store: store,
	query: {"type": "parent"},
	rootId: "root",
	rootLabel: "Groups",
	childrenAttrs: ["children"]
});

var treeObject = new dijit.Tree({
	model: treeModel
}, "treeDiv");

//...
// ]]></script>

In order to get the selected tree node object you should use the “selectedItem” attribute in the Dijit tree object as follows

dijit.byId("[Tree Node ID]").attr("selectedItem");

In our example to show the id of the selected leaf node when clicking the “Get Selected Node” button, the following code will do the job:

if (dijit.byId("treeDiv").attr("selectedItem") != null) {
	if (dijit.byId("treeDiv").attr("selectedItem").type == "Leaf") {
		document.getElementById("selectedNode").innerHTML =
                "Selected Node is " + dijit.byId("treeDiv").attr("selectedItem").id;
	}
}

This code is tested in Dojo 1.4 environment. download the code from here.

TypeError: this._arrayOfTopLevelItems is undefined

You may find this Dojo error when you are trying to create a JSON store object from a JSON string as follows:

var store = new dojo.data.ItemFileWriteStore({
	data: jsonString
});		

To resolve this issue, all what you should do is to convert the json string to a json object by using the dojo.fromJson as follows:

var store = new dojo.data.ItemFileWriteStore({
	data: dojo.fromJson(jsonString)
});

Reading the Dojo DateTextBox in your custom format

In Dojo 1.4, you can use the DateTextBox component for showing an input calendar for the user, as follows:

<label for="sampleDate">Select the Date: </label>
<input id="sampleDate" dojoType="dijit.form.DateTextBox"></input>
<button id="sampleButton" class="buttonClass" id="getDateValue" 
dojoType="dijit.form.Button" type="button" 
onclick="getDateValue();">Get Date Value</button>

However, getting the date value using the value property dijit.byId("sampleDate").value will result in a value like this:

Wed Nov 30 2011 00:00:00 GMT+0200

If you want to simply format the date object in the “yyyy-MM-dd” format (for example), you can use the dojo.date.locale.format as follows:

dojo.date.locale.format(dijit.byId("sampleDate").value, {datePattern: "yyyy-MM-dd", selector: "date"})

The complete getDateValue JavaScript function is as follows:

<script type="text/javascript">
function getDateValue() {
    alert("Default Date: " + dijit.byId("sampleDate").value);
    alert("Formatted Date: " + dojo.date.locale.format(dijit.byId("sampleDate").value, 
    {datePattern: "yyyy-MM-dd", selector: "date"}));
}
</script>

I wish that this tip can be useful to you.

[Problem] Tried to register widget with id==xxx but that id is already registered

You may face this error while working with the Dojo widgets. The main reason behind this error is that you are trying to create a widget with an id that already exists. The holder of this id may be the same widget.

If it is the same widget then you can destroy the old attached Dojo widget before re-creating the new one again as shown:

attachedWidget = dijit.byId("xxx");
		
if (attachedWidget ) {			
    attachedWidget.destroy();
    attachedWidget = null;
}
// Create the new attached widget here with the same id (xxx)...

If it is not the same widget then you can create the widget with another id without having to destroy the old one.