Sponsor: NC jQuery & JavaScript Camp: Winter 2010

I paid the extra money to become a sponsor of NC jQuery & JavaScript Camp: Winter 2010.  I’m going to be attending with Brian Hall and perhaps others on January 30.  The current list of attendees is really exciting.  My anticipation has been raised by listening to the yayQuery podcast and the Official jQuery podcast.

jquery Logo

TabbedMaxContent with Multiple Points

Last time I showed how to use the TabbedMaxContent from Google to display large amounts of possibly dynamic data. The example I used only had one GMarker, though, which isn’t especially useful. Most map users want to have multiple points. So, with that in mind, here are some of the techniques that you need to use to handle multiple markers that use TabbedMaxContent.

First, we need some latitude, longitude, and label triples. Since this example isn’t using AJAX to retrieve the information, we’re going to use some fixed JavaScript assignments:

pointdefs[0] = new Object;
pointdefs[0].latitude  = 35.6058733469;
pointdefs[0].longitude = -77.3629331589;
pointdefs[0].label     = "Austin Building";

pointdefs[1] = new Object;
pointdefs[1].latitude  =  35.606426;
pointdefs[1].longitude = -77.364464;
pointdefs[1].label     = "Rawl Building";

pointdefs[2] = new Object;
pointdefs[2].latitude  =  35.613659;
pointdefs[2].longitude = -77.370827;
pointdefs[2].label     = "Cotanche Building";

There’s nothing special here, we’re just creating an array of objects to make life easier. Each object has three attributes, the previously mentioned latitude, longitude, and label. So, we have objects with the information we need, how do we turn these into useful map objects? We will need to loop over the basic geo data and:

  1. create GPoints (which are Google’s latitude/longitude classes)
  2. create a Gmarker from the previous GPoint
  3. create two MaxContentTabs for the GMarker
  4. assign a click event handler to fire off the MaxContentTab open method

All of this functionality can be encapsulated into a single function that returns the constructed GMarker:

function createMarker(point, index, pointdef) {
	var tab1html;
	var tab2html;
	var tab1;
	var tab2;
	var tabs = [];	
	var marker = new GMarker(point);
	
	tab1html = '

Point ' + index + ' tab 1

'; tab1 = new MaxContentTab('Tab 1', tab1html); tabs.push(tab1); tab2html = '<p>This is the second tab. Here is a random valued Google Chart to give an example of interesting content</p><p id="tab2image"></p>'; tab2 = new MaxContentTab('Tab 2', tab2html); tabs.push(tab2); // HTML for minimized and maximized state of the TabbedMaxContent window var regularInfo = pointdef.label + '<br/ ><a href="javascript:void(0)" onclick="javascript:map.getInfoWindow().maximize()">more information ... </a>'; var summaryInfo = '<a href="javascript:void(0)" onclick="javascript:map.getInfoWindow().restore()">less information ... </a>'; GEvent.addListener(marker, "click", function() { marker.openMaxContentTabsHtml(map, regularInfo, summaryInfo, tabs, { maxTitle: 'Detailed Information'} ); }); return marker; }

In the code above, we’re passing in a GPoint, an index (for labeling purposes), and a pointdef (one of the objects in our array). Other than handling an object’s attributes for the label, the code is pretty much the same as our previous MaxContentTab example. Using a function gives us the isolation of the constructed GMarker to prevent the JavaScript engine from reusing the string data and giving us the proper results when we click each GMarker.

All that remains is a driving loop that calls the function:

			for (var i = 0; i < pointdefs.length; i++) {
				// Single GLatLng instance
				var point;

				point =  new GLatLng(pointdefs[i].latitude, pointdefs[i].longitude);			
				marker = createMarker(point, i, pointdefs[i]);

				// Add this marker to the map itself
				map.addOverlay(marker);
				markers.push(marker);
			}

In real production code, you'd want to create a variable to contain the array length, rather than having it evaluated for every element of pointdefs. In addition, you could move the GPoint assignment to the createMarker function, rather than passing it in as I've done here. The point creation in the loop, rather than the function, is a remnant of earlier examples I created for myself.

The final line of code in the loop, markers.push(marker), is not needed for our example in this posting. It's a taste of things to come, an array to allow for a sidebar that gives you alternate means of accessing your map markers.

Update: I almost forgot my link to a live example that puts all the code together.

Adding Dynamic Behavior When Using TabbedMaxContent

Last time I promised a continuation of the series describing how I used the Tabbed Max Content JavaScript class for some Google Maps at ECU. One of the inspirations I used was the many examples in the Google Maps API Demo Gallery. Specifically, I used code snippets from the Tabbed Max InfoWindow + Dynamic Content example. Thanks to Nianwei Liu for the Tabbed Max InfoWindow example posted there.

The simple example from my last posting didn’t need any dynamic actions; all of the rendering was handled by the Tabbed Max Content itself. If the contents of the Tabbed Max Content may change, though, we need additional actions.

GEvent.addListener(map.getTabbedMaxContent(), 'selecttab', function(tab) {
   switch (tab.getLabel()) {
      case 'Availability':
         // Generate the dynamic HTML as strings
         // Set the html content  of the div, p, or other block element to be that string
      break;
   }
});

In the code above we have the outline of our basic tab selection event listener. The method that will fire is selecttab, generated by the map’s current Tabbed Max Content instance. We only have to do this once in the entire map, not once per marker.

I have created a simple example that generates a random value and sets the image data for a Google chart to graph the value:

http://www.carolinamantis.com/demo/tabbedmaxcontent/ex02.html

My example adds this block of code for the dynamic aspect:

GEvent.addListener(map.getTabbedMaxContent(), 'selecttab', function(tab) {
   switch (tab.getLabel()) {
      // We could also have used case 1 below (tabs are numbered from 0 and increase as they go left to right)
      // Here I choose to switch on the actual text of the tab, note it is enclosed in quotes but the numeric 1
     // is not.
     case 'Tab 2':
        // Everything needed for a Google chart but the final value
        var url = 'http://chart.apis.google.com/chart?chs=170x170&chtt=Power%20Reading&cht=gom&chd=t:';
        // Generate a random number between 0 and 100
       var randomnumber=Math.floor(Math.random()*101);
       url += randomnumber;
       var htmlsrc = '';
       // Use jQuery to set the HTML for the paragraph.
       $('#tab2image').html(htmlsrc);
    break;
    }
});			

If you visit the example page, you can click on the point and then expand the Tabbed Max Content. Every time you switch between Tab 1 and Tab 2, a new random value will be generated and the graphic will be updated.

Next time I will post about how to manage the case where you have many points on your map, and must generate separate dynamic content on a tab for each one.

Using TabbedMaxContent with Google Maps

While working on a project to show general purpose computer labs at East Carolina University, I needed to combine several techniques, libraries and technologies.  This post is about my experience with the TabbedMaxContent JavaScript class from the gmaps utility library.

The examples and API reference certainly helped, but it took more effort to figure out how to handle some of the dynamic interactions needed in our lab map.  The big challenge was separating the functionality to display the “static” information in the tabs from the dynamic information.

Whenever I’m putting together a project like the lab map, I like to create bite sized examples of the parts working independently. Then combining the techniques prevents trying to debug everything all at once. With that in mind, I wanted my own example of a Google Map with a single marker point using a TabbedMaxContent. I have put together a simple example at http://www.carolinamantis.com/demo/tabbedmaxcontent/ex01.html

The beginning of this code uses the Google loader to load both jQuery and the Google Maps API. Why use your own bandwidth (and your users) when they can use a previously cached copy?


	google.load("maps", "2");
	google.load("jquery", "1.3.2");

Note that where I have PUTYOURMAPKEYHERE you need to use the Google Maps key generated for the domain/IP address you’re using to host the map. If you only want the Google AJAX Library APIs and no map, then you can leave the query string (including question mark) out of the URL.

You also need to include the source for the TabbedMaxContent class. I put mine in my document root, but please host your own if you are going to use it.


Continuing on, we have the meat of the TabbedMaxContent and Gmarker creation:

			tab1html = '

This is the first tab. You have few limits to the HTML that may be put here

'; tab1 = new MaxContentTab('Tab 1', tab1html); tabs.push(tab1); tab2html = '

This is the second tab. Here is a fixed Google Chart to give an example of interesting content

'; tab2 = new MaxContentTab('Tab 2', tab2html); tabs.push(tab2); // HTML for minimized and maximized state of the TabbedMaxContent window regularInfo = '
more information ... '; summaryInfo = 'less information ... '; // Add a listener for the click event on the marker itself GEvent.addListener(marker, "click", function() { marker.openMaxContentTabsHtml(map, regularInfo, summaryInfo, tabs, { maxTitle: 'Detailed Information'} ); }); // Add this marker to the map itself map.addOverlay(marker);

Beyond the usual point/marker/map overlay creation, the difference here is the additional variables for the HTML tab contents, the MaxContentTab instances (which in essence are the tabs), and the addition of a listener for when the marker is clicked. When our marker is clicked, we want it to invoke openMaxContentTabsHtml, a method which is part of the TabbedMaxContent API. We pass to this method our map instance, the HTML string for the minimized information, the HTML string for the maximized information, the array of MaxContentTabs, and and additional configuration hash that in our example specifies a title for when the TabbedMaxContent is maximized.

Note that you can have the marker creation in a loop and reuse the variables point, marker, tabNhtml, and tabN. The lab example, which is more sophisticated in that it is using some jQuery AJAX loading, does just that.

For now, I’ll leave off with some changes that I would make if I were to redo my code. I’d probably use jQuery to get the map div, as opposed to the generic getElementById. I would also try to better use the jQuery event model and management system rather than the GEvent system. For now, these techniques work and work cross-browswer. At the end of the day, that’s what’s important.

Next time, I’ll go deeper into TabbedMaxContent with jQuery’s AJAX APIs to handle a retrieved marker set and to have dynamic data within the tabs.

Until then…

Creating a JSONP Data Provider in PHP

PHP 5.2 or PECL json 1.2 has the ability to convert data structures into JSON:

$data = array('zero', 'one', 'two', 'three');
$json = json_encode($data);
echo $json

However, if you are deploying to a lowered PHP version, either because your hosting provider hasn’t caught up or you’re using Red Hat Enterprise (even the latest 5.3 only has PHP 5.1.2), then you need an alternate solution. I’ve visited http://www.json.org and found a good package: PEAR::Services_JSON (aka pear install Services_JSON). Although it’s in the beta channel, I’ve had good luck with it.

The equivalent code to the above is:

require('Services/JSON.php');
$data = array('zero', 'one', 'two', 'three');
$encoder = new Services_JSON(); 
$json = $encoder->encode($data);
echo $json;

So, that is how you get a JSON string. What if you want to actually emit the JSON in an AJAX-ically useful way? Just add the PHP header function:

require('Services/JSON.php');
$data = array('zero', 'one', 'two', 'three');
$encoder = new Services_JSON(); 
$json = $encoder->encode($data);
header("Content-type: application/json");
echo $json;

Now, for the final touch. Turning out standard JSON into JSONP. The only difference is that we need a JavaScript function definition wrapping the code:

require('Services/JSON.php');
$data = array('zero', 'one', 'two', 'three');
$encoder = new Services_JSON(); 
$json = $encoder->encode($data);
header("Content-type: application/json");
echo 'callbackname (' . $json . ');';

We’ve added a function wrapper around the JSON. That allows for the data to be retrieved using the jQuery API. However, we can’t hard code the callbackname as you see above. We need to use the passed in value from jQuery.

$(function() {
     $.getJSON('provider.php?callback=?',
                    {},  // No additional parameters sent
                    function (data) {
                         // data is now JSON object instantiated from retrieved info
                         alert(data[0]) // Will show 'zero' in the alert that pops up
                    })
     });
});

That strange second question mark in the URL will be replaced by a jQuery created random number string that is expected to be returned in your callback name.

Save the code below as provider.php:

require('Services/JSON.php');
$data = array('zero', 'one', 'two', 'three');
$encoder = new Services_JSON(); 
$json = $encoder->encode($data);
header("Content-type: application/json");
echo $_GET['callback'] . ' (' . $json . ');';

If you are using the outstanding Firebug add-on for Firefox, you’ll be able to see the GET request made by the page containing the jQuery code as well as the JSONP response from the script.

Why go through this trouble? Because we can now put provider.php on any server, regardless of whether the page calling provider.php is in the same domain or not. JSONP gets around the normal AJAX “same domain” restriction.

Now that we have our own provider, we can create simple arrays, complex arrays, and even objects that can be turned into JSON and sent to jQuery enabled pages, regardless of domain.

For additional information, see this IBM Developerworks article. It lists some of the existing JSONP services, including the URL to use with the jQUERY ajax invocations.