Make HTML5 microdata useful, Part 1: Using jQuery on top of microdata

Create an interactive map with the jQuery UI Map plugin and your microdata

The microdata specification lists two reasons why you might want to use microdata: To allow generic scripts to provide services that are customized to the page or to enable content from a variety of cooperating authors to be processed by a single script in a consistent manner. In this two-part series, learn to use microdata in both of these ways, starting with generic scripts on top of microdata. In this article, you will write one snippet of HTML to give you both an interactive event map and to enable Google, Bing, and Yahoo to display your page better in search results with Rich Snippets.

06 Mar 2012 - Added links to Part 2 of this article with sidebars in the Introduction, Conclusion, and a new resource in Resources.

Share:

Lin Clark, Drupal Developer, Digital Enterprise Research Institute, NUI Galway

Author photoLin Clark is a Drupal developer specializing in Linked Data. She is the maintainer of multiple Drupal modules, such as Microdata and SPARQL Views, and is an active participant in the W3C’s HTML Data Task Force and Drupal's HTML5 initiative. She attended Carnegie Mellon University and is finishing a research masters degree at the Digital Enterprise Research Institute at NUI Galway. More information is available at lin-clark.com.



06 March 2012 (First published 08 November 2011)

Also available in Chinese Russian Japanese Vietnamese Spanish

Introduction

Libraries like jQuery have revolutionized the way that developers create sites, spurring the development of a large collection of ready-to-go scripts that you can just add to HTML pages. Microdata has the potential to take this revolution a step further, making it easier to create scripts that work seamlessly on any site, regardless of variations in the HTML templates.

Frequently used acronyms

  • CSV: Comma separated value
  • JSON: JavaScript Object Notation
  • RDF: Resource Description Framework
  • RDFa: RDF in attributes
  • SEO: Search engine optimization

For example, to display planned events on a map, jQuery plugins integrate with Google maps and make it easy to create an interactive map on your site. The only effort required on your part is to create a file in a structured format, such as JSON or CSV, that contains the locations. Having this data in a separate structured format can be a problem, though, if you also want to display the information in HTML on the page. To maintain the content in both the HTML and the data file, either you have to write a script that translates between the HTML on the page and the data file or you have to duplicate your effort. This approach can lead to the HTML and data files getting out of sync very quickly.

Keeping files in sync is one main advantage of using HTML data formats like microdata and RDFa—you can write the HTML as you normally would and then add a few attributes in the HTML tags to make it double as your data store. Because these HTML data formats are web standards developed and sanctioned by the WHATWG and W3C, people already build tools that understand how to extract data from the HTML, and you can easily reuse these tools on your site.

As microdata just started gaining traction recently, only a few plugins have been released with support. An example is the jQuery UI Map plug-in, which provides the kind of map described above.


Creating a Rich Snippet-ready event map

You can use one small segment of HTML to get both an interactive event map and better SEO.

As described in an earlier article (see Resources for a link), microdata can be used with terms from the Schema.org vocabulary to help search engines understand your content better. This approach allows the search engines to show Rich Snippets, which bring the most important information about the page into the search result itself.

The annotations you add to get Rich Snippets can pull double duty. In addition to better search results, you can also use them on your own page to provide a better display.

You'll use a jQuery plugin to do this. I include the relevant files in the example code download, which includes a slightly modified version of the plug-in.


The basic event listing

Let's create a listing of DrupalCamp events throughout the world. To start, create an HTML page with two events as in Listing 1.

Listing 1. Basic HTML listing two events
<!DOCTYPE html>
  <head>
    <title>Upcoming DrupalCamps</title>
  </head>
  <body id="doc">
    <h1>Upcoming DrupalCamps</h1>

    <!-- Event 1: DrupalCamp Toulouse -->
    <div>
      <h2><a href="http://example.com/drupalcamp_fr">DrupalCamp Toulouse 2011</a></h2>
      <img src='images/drupalicon_fr.png' />
      <p>Vous l&rsquo;attendiez tous, le DrupalCamp Toulouse aura bien lieu, 
r&#233;uni avec 2 autres &#233;v&#233;nements sous la bannière 
"Capitole du Libre": le DrupalCamp, une Ubuntu Party, une Akademy 
(&#233;v&#232;nement KDE).</p>
      <div>
        ENSEEIHT, Toulouse, Haute-Garonne, FR
      </div>
      <div>
        <time datetime="2011-11-26T09:00:00+01:00">November 26, 10:00am</time> &ndash;
        <time datetime="2011-11-27T17:00:00+01:00">November 27, 6:00pm</time>
      </div>
    </div>

    <!-- Event 2: DrupalCamp Ohio -->
    <div>
      <h2><a href="http://example.com/drupalcampohio">Drupalcamp Ohio 2011</a></h2>
      <img src='images/drupalicon_oh.png' />
      <p>The Central Ohio Drupal User Group (CODUG) is proud to announce 
Ohio&rsquo;s first Drupalcamp. On Saturday, December 3rd, we&rsquo;ll hold an 
all-day camp with keynote speaker, breakout sessions and Birds of Feather groups at The 
Ohio State University&rsquo;s Nationwide and Ohio Farm Bureau 4-H Center.</p>
      <div>
        The Ohio State University, Columbus, Ohio, US
      </div>
      <div>
        <time datetime="2011-12-03T09:00:00-05:00">December 3, 9:00am</time> &ndash;
        <time datetime="2011-12-03T17:00:00-05:00">5:00pm</time>
      </div>
    </div>

  </body>
</html>

Notice that you marked up the dates with a special element. This element is the new time element, which is part of the core HTML5 standard. It enables you to add exact times to pages so they can be easily parsed and used by applications. In this example, Google uses these time elements to choose the next three events from the page to display in Rich Snippets. Note that at the time of publishing, there is discussion about whether to change the time element to a more generic data element. If this goes through, "time" would be replaced by "data" and "datetime" would be replaced by "value" in the code below.

The datetime is formed starting with the date in YYYY-MM-DD format. Next, a "T" separates the date from the time, which is given in HH:MM:SS format. Then the offset from Universal Coordinated Time (UTC) is given. For example, in winter France is 1 hour ahead of UTC, so the offset adds '+01:00' to the end of the datetime string. Ohio is 5 hours behind UTC in winter, so the offset adds '-05:00' to the end of the datetime string. This addition results in the machine-readable value "2011-12-03T09:00:00-05:00" for 9am Central Standard Time on December 3, 2011.

Save the file to the jquery-ui-map/demos directory on your web server and load it in your browser. Figure 1 shows the details for both events described in Listing 1.

Figure 1. Basic page listing two events
Screen capture of basic page listing two events

Making the events Rich Snippet-ready

The page with the time element includes a little bit of machine-understandable information. Now see if this addition has changed how Google displays the results. Go to Google's Rich Snippets Testing Tool and enter the URL for your page (see Figure 2). Rather than event details, you see preview generation errors that the page has no authorship markup, rich snippet markup, or authorship information.

Figure 2. No results for basic HTML in the Rich Snippets testing tool
No results for basic HTML in the Rich Snippets testing tool

Even though Google knows that your page lists times, it doesn't know that these times are start and end times for events. To help Google understand this, you need to add few Schema.org terms (see Listing 2). Publishing Schema.org markup with microdata is covered in more detail in a previous article (see Resources for a link).

Listing 2. Make the <div> of each event into an Event item
<!-- Event 1: DrupalCamp Toulouse -->
<div itemscope itemtype="http://schema.org/Event">
  ...
</div>

<!-- Event 2: DrupalCamp Ohio -->
<div itemscope itemtype="http://schema.org/Event">
  ...
</div>

Next add the url and name properties to the event. Note that you add a <span> around the name to have a place to add the itemprop attribute. You also add the image property. You don't need the image for this Rich Snippet, but you will use it later.

Listing 3. Add the url and name properties to the event
<!-- Event 1: DrupalCamp Toulouse -->
<div itemscope itemtype="http://schema.org/Event">
  <h2><a href="http://example.com/drupalcamp_fr" itemprop="url"><span 
itemprop="name">DrupalCamp Toulouse 2011</span></a></h2>
  <img itemprop="image" src='images/drupalicon_fr.png' />
  ...
</div>

<!-- Event 2: DrupalCamp Ohio -->
<div itemscope itemtype="http://schema.org/Event">
  <h2><a href="http://example.com/drupalcampohio" itemprop="url"><span 
itemprop="name">Drupalcamp Ohio 2011</span></a></h2>
  <img itemprop="image" src='images/drupalicon_oh.png' />
  ...
</div>

Now you can add a start time and (optionally) an end time. Because you used the time element, you already have a machine-readable value for the start and end times. You just need to add the Schema.org terms to let Google know which is which (see Listing 4).

Listing 4. Add the startDate and endDate properties on the time elements
<!-- Event 1: DrupalCamp Toulouse -->
<div itemscope itemtype="http://schema.org/Event">
  ...
  <div>
    <time itemprop="startDate" datetime="2011-11-26T09:00:00+01:00">November 26, 
10:00am</time> &ndash;
    <time itemprop="endDate" datetime="2011-11-27T17:00:00+01:00">November 27, 
6:00pm</time>
  </div>
</div>

<!-- Event 2: DrupalCamp Ohio -->
<div itemscope itemtype="http://schema.org/Event">
  ...
  <div>
    <time itemprop="startDate" datetime="2011-12-03T09:00:00-05:00">December 3, 
9:00am</time> &ndash;
    <time itemprop="endDate" 
datetime="2011-12-03T17:00:00-05:00">5:00pm</time>
  </div>
</div>

Test the page again in the Testing Tool. Because the URLs point to a different domain than the base URL, you see warnings that prevent the Rich Snippet from displaying (Figure 3).

Figure 3. Warning that prevent the Rich Snippet display
Screen capture of warning that prevents the Rich Snippet display

Google requires that the links to the events point to pages within the same domain. This requirement is designed to prevent spamming practices. If you search and replace example.com with your domain and test again, you should see the Rich Snippet display as in Figure 4.

Figure 4. Rich Snippet displaying both events with their start dates
Screen capture of Rich Snippet displaying both events with their start dates

Note that even if the testing tool shows you a Rich Snippet, you are not guaranteed to have Rich Snippet displays in real search results. You must apply to Google to have your site reviewed before Google will enable Rich Snippet output for your pages in search results. For more information on this, see the FAQ.


Adding the jQuery UI map

Because the DrupalCamps are held all over the world, your visitors will want a map showing which events are within a reasonable travel distance. To get a map, just add a few more pieces of information.

First, set up the map for display. Add a <div> in the document to place the map below the page heading and above the event listings (Listing 5).

Listing 5. Adding the map <div>.
<body id="doc">
  <h1>Upcoming DrupalCamps</h1>

  <div>
    <div id="map_canvas"></div>
  </div>

 <!-- Event 1: DrupalCamp Toulouse -->

You need to add some JavaScript files to turn this <div> into an interactive map. Also add some CSS files, which give a height to the #map_canvas <div> so you can see it. The local files are included in the source code (see Download).

Listing 6. Adding the external CSS, JavaScript libraries, and inline jQuery
<head>
  <title>Upcoming DrupalCamps</title>

  <!-- External Stylesheets -->
  <link rel="stylesheet" type="text/css" 
href="http://yui.yahooapis.com/3.2.0/build/cssreset/reset-min.css" />
  <link rel="stylesheet" type="text/css" 
href="http://yui.yahooapis.com/3.2.0/build/cssbase/base-min.css" />
  <link rel="stylesheet" type="text/css" 
href="http://yui.yahooapis.com/3.2.0/build/cssfonts/fonts-min.css" />
  <link rel="stylesheet" type="text/css" href="css/main.css" />

  <!-- Google Maps API and jQuery, served by Google -->
  <script type="text/javascript" 
src="http://maps.google.com/maps/api/js?sensor=false"></script>
  <script type="text/javascript" 
src="http://ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.min.js"></script>
  <!-- jQuery UI Maps files for placing markers and for parsing Microdata -->
  <script type="text/javascript" src="../web/jquery.fn.gmap.js"></script>
  <script type="text/javascript" 
src="../ui/jquery.ui.map.services.js"></script>
  <script type="text/javascript" 
src="../ui/jquery.ui.map.microdata.js"></script>
  ...

Then add a block of jQuery, which gets the #map_canvas div when the page initializes and turns it into a map (see Listing 7). You'll add more to this function later.

Listing 7. Initializing the map
...
  <!-- Grab the #map_canvas div and turn it into a map -->
  <script type="text/javascript">
    $(function() {
      map = $('#map_canvas');

      map.gmap().bind('init', function () {
      });
    });
  </script>
</head>

When you reload the page, you will see the map (in Figure 5). It doesn't yet have any events marked; you still need to add a little more information for those to show up.

Figure 5. A basic map without any markers
Screen capture of a basic map without any markers

Adding the locations in microdata

You need to add the locations of the events in microdata before you can add the markers to the map. For now, treat each address as a single string. However, addresses have multiple distinct parts, and you want to express this structure in the microdata to easily and consistently get the right values, even if you change the HTML structure later.

First, add the location property, which takes a Place for its value (see Listing 8).

Listing 8. Adding the location property to the event
<!-- Event 1: DrupalCamp Toulouse -->
<div itemscope itemtype="http://schema.org/Event">
  ...
  <div itemprop="location" itemscope itemtype="http://schema.org/Place">
    ENSEEIHT, Toulouse, Haute-Garonne, FR
  </div>
  ...
</div>

The Place can have a name and a postal address. A postal address is an item with its own properties, such as the street address, the city (addressLocality), state (addressRegion), and addressCountry, which should be the two-letter ISO 3166-1 alpha-2 country code. Listing 9 breaks these properties out using span tags.

Listing 9. Using span tags
<!-- Event 1: DrupalCamp Toulouse -->
<div itemscope itemtype="http://schema.org/Event">
  ...
  <div itemprop="location" itemscope itemtype="http://schema.org/Place">
    <span itemprop="name">ENSEEIHT</span><br /> 
    <span itemprop="address" itemscope itemtype="http://schema.org/PostalAddress">
      <span itemprop="streetAddress">Rue Sylvain Dauriac</span><br />
      <span itemprop="addressLocality">Toulouse</span>,
      <span itemprop="addressRegion">Haute-Garonne</span>,
      <span itemprop="addressCountry">FR</span>
    </span>
  </div>
  ...
</div>

You can format the address for the other event in the same way.


Placing the locations on the map

You're ready to add the markers to the map. First, parse the microdata on the page into an object that you can use. To do this, specify which type of top-level item is the source of the data that you want. Top-level items in microdata can be seen as root items; they are items that are not a property of any other item but do have properties of their own. Specify http://schema.org/Event. This returns an object for each of the Events (see Listing 10).

Listing 10. Parsing the microdata into an object
<script type="text/javascript">
    $(function() {
      map = $('#map_canvas');

      map.gmap().bind('init', function () {
        // Process the microdata for each Event into an object.
        map.gmap('microdata', 'http://schema.org/Event', function(result, item, index) {
          window.console.log(result);
        });
      });
    });
</script>

You can see the object that is created from the microdata in the JavaScript console in browsers such as Firefox and Chrome. Each item in microdata is translated into an object with its own properties object, which contains any of the itemprop values from that item. Because properties in microdata can be multivalued, itemprop values are handled as arrays (see Figure 6).

Figure 6. Examining the object in the JavaScript console of Chrome
Screen capture of the object source in the JavaScript console of Chrome

This object has all of the properties you need to get the geocoordinates of the location, which you need to place the marker for the event. To get this information, extract the parts of the address from the object and combine them into an address string that Google will understand. You can then use Google's Geocoder to get the coordinates for this address. The jQuery UI Map library provides a wrapper function that you can use to run the Geocoder request.

Note that if you get an undefined error, double check to make sure that you have all of the required properties on all of the Events that are listed (see Listing 11).

Listing 11. Running the Geocoder request for the address
<script type="text/javascript">
    $(function() {
      map = $('#map_canvas');

      map.gmap().bind('init', function () {
        // Process the Microdata for each Event into an object.
        map.gmap('microdata', 'http://schema.org/Event', function(result, item, index) {
          // Traverse from the Event to the Place and from the Place to the
          // Address to get the properties.
          var placeName = result.properties.location[0].properties.name[0];
          var streetAddress = 
result.properties.location[0].properties.address[0].properties.streetAddress[0];
          var city = 
result.properties.location[0].properties.address[0].properties.addressLocality[0];
          var state = 
result.properties.location[0].properties.address[0].properties.addressRegion[0];
          var country = 
result.properties.location[0].properties.address[0].properties.addressCountry[0];

          // Join the address parts into a comma-separated string.
          var address = [placeName, city, state, country].join(', ')

          // Run the Geocoder request for the address.
          map.gmap('search', {'address': address } , function(result, status) {
            window.console.log(result);
            // We will place the marker here.
          });
        });
      });
    });
</script>

If you look at the result in the console, you will see that the service has returned one or more locations that match your address. You will only use the first one as this is the best match. The object has a geometry property that contains the geocoordinate information that you need (see Listing 12).

Listing 12. Placing the marker
<script type="text/javascript">
    $(function() {
      map = $('#map_canvas');
      map.gmap().bind('init', function () {
        // Process the Microdata for each Event into an object.
        map.gmap('microdata', 'http://schema.org/Event', function(result, item, index) {
          // Traverse from the Event to the Place and from the Place to the
          // Address to get the properties.
          var placeName = result.properties.location[0].properties.name[0];
          var streetAddress =
result.properties.location[0].properties.address[0].properties.streetAddress[0];
          var city = 
result.properties.location[0].properties.address[0].properties.addressLocality[0];
          var state = 
result.properties.location[0].properties.address[0].properties.addressRegion[0];
          var country = 
result.properties.location[0].properties.address[0].properties.addressCountry[0];

          // Join the address parts into a comma-separated string.
          var address = [placeName, city, state, country].join(', ')

          // Run the Geocoder request for the address.
          map.gmap('search', {'address': address } , function(result, status) {
            if (status == google.maps.GeocoderStatus.OK) {
              // Create a LatLng object.
              var lat = result[0].geometry.location.lat();
              var lng = result[0].geometry.location.lng();
              var latlng = new google.maps.LatLng(lat, lng);
  
              // Place the marker.
              var markerOptions = { 
                'bounds':true,
                'position': latlng
              };
              map.gmap('addMarker', markerOptions);
            } else {
              alert("Geocode was not successful for the following reason: " + status);
            }
          });
        });
      });
    });
</script>

When you reload the page, you should see the location markers as in Figure 7.

Figure 7. Markers placed on the map
Screen capture of markers placed on the map

Making the map interactive

The map is even more helpful if users can click on the marker to get event details, such as the date and a link to the event. Adding this functionality isn't hard to do.

First, extract the additional information that you want about the event from the parsed microdata (see Listing 13).

Listing 13. Getting the supplementary information
<script type="text/javascript">
    $(function() {
      map = $('#map_canvas');
      map.gmap().bind('init', function () {
        // Process the Microdata for each Event into an object.
        map.gmap('microdata', 'http://schema.org/Event', function(result, item, index) {
          var eventName = result.properties.name[0];
          var logo = result.properties.image[0];
          var url = result.properties.url[0];
          var start = result.properties.startDate[0];

          ...
      });
    });
</script>

Before you add the marker to the map, create a block of HTML with the event details (see Listing 14).

Listing 14. Creating the HTML for the info window
            // Run the Geocoder request for the address.
            map.gmap('search', {'address': address } , function(result, status) {
              if (status == google.maps.GeocoderStatus.OK) {
                // Create a LatLng object.
                var lat = result[0].geometry.location.lat();
                var lng = result[0].geometry.location.lng();
                var latlng = new google.maps.LatLng(lat, lng);
    
                var eventDetails = '';
                eventDetails += '<div class="iw">';
                eventDetails += '<img src="'+logo+'"></img>';
                eventDetails += '<h2><a href="'+url+'">'+eventName+'</a></h2>';
                eventDetails += new Date(start).toDateString();
                eventDetails += '</div>';
    
                // Place the marker.
                ...

Then add the event details to the marker you're creating. Use jQuery's .click to say which function you want to run when someone clicks the marker. In the function, use the openInfoWindow helper function provided by the jQuery UI Maps library to build the window and simply pass in your HTML as the content for that window (see Listing 15).

Listing 15. Adding the Info Window as a click event
            // Place the marker.
            ...
            map.gmap('addMarker', markerOptions).click( function() {
              map.gmap('openInfoWindow', { 'content': eventDetails }, this ); 
            });

Reload after this task is done and then click the marker. You'll see the window pop up with the event details (see Figure 8).

Figure 8. Click event on marker
Screen capture of window with event details after user clicks the event marker

To improve the appearance, add a few CSS rules. I added these in the downloadable example code (see Download). In the end, your map resembles Figure 9.

Figure 9. Final map
Screen capture of final map with two event markers, a pop-up window, and full event details

Conclusion

Microdata makes it easier to use the data on your page in dynamic ways, such as on Google maps, using jQuery plugins. The microdata specification talks about the generic scripts which you can use as plug-ins on different, unrelated sites. Microdata also makes it easier to combine data from multiple different sites to create new applications. In the next article, I will demonstrate how you can use Drupal to create such an application.


Download

DescriptionNameSize
Article source codejquerymicrodatapt1_source.zip464KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Open source, Web development
ArticleID=769789
ArticleTitle=Make HTML5 microdata useful, Part 1: Using jQuery on top of microdata
publish-date=03062012