Creating a WordPress Tag Search using jQuery

November 8th, 2011

The WordPress taxonomy functions make it easy to give your for 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!

The technique applied on Seth Anderson’s photo site.

You can preview the code here.

“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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function showalltags() {
 
	$my_query = new WP_Query('posts_per_page=1');
 
	while ($my_query->have_posts()) : $my_query->the_post();
 
	$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;
 
	endwhile;
 
}

And output it somewhere in your theme like so:

1
2
3
4
<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:

1
2
3
4
5
6
7
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>');	
	$('.taxonomies').append('<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”:

1
2
3
4
5
6
7
8
9
10
$('.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:

1
2
3
4
5
$('.tagtoggle').bind('click', function(event) {
	event.preventDefault()
	$('.tagtoggle').removeClass('selected');
	$(this).addClass('selected');
});

Construct the URL

This function is the core of the mechanism:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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:

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

And that’s it! You can take a look at the full JavaScript in context here.

Updated 12/10/11 to capture the tag slug from the class, rather than inferring it from the tag name.

Interactive Touchscreen Interface using jQuery and HTML5 Video

October 27th, 2011

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

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

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 version of the jQuery vertical center function I previously posted, 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.

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.

1
<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.

1
2
$('.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:

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

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

Playing a video.

Playing a video.

The touch-screen rig.

The touch-screen rig.

jQuery Vertical Center

October 20th, 2011

This is to center elements vertically in the middle of the browser window.

It’s pretty straightforward, but note line 4. On one of the sites I used this on the centered element needed to appear in the middle of the field under a top navigation bar, so this looks for that element and factors that into the calculation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
jQuery.fn.center = function () {
 
	var $t = ( ( $(window).height() - this.outerHeight() ) / 2);
	var $h = $('.header').outerHeight();
 
      	if ( ( $(window).height() - $h ) >= this.outerHeight() ) {
		jQuery('.shade').fillWindow();
 
    		this.css({ "position":"fixed","top" : ($t + "px") });
 
		if ( $(window).width() >= this.outerWidth() ) {
    			this.css("left", (($(window).width() - this.outerWidth()) / 2) + $(window).scrollLeft() + "px");
		}
 
	} else {
		// Don't do
	}
 
	this.show();
 
   	return this;
 
}

And then use it like this:

1
$('.someElement').center();

Also, do remember to use this only once all the elements have loaded. It’ll come out wrong if you center before, for instance, all the images have come in. So make sure the element is at its full dimensions before you fire this.

Creating a jQuery Slideshow of the WordPress Attachments Gallery

April 7th, 2011

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:

1
<?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!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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:

1
<?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).

1
2
3
4
5
6
7
8
.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.

1
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:

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// 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.

jQuery Extract Headings for Tab Controls

June 20th, 2010

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:

1
2
3
4
5
6
7
8
9
10
<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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
$(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!