JavaScript

Simple jQuery Show/Hide Tabs

Updated June 20, 2010: There’s an improved version of this code posted here. Use it instead!

I am learning jQuery and really loving it! Here’s a simple bit of code I wrote to achieve an interface element. It’s basically tabs — there’s a list of links and when you click one the corresponding content is shown. It assumes there’s an equal number of links and content blocks. And that they’re in the same order. The HTML is like so:


The # links / id’s on the content div’s are to give some functionality for people without JavaScript. Here’s the jQuery:


Simple! Best wishes to you in your coding!

Posted Dec 09, 2009

Simple jQuery Fade-in/Fade-out slideshow

Hi there. I’m learning jQuery and finding it super easy and fun to work with!

Here’s a simple fade-in/out slideshow I wrote for a friend’s website. I knew there were already plug-ins out there for achieving this sort of thing, but I decided to write my own in order to become more familiar with some of the basic concepts.

See it in action. Here’s the HTML we’re outputting:


And here’s the jQuery:


As you can see, it’s pretty basic. There’s a number of improvements that could be made, such as having the jQuery insert the control links as they’re somewhat redundant for the non-JS user. Also, it’s a bit fussy about divs — you might want to have some inside your slides but, as written, that would confuse it. I’ll post again if I get around to addressing that.

As always, best wishes to you in your coding!

Posted May 09, 2010

ModX Snippet for a simple jQuery Flickr fade-in/out slideshow

Hello! I posted previously about a simple jQuery slideshow I created for accessing sets on Flickr. To use it at work, where we’re running ModX CMS, I turned it into an easily-reusable “snippet.” You can see it in action here.

Here’s what you do:

  1. Call jQuery in the head of your template. As of this writing I’m using 1.3;
  2. Upload the Flickr API library to your server and call it something like “flickr_api.inc.php”. It is a very slightly modified version of Rasmus Lerdorf’s PHP5 Flickr_API;
  3. Upload the jQuery slideshow to your server and call it something like “flickrslideshow.js”. An explination of the jQuery can be found in a previous post;
  4. Create a new snippet called “flickr-slideshow”. The contents of that snippet is bellow;
  5. And finally, call the snippet like so: [[flickr-slideshow? &photoset=`XXXXXXXXXXXXXXXXX`]], replacing, of course, the XXXs with the ID of the Flickr set.

The contents of the “flickr-slideshow” snippet:

<?php
 
// Get the photoset from the snippet call
$photoset = isset($photoset) ? $photoset : '';
 
// Your path to to the Flickr API library 
include_once("pathto/flickr_api.inc.php");
 
// Your API key	
$secrets = array('api_key'=>'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX','api_secret'=>'XXXXXXXXXXXXXXXX');
 
// Create an instance of the Flickr class, using your credentials
$flickr = new Flickr($secrets);
 
// Load the set we're looking for into an array
$photos = $flickr->photosetsGetPhotos($photoset);
 
// Start a counter
$counter = 1;
 
// Get the total number of photos in the set
$photocount = count($photos['photos']);
 
// A wrapper div, used by the jQuery
echo '<div class="flickrslideshow">';
 
// Now to loop through the photos in the set...
foreach($photos['photos'] as $photo) {
 
// Get the path to the image
$url = $flickr->getPhotoURL($photo,'');
 
// Get the image info
$info = $flickr->photosGetInfo($photo['id']);
 
// Another div for the jQuery
echo '<div>';
 
// Output the image tag, with the title as the alt
echo '<span class="image"><img src="'.$url.'" alt="'.$info['title'].'" /></span>';
 
echo '<p class="caption">';
 
// If the photo has a title, output that...
if ($info['title'] != ""){ echo '<strong>'.$info['title'].'.</strong> '; }
 
// and ditto for the descrition.
if ($info['description'] != ""){ echo ' '.$info['description'].' '; }
 
echo '</p>';
 
// Output the count
echo '<p class="controls">Image '.$counter.' of '.$photocount.' | ';
 
// Set up some numbers to use for the prev/next controls
$counterprev = $counter - 1;
$counternext = $counter + 1;
 
// Output the "previous" control
if ($counterprev > 0) { echo '<a href="#'.$counterprev.'" class="prev">Prev</a>'; }
 
// Output a separator, if needed
if ($counterprev > 0 && $counternext <= $photocount) { echo ' / '; }
 
// Output the "next" control
if ($counternext <= $photocount) { echo ' <a href="#'.$counternext.'" class="next">Next</a>'; }
 
echo '</p></div>';
 
// Iterate the counter
$counter++;
 
}
 
// Include the jQuery slideshow code (change to match the path on your server, of course)
echo '<script type="text/javascript" src="/pathto/flickrslideshow.js"></script>';
 
echo '</div>';
 
?>

This could be improved in a myriad of ways. For instance, the presentation layer could be abstracted out into ModX chunks and the jQuery could be made to insert the markup which is essentially redundant in the no-script view.

If you notice any errors please post in the comments. As always, I hope that seeing this helps you in your own coding!

Update, May 23 2012: I’m no longer working on a ModX site, so can no longer do much to support this code. It should be considered “as is.”

Posted May 21, 2010

jQuery Extract Headings for Tab Controls

Hi there. I posted previously about making jQuery tabs, but this week I found myself wanting take that code a bit further. In the old version, the “controls” were written out in a ul above the slides. It got the job done, but wasn’t ideal from a progressive-enhancement point of view (non-JS don’t really have much need for them, so they really shouldn’t be there for those users).

In this version we’re going to include an h3 at the top of each slide and have jQuery pull those out to make the ul controls. The HTML is like so:

<div id="slides">
	<div class="slide">
		<h3>Slide 1</h3>
		<p>This is the content of slide one</p>
	</div>
	<div class="slide">
		<h3>Slide 2</h3>
		<p>This is the content of slide two</p>
	</div>
</div>

And the jQuery (the new action is in the first fifteen lines):

$(document).ready(function() {
 
	// Insert the wrapper for the controls
	$('#slides').before('<ul id="slidecontrols"></ul>');
 
	//For each heading...
	$('#slides div.slide > h3').each(function(){
		// Copy it into the controls
		$(this).clone().appendTo("#slidecontrols");
		// and replace the heading markup with list markup, keeping the contained text
		$('#slidecontrols h3').replaceWith('<li><a href="#">' + $(this).text() + '</a></li>');
	});
 
	// Hide those H3s
	$('.slide > h3').hide();
 
	//Set the initial state: highlight the first button...
	$('#slidecontrols').find('li:eq(0)').addClass('selected');
 
	//and hide all slides except the first one
	$('#slides').find('> div:eq(0)').nextAll().hide();
 
	//actions that apply on click of any of the buttons
	$('#slidecontrols li').click( function(event) {
 
		//turn off the link so it doesn't try to jump down the page
		event.preventDefault();
 
		//un-highlight the buttons
		$('#slidecontrols li').removeClass();
 
		//hide all the slides
		$('#slides > div').hide();
 
		//highlight the current button
		$(this).addClass('selected');
 
		//get the index of the current button...
		var index = $('#slidecontrols li').index(this);
 
		//and use that index to show the corresponding slide
		$('#slides > div:eq('+index+')').show();
 
	});
 
});

Happy coding!

Posted Jun 20, 2010

Creating a jQuery Slideshow of the WordPress Attachments Gallery

Since version 2.5 WordPress has included the ability to upload images and “attach” them to posts and pages. The user can upload a batch of images and the system will create copies at various sizes specified in the site settings. There’s an interface for setting an order and captions. It’s good, and I decided to make use of it for a site I’ve been working on.

The image set is normally included in the post by typing the shortcode in the post body. But in this case I didn’t want my users to have to bother with that — I wanted to call the gallery directly in the theme. And then I wanted to display the images using a simple sliding interface. Unlike my previous slideshow, this one needed to support many instances on a single page.

See an example of the jQuery in action | Download a ZIP of all the files

Insert the Attachment Gallery via the Theme

As helpfully explained in the WordPress codex, it’s very easy to call the gallery from the theme:

<?php echo do_shortcode('[gallery option1="value1"]'); ?>

But I wasn’t satisfied with the HTML this outputs; I wanted to create my own, which meant adding a function. For this I am indebted to Dameian Lott’s WordPress Image Attachment Gallery (Revisited) post. His method has a lot of useful features and is meant to be really flexible so if you’re going to be doing customization I urge you to head over to his site, grab the full code and read his explanation.

I stripped it down to only the parts I needed. Specifically, I’m outputting the “large” size images, wrapped in certain markup:

Note: This function has been revisited in the post Showing Images from the WordPress Attachments Gallery. Use it instead!

function marty_get_images($size = 'large', $limit = '0', $offset = '0', $post_id = '$post->ID') {
	global $post;
 
	$images = get_children( array('post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => 'ASC', 'orderby' => 'menu_order ID') );
 
	if ($images) {
		echo '<div class="gallery">';
 
		$num_of_images = count($images);
 
		if ($offset > 0) : $start = $offset--; else : $start = 0; endif;
		if ($limit > 0) : $stop = $limit+$start; else : $stop = $num_of_images; endif;
 
		$i = 0;
		foreach ($images as $attachment_id => $image) {
			if ($start <= $i and $i < $stop) {
			$img_title = $image->post_title;   // title.
			$img_description = $image->post_content; // description.
			$img_caption = $image->post_excerpt; // caption.
			$img_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
 
				if ($img_alt == '') {
				$img_alt = $img_title;
				}
 
				if ($size == 'large') {
				$big_array = image_downsize( $image->ID, $size );
 				$img_url = $big_array[0]; // large.
				} else {
				$img_url = wp_get_attachment_url($image->ID); // url of the full size image.
				}
			?>
 
			<div class="image"><p><img src="<?php echo $img_url; ?>" alt="<?php echo $img_alt; ?>" title="<?php echo $img_title; ?>" /></p></div>
 
			<?php if ($img_caption != '') : ?>
			<div class="attachment-caption"><?php echo $img_caption; ?></div>
			<?php endif; ?>
			<?php if ($img_description != '') : ?>
			<div class="attachment-description"><?php echo $img_description; ?></div>
			<?php endif;
			}
			$i++;
		}
	}
 
		echo '</div><!-- End gallery -->';
 
}

And then I call it in my theme like so:

<?php marty_get_images('large','0','0',"$post->ID"); ?>

The jQuery Slideshow

Before getting into it, I want to say that there are a number of things about this code that are unresolved, and even as I go through writing these notes I’m seeing duplication that ought to be refactored/abstracted out. But I’m posting it here so as to assist others who, like me, are still learning this stuff. Any suggestions you have to improve this code are very welcome!

OK, first the CSS. I like to put as much of the CSS as possible into my stylesheet (as opposed to inserting it on the fly).

.gallerywrapper {width: 700px; overflow: hidden; }
.gallerywrapper .image {width:700px; float: left; }
.gallerywrapper .image p {margin-bottom: 0;}
.gallerywrapper .slidecontrols {clear: both; width: 100%; overflow: hidden; font-size: 11px; margin-top: 0;}
.gallerywrapper .slidecontrols a {color: #ccc; font-weight: bold; font-size: 110%;}
.gallerywrapper .slidecontrols a:hover {text-decoration: none;}
.gallerywrapper .slidecontrols a.active {color: #666;}
.gallerywrapper .slidecontrols a.active:hover {cursor: pointer; text-decoration: underline;}

As you can see, my slides are set to 700px wide. That’s specific to my case and you will want to change that. The best solution would be to re-write the code so that it’s dynamic to whatever context it’s applied to, and down the road I’ll do that.

On to the jQuery! First off we’ll detect the width of our first slide, as we’ll need this variable for calculations later on.

var slideWidth = $('.image:eq(0)').width();

Presenting images in a slideshow requires some elements (such as controls) that aren’t relevant when the images simply follow each other on the page. Because we want our non-JavaScript visitors to be served only the code they need, we left these elements out of the markup we generated above. Here we’ll inject that mark-up which is specific to the slideshow:

// When there's more than one slide, act on the Gallery div
$('.image:not(:only-child)').parent().each(function(index) {
 
	// Count the number of slides
	imageLength = $(this).find('.image').length;
 
	// Wrap the whole Gallery in a div.
	$(this).wrap('<div class="gallerywrapper" />');
 
	// Set the Gallery div to the combined width of all the slides
	$(this).css('width', slideWidth * imageLength);
 
	// Inject the controls
	$(this).parent().append('<p class="slidecontrols"><a title="Previous" class="prev"><span>&larr;</span></a> <a title="Next" class="next"><span>&rarr;</span></a> <span class="counter" /></p>');
 
	// Add the slide count to the controls
	$(this).parent().find('.slidecontrols').append('/' + imageLength);
 
	// Determine which instance of the gallery we're working with
	thisWrapper = $('.gallerywrapper').index($(this).closest('.gallerywrapper'));
 
	// Run the Manager method, which is explained below
	manageGallery(0, thisWrapper);
 
 });
 
 
// The listener on the controls
$('.slidecontrols a').click(function(event) {
 
	// Determine which instance of Gallery we're working with
	thisWrapper = $('.gallerywrapper').index($(this).closest('.gallerywrapper'));
 
	// Detect the left-margin of the Gallery...
	currentMargin = parseInt($(this).closest('.gallerywrapper').find('.gallery').css('marginLeft'), 10);
 
	// ... and use it to determine which slide is showing
	currentPosition = currentMargin / -slideWidth;
 
	// Set the target position of the slideshow, determined by which link was clicked
	nextPosition = ($(this).attr('title')=='Next') ? currentPosition+1 : currentPosition-1;
 
	// Run the gallery manager
	manageGallery(nextPosition, thisWrapper);				
 
});
 
// The listener on the image
// This is similar to the above. It would probably be possible/better to combine them into one, reducing duplication.
$('.image').click(function(event) {
	thisWrapper = $('.gallerywrapper').index($(this).closest('.gallerywrapper'));
	currentMargin = parseInt($(this).closest('.gallerywrapper').find('.gallery').css('marginLeft'), 10);
	currentPosition = currentMargin / -slideWidth;
 
	// Here we're counting the total number of slides...
	maxPosition = $(this).closest('.gallerywrapper').find('.image').length -1;
 
	// ... and setting the slideshow to advance by one unless we're at the end, in which case return to the beginning
	nextPosition = currentPosition < maxPosition ? currentPosition+1 : 0 ;
 
	manageGallery(nextPosition, thisWrapper);				
	});
 
 
// And, finally, the Manager method
function manageGallery(position, index){
 
	// Here again we're detecting the number of slides
	maxPosition = $('.gallerywrapper:eq('+index+')').find('.image').length -1;
 
	// Create shorthands for the elements we're going to manipulate
	next = $('.gallerywrapper:eq('+index+')').find('.next');
	prev = $('.gallerywrapper:eq('+index+')').find('.prev');
	gallery = $('.gallerywrapper:eq('+index+')').find('.gallery');
	counter = $('.gallerywrapper:eq('+index+') .slidecontrols .counter');
 
	// This is for displaying the current slide number to the user
	displayposition = position + 1;
 
 
	// If the target position is within range...
	if (position <= maxPosition && position >= 0) {
 
		// do the slide...
		$(gallery).animate({ 'marginLeft' : 700 * (-position) } );
 
		// and update the display counter.
		$(counter).html(displayposition);
 
		// If we're at the last slide, grey out the "Next" link
		position == maxPosition ? $(next).removeClass('active') : $(next).addClass('active');	
 
		// And if we're at the first slide, grey out the "Previous" link
		position == 0 ? $(prev).removeClass('active') : $(prev).addClass('active');
	}
}

That’s it! There’s no doubt this could be greatly improved, but I hope seeing it helps you in your project.

Posted Apr 07, 2011

Interactive In-Gallery Video Kiosk for Artist Midi Onodera

An interface to display short video clips in a gallery setting.
An interface to display short video clips in a gallery setting.

Interactive Touchscreen Interface using jQuery and HTML5 Video

Playing a video.
Playing a video.

In 2009 artist Midi Onodera created a series of 52 short clips designed for podcasting. I have been fortunate to be involved in this project, providing technical support and new media development. When exhibition opportunities arise, such as Onodera’s recent show at Concordia University in Montreal, we grapple with ways to present these new-media-specific works in the gallery setting. Knowing Concordia had some sweet gear, Onodera chose show this series on a large touch-screen monitor. I designed and implemented the interface using web-standard technologies including jQuery and HTML5 Video.

I have posted the code on Github. It uses a jQuery vertical center function I previously created, as well as some other interface effects. One of my favourite of its features is the randomization: The thumbnails are shuffled as the page is loaded and then each moves to the end of the page upon being selected. At its core are the functions to load and play the clips.

The touch-screen rig.
The touch-screen rig.

Swapping Sources in an HTML 5 Video

The interface provides access to 53 video clips, but rather than giving each its own instance, I set up an single video element, omitting a source.

<video class="video" id="video" controls="" autoplay="" width="640" height="426"></video>

And then, when a video is selected, its source is added in and the element begins to play.

$('.video').attr('src','movies/' + movieNum + '.m4v');
document.getElementById('video').play();

But the strangest thing happened when the next video is loaded and played: The audio from the first clip would play along with the new! I tried all sorts of things, including completely unloading the video element and creating a new one for each cycle, but still the audio could be heard. What a strange bug!

In the end I found it could be solved by, upon closing the clip, pausing the playback and then removing the video source:

document.getElementById('video').pause();
$('.video').removeAttr('src');

All of this can be seen in-context over at Github.

Posted Oct 27, 2011

Creating a WordPress Tag Search using jQuery

The technique applied on Seth Anderson’s photo site.

The WordPress taxonomy functions make it easy to give your visitors links to per-tag archives of posts. But is it possible to give them archives of more than one tag? Sure it is!

“Any” & “And” Tag Intersections

By passing parameters via the URL, WordPress will create pages for the intersections of multiple tags. This means we can get posts with any of the tags by separating terms with a comma, like so: /?tag=onetag,anothertag. That is to say a post must have one but need not have all of the tags to qualify.

Conversely, we can get posts with all of the tags by separating terms with a plus, like so: /?tag=onetag+anothertag. This will return only posts that have every tag. There’s various issues with the default output of this, however, so go ahead and install the tTDO Tag Fixes plugin. Running this plugin changes the “all” URL string to /?tdo_tag=onetag+anothertag.

Show All Tags

The first thing we’ll do is print out all the tags, with links, onto the page. This goes in your theme’s functions.php:

function showalltags() {
 
	$tags = get_tags();
	$html;
	foreach ($tags as $tag){
		$tag_link = get_tag_link($tag->term_id);
 
		$html .= "<a href='{$tag_link}' title='{$tag->name} Tag' class='{$tag->slug}'>";
		$html .= "{$tag->name}</a>";
	}
	echo $html;
 
}

And output it somewhere in your theme like so:

<div class="taxonomies">
	<h3 id="tags">Tags</h3>
	<div class="taglist"><?php showalltags(); ?></div>
</div>

Rewrite the markup

Now we’re going to use JavaScript to update the page’s markup to reflect the JavaScript-dependent functionality we’re building. We do this with JS because these controls are only relevant to users with JS-enabled browsers — for others their presence would only be confusing.

Get your jQuery(document).ready(function($) started then include this:

function initialLoad () {
$('.taxonomies h3').html('Select one or more tags, then press <em>&ldquo;Search Tags&rdquo;</em>');
$('.taglist').before('<div class="searchcontrols"><span>Find photos with </span><a id="any" class="tagtoggle selected">any</a><span class="slash"> / </span><a id="all" class="tagtoggle">all</a><span> of the selected criteria</span></div>');	
$('.taxonomies').append('<div><a class="searchtags">Search Tags</a></div>');	
}
 
initialLoad();

Capture tag clicks

When a user clicks a tag, we’ll override the link and instead add the class “selectedtag”:

$('.taglist a').bind('click', function(event) {
event.preventDefault();
 
if ( $(this).hasClass('selectedtag') ) {
$(this).removeClass('selectedtag');
} else {
$(this).addClass('selectedtag');
}
 
});

Toggle “Any” & “And”

Similarly, capture clicks to the controls:

$('.tagtoggle').bind('click', function(event) {
event.preventDefault()
$('.tagtoggle').removeClass('selected');
$(this).addClass('selected');
});

Construct the URL

This function is the core of the mechanism:

function searchTags() {
var optionTexts = [];
 
// For each selected tag...
$('.selectedtag').each(function() { 
 
// capture the slug from class (stripping out the " selectedtag")
var str = $(this).attr('class').replace(/\sselectedtag+/g, '');
 
// and add it to an array
optionTexts.push(str);
});	
 
// Set the URL format appropriate to the selected search type
if ( $('#any').hasClass('selected') ) {
var param = 'tag';
var separator = ',';
} else {
var param = 'tdo_tag';
var separator = '+';			
}
 
// Put the tag list together
var tagstring = optionTexts.join(separator);
 
// And send them off to the final, constructed URL
window.location = '/?' + param + '=' + tagstring;
 
}

Enable the Search Button

Last but not least, the action on the search button calls the search function:

$('.searchtags').bind('click', function(event) {
event.preventDefault()
searchTags();
});

And that’s it! You can grab the complete code from Gist.

Updated December 10, 2011 to capture the tag slug from the class, rather than inferring it from the tag name.
Updated May 20, 2012 to reflect a suggestion made by commenter Fris.
Updated May 20, 2013 to reflect a IE7 fix submitted by Betty Jašková.

Posted Nov 08, 2011

Integrating WordPress and Google Maps API V.3


Here we will go through the steps for creating a Google Maps interface for a WordPress blog. Posts will appear as markers on the map, which you can click to reveal the post content in the Google Maps Infowindow.

Download a ZIP of the complete example.

Note, August 24 2013: In August 2013 Google updated the API to a new visual style that more closely matches their core products. One of the changes is that markers no longer display shadows. There may be other side effects as well, though as of this writing everything else seems to work properly. More investigation is warranted.

Also, a lot of people have trouble getting their markers to display. Often this has to do with the path to the image file. If you download the ZIP from Github, you will likely need to update the paths in map.js. Eventually I’ll revise the tutorial to address this, but in the meantime it’s something to keep an eye out for.

I’m starting with a fresh WordPress install, which at the time of this writing is v. 3.3.1. You may be modifying an existing theme, but for the sake of this tutorial, I’ll start a new one. But note that I’ll be doing only the minimum here to create a working demo, omitting much that is necessary for an actual, standards-compliant and useful website.

Add Location Data to Posts

Update: I’ve published the Address Geocoder plugin for WordPress that simplifies the process of adding location data to posts. I recommend using it instead of the custom fields technique outlined here. After installing it, skip to the next section, “Initialize Custom Theme,” below. Your code will need to be modified in three places, as noted below.

Custom Fields metabox

In order to know where to place a marker on a map, the Google Maps API needs to be supplied with latitude and longitude coordinates in the format of a LatLng object. We’re going to include this as a Custom Field with our posts.

Navigate to the Post Edit page and scroll down to the Custom Fields box. (If you don’t see it, you may need to turn it on via the Screen Options button at the top of the page.)

Name the custom field “latlng.” Now, there are various geocoding services on the web that allow you convert an address to latlng (including the Google Maps API itself, but that’s another topic). For this example we’ll use the White House. Go to Geocoder.us and enter “1600 Pennsylvania Avenue NW, Washington DC”. It will spit out a number in brackets such as (38.898748, -77.037684). Use that as the value and press Add Custom Field.

If you like, you can optionally create a few more posts and geocode them with locations in Washington DC.

Initialize a Custom Theme

In the wp-content/themes directory, create a new folder; mine will be called mapdemo. In this new folder, create a blank file called style.css and one called index.php; these are the minimum needed to created a WordPress theme. Also create a file called map.js.

To make our theme recognizable by WordPress we will place what’s called a theme header into our style.css. The very minimum header is as follows:

/* 
Theme Name: Map Demo
*/

You can now go ahead and activate your theme using the WordPress dashboard (Appearance -> Themes). But if you load your blog at this point you will see that it’s totally blank. Let’s put in the bare-bones of an HTML page, with a couple of functions required by WordPress. Put the following into index.php:

<html>
	<head>
		<title>Maps Test</title>
		<?php wp_head(); ?>
	</head>
 
	<body>
		<?php wp_footer(); ?>
	</body>
</html>

Link the JavaScript Files

Our map will be created using two JavaScript files: The Google Maps API library and our own maps.js. We do this in the head section of index.php, after the title tag. To include the API we will link directly to the file on Google’s server:

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

And to include maps.js we will do something similar, but you’ll notice that we use WordPress’ get_stylesheet_directory_uri() function to dynamically write the path to the file in our theme directory:

<script type="text/javascript" src="<?php echo get_stylesheet_directory_uri() ?>/map.js"></script>

Create the Map

WordPress uses something called The Loop to display posts. Let’s get that started by testing if, given the parameters passed via the URL, WordPress has any posts to display. In index.php, after the opening body tag, write:

<?php if ( have_posts() ) : ?>
	<!-- WordPress has found matching posts -->
<?php else : ?>
	<!-- No matching posts, show an error -->
	<h1>Error 404 &mdash; Page not found.</h1>
<?php endif; ?>

Note: If you are interested in using this with a custom post type, depending on the context your loop may look different. Perry at Point At The Moon has posted a follow-up tutorial that includes custom post types.

Next create a DIV element that will be the container for our map; we’ll tell the API its name and Google will fill it with magic! We’ll use CSS to make it fill the window. After the line with the “WordPress has found matching posts” comment, write:

<div id="map" style="width: 100%; height: 100%;"></div>

Now open up maps.js. We’re going to create a JavaScript function that loads in a map with a few settings, which I won’t go into the details of here, but are well documented. Write:

function initialize() {
	map = new google.maps.Map(document.getElementById('map'), { 
		zoom: 12, 
		center: new google.maps.LatLng(38.898748, -77.037684), 
		mapTypeId: google.maps.MapTypeId.ROADMAP 
	});
}

Note: I’ve created a follow-up post on centering the map on the latest post.

But, like all functions, it’s not enough to define it — it must also be called. We’ll do this by modifying the body tag of index.php, like so:

<body onload="initialize()">

Reload your page and you should now see a map filling the window, centred on Washington DC.

Place the Markers

The way we’re going to get our markers onto the map is to:

  1. create a list (an array) of locations;
  2. specify the images we’re going to place at those locations;
  3. then go through (iterate) through the list, placing each one.

First up, the locations. We’re going to finish up that WordPress loop we started, where for each post we output an item in an array called “locations.” At this point we’re going to output only our “latlng” custom field, not the post title or content or any of the others. In index.php, right above the div with the ID of “map”, write this:

<script type="text/javascript">
	var locations = [
		<?php  $i = 1; while ( have_posts() ) : the_post(); ?>
			<?php if ( get_post_meta($post->ID, 'latlng', true) !== '' ) : ?>
				{
					latlng : new google.maps.LatLng<?php echo get_post_meta($post->ID, 'latlng', true); ?>, 
				},
			<?php endif; ?>
		<?php $i++; endwhile; ?>
	];
</script>

Note: If you are using the Address Geocoder plugin, here are the first two modifications you need to make. Change the two instances of get_post_meta($post->ID, ‘latlng’, true) to get_geocode_latlng($post->ID).

Next we’ll define the images we want to use as markers. Each marker has two parts — the marker itself and its shadow, and for each you will supply paths to images of your choosing; I’ve included a couple in the ZIP, linked above, or you can use your own. Upload the images into your theme folder. At the very top of maps.js, above the initialize function, write:

var pinkmarker = new google.maps.MarkerImage('/wp-content/themes/mapdemo/pink_Marker.png', new google.maps.Size(20, 34) );
var shadow = new google.maps.MarkerImage('/wp-content/themes/mapdemo/shadow.png', new google.maps.Size(37, 34) );

If you are using your own images you will want to check Google’s documentation of the MarkerImage options to see what each part of these lines mean, so that you can customize them.

Note: I’ve created a follow-up post on using the post’s Featured Image as the marker.

And the final step is to loop through the locations, placing a marker for each onto the map. In maps.js, within the “initialize” function, after the “var map…” block, write:

for (var i = 0; i < locations.length; i++) {  
	var marker = new google.maps.Marker({
    	position: locations[i].latlng,
		icon: pinkmarker,
		shadow: shadow,
		map: map
	});
}

Reload your page and you should now see your marker(s) placed on the map.

Enable the Infowindow

Our last task is to create a Google Maps “infowindow” which will pop-up when a marker is clicked, showing the title and content of the post. We will do this by:

  1. outputting the content into a hidden div;
  2. referencing that div in our array of locations;
  3. and adding what’s known as a “click listener” to our JavaScript function to open the window.

First up, the content. This will be a a second “while” loop, immediately before the one we wrote previously. It will output a series of DIVs, each with a unique ID and containing content to be shown in the infowindow. In index.php, directly following the “matching posts” comment line, write:

<div style="display: none;">
	<?php $i = 1; ?>
	<?php while ( have_posts() ) : the_post(); ?>
		<?php if ( get_post_meta($post->ID, 'latlng', true) !== '' ) : ?>
			<div id="item<?php echo $i; ?>">
				<p><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></p>
				<?php the_content(); ?>
			</div>
		<?php endif; ?>
		<?php $i++;	?>
	<?php endwhile; ?>
</div>

Note: If you are using the Address Geocoder plugin, here’s the third modification you need to make. Change get_post_meta($post->ID, ‘latlng’, true) to get_geocode_latlng($post->ID).

Next add a reference to these DIVs to our locations object. Directly after this line:

latlng : new google.maps.LatLng<?php echo get_post_meta($post->ID, 'latlng', true); ?>,

Add this:

info : document.getElementById('item<?php echo $i; ?>')

Now define the infowindow (documentation at Google). Switch over to maps.js and, at the very top, write this:

var infowindow = new google.maps.InfoWindow();

Finally, add a click listener for each marker. Within the “for…” block of the “initialize” function, after the “var marker…” block, write:

google.maps.event.addListener(marker, 'click', (function(marker, i) {
  return function() {
    infowindow.setContent(locations[i].info);
    infowindow.open(map, marker);
  }
})(marker, i));

And that’s it! Reload the page and you will now be able to click the marker(s), opening up an infowindow containing the post content. Mission accomplished!

Posted Feb 15, 2012

Using the WordPress Featured Image / Post Thumbnail as a Google Maps Marker

This is a follow-up to my previous post on combining WordPress and Google Maps. Jayc asks:

“Is there any way to change the MarkerImage to the featured image for the post?”

Yes, there is!

But one thing to keep in mind is that markers are not one image but two — the marker itself and the shadow. What we’re going to cover here will show how to use the Featured Image as the marker, while continuing to use a single shadow across all posts. This means it is appropriate for markers that share the same size and silhouette.

We’ll be using the code from the previous post as the starting point. The complete example with these changes applied is also available for download from GitHub.

Initiate Featured Images

Create a new file in your theme directory, called functions.php. In it, write:

<?php
 
// Featured Images / Post Thumbnails
if ( function_exists( 'add_theme_support' ) ) {
    add_theme_support( 'post-thumbnails' );
}
 
?>

This is the basic code. For more features see Post Thumbnails in the Codex.

When you go to your Post Edit screen you will now see a metabox called Featured Image. Clicking the link, you will be prompted to upload a file. Once you have done that, click the “Use as featured image” link (it’s next to “Insert into post”).

Make a marker declaration in the loop

We’re going to move our marker declaration into the WordPress loop. We are going to add another parameter to our locations variable, so in index.php look for the line:

info : document.getElementById('item<?php echo $i; ?>')

And place a comma at the end of it.

Because Featured Images are not required by WordPress when publishing a post, we will test if one has been set and to provide a fallback. On the next line include the following test:

<?php if ( has_post_thumbnail() ) { ?>
	// There is a Featured Image
<?php } else { ?>
	// No Featured Image, use fallback
<?php } ?>

And under “use fallback”, define a fallback image

// No Featured Image, use fallback
marker : new google.maps.MarkerImage('<?php echo get_stylesheet_directory_uri() ?>/pink_Marker.png', new google.maps.Size(20, 34) )

Get the path to the Featured Image

The normal way to access the post thumbnail is to use the the_post_thumbnail() function, but this returns a full img tag. We want only the image path, so let’s make our own function that does this. Open functions.php and add:

function get_thumbnail_path($post_ID) {
	$post_image_id = get_post_thumbnail_id($post_ID->ID);
	if ($post_image_id) {
		$thumbnail = wp_get_attachment_image_src( $post_image_id, 'post-thumbnail', false);
		if ($thumbnail) (string)$thumbnail = $thumbnail[0];
		return $thumbnail;
	}	
}

And back in index.php, under the “There is a Featured Image” comment, create a marker declaration that includes a call to the function:

// There is a Featured Image
marker : new google.maps.MarkerImage('<?php echo get_thumbnail_path($post->ID); ?>', new google.maps.Size(20, 34) )

Update the map JavaScript

Last but not least, we’ll modify the maps.js to reflect our changes. Find the line that declares the “pinkmarker”:

var pinkmarker = new google.maps.MarkerImage('/wp-content/themes/mapdemo/pink_Marker.png', new google.maps.Size(20, 34) );

and remove it; it’s no longer needed. Next, look for the line:

icon: pinkmarker,

And change it to:

icon: locations[i].marker,

And that’s it! Save everything and you should now see the featured image loaded as the marker.

Posted Mar 19, 2012

Center a Google Map on the Latest WordPress Post

This is a follow-up to my previous post on integrating WordPress and Google Maps. Damien asks:

“Would it be possible to centre the map on my latest post?”

Yes, it is!

This builds off the example in the previous post and I’m using the Address Geocoder plugin to set latlngs. You can see and download the entire working example at GitHub.

Output the JavaScript with PHP

The line that defines the center of the map is presently specified in a static way — we arbitrarily chose a location and included its latlng in the JavaScript function initialize(). Now we will revise this so that it is pulling a dynamic variable from WordPress. There are a number of ways to accomplish this, but here we’re going to go with the most straightforward, which is to move the entire contents of the static maps.js into the dynamic index.php.

Open maps.js and copy the contents to your clipboard. Then open index.php and look for this line:

<script type="text/javascript" src="<?php echo get_stylesheet_directory_uri() ?>/map.js"></script>

Remove the src parameter and paste the JavaScript between the opening and closing tags so that you have this:

<script type="text/javascript">
 
var infowindow = new google.maps.InfoWindow();
var pinkmarker = new google.maps.MarkerImage('/wp-content/themes/mapdemo/pink_Marker.png', new google.maps.Size(20, 34) );
var shadow = new google.maps.MarkerImage('/wp-content/themes/mapdemo/shadow.png', new google.maps.Size(37, 34) );
 
function initialize() {
	map = new google.maps.Map(document.getElementById('map'), { 
		zoom: 12, 
		center: new google.maps.LatLng(38.898748, -77.037684), 
		mapTypeId: google.maps.MapTypeId.ROADMAP 
	});
 
	for (var i = 0; i < locations.length; i++) {  
		var marker = new google.maps.Marker({
	    	position: locations[i].latlng,
			icon: pinkmarker,
			shadow: shadow,
			map: map
		});
		google.maps.event.addListener(marker, 'click', (function(marker, i) {
		  return function() {
		    infowindow.setContent(locations[i].info);
		    infowindow.open(map, marker);
		  }
		})(marker, i));
	}
 
}
 
</script>

Next we are going to add in a simple WordPress loop to get the single latest post and assign its latlng to a PHP variable.

Right inside the initialize() function, before the definition of map, add:

<?php
 
$the_query = new WP_Query('showposts=1');
 
while ( $the_query->have_posts() ) : $the_query->the_post();
	$coordinates = get_geocode_latlng($post->ID);
endwhile;
 
?>

And lastly, output that variable to the line that defines the map’s center, in place the static latlng. Look for this line:

center: new google.maps.LatLng(38.898748, -77.037684),

And change it to this:

center: new google.maps.LatLng<?php echo $coordinates ?>,

Done! On load, your map will now center on the marker of the latest post.

Posted Jul 01, 2012

Origins: A User-Generated Museum Exhibit created using iPads, Google Maps and WordPress

Visitors using Origins
Visitors interacting with Origins online at the Chinese American Museum. Photo © Francis Specker

Origins: The Birth and Rise of Chinese American Communities in Los Angeles is a permanent, cutting edge exhibition celebrating the growth and development, since the 1890s, of Chinese American enclaves from Downtown Los Angeles to the San Gabriel Valley.

Origins was designed for the Chinese American Museum, Los Angeles by THINK Jacobson & Roth. I was fortunate to be brought on as a member of the THINK team, providing start-to-finish design and development on the exhibit’s media component, Origins online. An interactive website available in the exhibit’s Monterey Park area, Origins online allows visitors to find and share great places, stories and resources of the San Gabriel Valley and other LA neighborhoods. The in-gallery presentation includes a table with two iPads for accessing the website, and a third for recording video of visitors’ own stories.

Agile Design Process, No Wireframes.

Visitors using Origins
Visitors interacting with Origins online at the Chinese American Museum. Photo © Francis Specker

One of the earliest decisions we made on the project was to forego traditional wireframes.

The project team, consisting of CAM’s Steve Wong and Michael Truong, and THINK’s Lori Jacobson and Carla Roth, had a strong broad-strokes image of what they wanted. At our first meeting they described a map of the San Gabriel Valley, with markers indicating points of interest submitted by the public. And so, to our second meeting, I brought a working prototype demonstrating that functionality.

We continued in that vein for much of the project. At each meeting we reviewed the current state of the application and came to a consensus on development priorities for the next week. Feeling our way forward, we’d field lots of ideas and implement those with the highest priority. This process reminds me of the saying, “crossing the river by stepping on stones.”

Rather than discussing abstractions and hypotheticals, this approach allowed us to base our decisions on real experiences with a working interface. And rather than spending our limited time producing paper documents such as wireframes and mockups, all our effort went toward expressing ideas in code that could be poked, prodded, tested and, ultimately, launched.

“When Marty proposed working without wireframes, I immediately responded positively to this idea. With a tight budget and a hands-on client, working with iterative prototypes was the way to go. Rather than lingering in pre-production, this allowed us to quickly make team decisions and try things out in a very real way.”

— Carla Roth

At about the two-thirds mark we took a step back and presented our work, and the decisions it embodied, to Executive Director Michael Duchemin, who gave it his blessing. As the physical exhibition took shape we took a break, and by the time we reconvened the launch date had begun to loom large. So we locked development on new functionality and turned our focus to fit and finish.

I proposed that, since the site would be presented in-gallery using iPads, we adopt the iOS design vernacular. Applying gradients, highlights, textures and pop-overs would involve adding substantial complexity to the code, however, and increase the cost-of-change. For this reason we decided it was finally appropriate to create a mock-up and secure a more traditional client sign-off. From there we turned to browser testing and bug barbecue, then finished with the physical installation and documentation.

Origins Online
iPad screenshot of the completed interface.

Google Maps + WordPress + iPads

Curator Steve Wong described his own extensive use of Google Maps in charting the region’s activity and history, and imagined enabling something similar for the community. I had previous experience with the Google Maps Javascript API so was able to hit the ground running. We chose WordPress for the CMS because of its ease-of-use for both development and content management. Additionally, I knew that the Gravity Forms plugin would allow us to fulfill the user-submission aspect of the project. And we chose Media Temple to host based on the suitability of their offering and my own familiarity with their setup. (Their location in Los Angeles was a bonus!)

The implementation’s core technical concept is described in my WordPress + Google Maps tutorial. Also, this project was the original motivator for the Address Geocoder WordPress plugin, which is now available from the WordPress plugin directory. The other aspect of the technical challenge was creating a JavaScript-heavy site that would look its best on 3rd generation iPads running the Kiosk Pro browser, while also performing on the desktop.

I’m happy to answer questions regarding the technical aspects of this project, including the Google Maps API, WordPress or, more generally, our agile approach and deploying web-based museum exhibits using iPads. Questions about the overall exhibit should be directed to THINK Jacobson & Roth.

The Chinese American Museum is a great space with a ton of history and a staff truly committed to serving their community and preserving its heritage. If you’re in LA, go see the exhibit!

Posted Feb 02, 2013