Open Source Software Technical Articles

Want the Best of the Wazi Blogs Delivered Directly to your Inbox?

Subscribe to Wazi by Email

Your email:

Connect with Us!

Current Articles | RSS Feed RSS Feed

Creating a Mashup Dojo Widget

  
  
  

Dojo is a powerful user interface toolkit that lets developers build interactive web applications. In a previous article, I explained the steps you can take to create a custom Dojo component. While that article covered how to create the component structure, template, class, style, and how to test the component, the example I created was pretty simple. In this article, I will show how to create a more meaningful component that interacts with third-party APIs.



As you may remember from the previous article, the component template defines the HTML representation of the component, while the component class represents the component data and behavior. The elements of the component template can be referenced and used in the component class using the Dojo attach points.



Let's create a location viewer component that calls the Google Maps APIs to display the location of any valid address in the world on a map, and also states the latitude and longitude of the address. The component lets us configure the map zoom value, width, and height, and allows users to refresh a map by changing the address using the provided API.



We can declare the component within a page's HTML with the following syntax:




<div dojoType="custom.locationViewerWidget"
id="viewer1"
address="Cairo, Egypt"
zoom="5"
showLabel="true"
containerWidth="500px"
containerHeight="500px"/>


This declaration means that the component should display the location of the passed address ("Cairo, Egypt") on the map. The map has a zoom value equal to "5". The map width and height should be 500 pixels. Setting "showLabel" to true asks the LocationViewer component to show the address and its corresponding latitude and longitude above the map.



The Widget Template



Let's start creating the LocationViewer component by creating the component template. The LocationViewer template has two main HTML elements under the root div element. The span element displays the address information and the div element displays the address location on the map.




<div>
<span dojoAttachPoint="labelNode" class="locationWidgetLabel"></span>
<div dojoAttachPoint="mapNode"
style="width:${containerWidth}; height:${containerHeight}">
</div>
</div>


"labelNode" is used for referencing the span element, and "mapNode" is used for referencing the div element. The width and the height of the second div element are determined by the containerWidth and the containerHeight attributes, which as we'll see in a moment are defined in the widget class. The span element can be customized by modifying the "locationWidgetLabel" CSS class.



The Widget Class



As I explained in the previous article, the dijit._Widget class is the base class for all of the dijit widgets, while the dijit._Templated class is responsible for reading HTML templates and creating the widget's DOM tree. The locationViewer widget class inherits from both the dijit._Widget and the dijit._Templated classes using the dojo.declare API. The next code listing shows the complete code of the widget class:




dojo.provide("custom.locationViewerWidget");

dojo.require("dijit._Templated");
dojo.require("dijit._Widget");
dojo.require("dijit.Tooltip");

dojo.declare("custom.locationViewerWidget", [ dijit._Widget, dijit._Templated ],
{
templatePath: dojo.moduleUrl("custom","templates/locationViewerWidget.html"),
address: "",
showLabel: false,
zoom: 6,
containerWidth: "300px",
containerHeight: "300px",

constructor: function() {
},

postCreate: function() {
var address = this.address;

this.refreshMap(address);
},

refreshMap: function(address) {
if (address == "") {
return;
}

var geocoder = new google.maps.Geocoder();

var localZoom = this.zoom;
var localShowLabel = this.showLabel;
var localLabelNode = this.labelNode;

var mapOptions = {
zoom: localZoom,
mapTypeId: google.maps.MapTypeId.HYBRID
};

var map = new google.maps.Map(this.mapNode, mapOptions);

geocoder.geocode({'address': address}, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);

var infoWindow = new google.maps.InfoWindow({
content: address
});

var marker = new google.maps.Marker({
map: map,
position: results[0].geometry.location
});

infoWindow.open(map, marker);

if (localShowLabel) {
localLabelNode.innerHTML = "\"" + address + "\" has the following coordinates: " + results[0].geometry.location;
}

} else {
alert("Error: " + status);
}
});
}
});


As you can see, the component attributes are defined near the top in the dojo.declare statement. The postCreate method calls refreshMap, the main method provided by the locationViewerWidget class, passing the address attribute value to it as a parameter. The refreshMap API uses the Google Maps v3 APIs for rendering the map and for resolving the address to get the latitude and longitude corresponding to the address.



The google.maps.Map object creates a map. Its constructor takes two parameters: the DOM object (div) to generate the map content on, and the map options object. The DOM object is referenced by the mapNode Dojo attach point, while the map options object is populated with the map zoom value and the map type.



The google.maps.Geocoder object is responsible for resolving the address by using the geocode API. This API takes two parameters: a JavaScript object that contains the address attribute, and a delegate function that is called once the geocoding operation is completed. The delegate function itself has two parameters that are filled when the operation is completed: the results object, which contains the latitude and the longitude of the location, and the status object, which represents the status code of the operation.



The google.maps.Marker object creates a marker on the map. Its constructor takes two parameters: the map object and the position of the marker (latitude and longitude).



The google.maps.InfoWindow object creates the Information window. Its constructor takes an object that contains the content attribute to be displayed. The information window is displayed when the open API is called.



Finally, if the showLabel attribute is set to true, the labelNode attach point HTML content is updated with the location and its information (latitude and longitude).


19a98812-f823-48dc-841e-bf029c63c6d7

The Widget Style



The widget style, defined in the custom.css file, applies to the labelNode span element. In our example it is a simple style class:




.locationWidgetLabel {
font: bold 17px serif;
}


Testing the Widget



To test the widget, you must register the locationViewerWidget module path in the HTML page using the dojo.registerModulePath statement, load the locationViewerWidget module using the dojo.require("custom.locationViewerWidget") directive, and include the custom.css style and the Google Maps API library script, as shown in the listing below:




<script src="dojo/dojo/dojo.js" djConfig="parseOnLoad: true">
</script>
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false">
</script>

<link href="dojo/custom/css/custom.css" rel="stylesheet" type="text/css"/>

<script type="text/javascript">
dojo.registerModulePath("locationViewerWidget", "custom/locationViewerWidget");

dojo.require("dojo.parser");
dojo.require("dijit.form.Button");
dojo.require("custom.locationViewerWidget");
</script>


Continuing with the HTML code for our example page, we create the LocationViewer widget via a div element whose Dojo type is custom.locationViewerWidget, with initial values for the address and the zoom attributes:




<div dojoType="custom.locationViewerWidget"
id="viewer1"
address="Cairo, Egypt"
zoom="5"
showLabel="true"
containerWidth="500px" containerHeight="500px"/>


Because the LocationViewer widget is a valid dijit widget, it can be retrieved at execution time using the dijit.byId function. The code listing below shows how to call the refreshMap API after retrieving its instance using the dijit.byId:




<script type="text/javascript">
function changeAddress() {
var viewer1 = dijit.byId("viewer1");
viewer1.refreshMap(document.getElementById("addressInput").value);
}
</script>

...
<label for="addressInput">Enter the address: </label>
<input id="addressInput" type="input"/>
<button id="changeLocationButton" dojoType="dijit.form.Button" onclick="javascript:changeAddress();">
Change location
</button>


When a user clicks the changeLocationButton button, the browser executes the changeAddress function, which itself calls the refreshMap API of the widget, passing the user's entered address to it.



The listing below shows the complete testing code of the LocationViewer widget:




<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<script src="dojo/dojo/dojo.js" djConfig="parseOnLoad: true">
</script>
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false">
</script>

<link href="dojo/custom/css/custom.css" rel="stylesheet" type="text/css"/>

<script type="text/javascript">
dojo.registerModulePath("locationViewerWidget","custom/locationViewerWidget");

dojo.require("dojo.parser");
dojo.require("dijit.form.Button");
dojo.require("custom.locationViewerWidget");
</script>

<link rel="stylesheet" type="text/css" href="dojo/dijit/themes/tundra/tundra.css"/>

<script type="text/javascript">
function changeAddress() {
var viewer1 = dijit.byId("viewer1");

viewer1.refreshMap(document.getElementById("addressInput").value);
}
</script>

</head>

<body class="tundra">
<label for="addressInput">Enter the address: </label>
<input id="addressInput" type="input"/>
<button dojoType="dijit.form.Button" onclick="javascript:changeAddress();">
Change location
</button>

<br/>

<div dojoType="custom.locationViewerWidget"
id="viewer1"
address="Cairo, Egypt"
zoom="5"
showLabel="true"
containerWidth="500px" containerHeight="500px"/>
</body>

</html>


When you display the testing HTML page, you should see a page that looks like this:





For reference, you can download the complete component source.



Conclusion



I hope this article illustrates how custom Dojo components can not only help speed up development of rich Web 2.0 applications, but also help you maintain the applications, as Dojo centralizes the related presentation and logic code in one place (the component).




This work is licensed under a Creative Commons Attribution 3.0 Unported License
Creative Commons License.


This work is licensed under a Creative Commons Attribution 3.0 Unported License
Creative Commons License.

Comments

Currently, there are no comments. Be the first to post one!
Post Comment
Name
 *
Email
 *
Website (optional)
Comment
 *

Allowed tags: <a> link, <b> bold, <i> italics