Create a Dynamic Flickr Image Search with the Dojo Toolkit

By  on  

Dojo Flickr

The Dojo Toolkit is a treasure chest of great JavaScript classes.  You can find basic JavaScript functionality classes for AJAX, node manipulation, animations, and the like within Dojo.  You can find elegant, functional UI widgets like DropDown Menus, tabbed interfaces, and form element replacements within Dijit.  In DojoX you can find charting libraries, special data stores, vector graphic helpers, and much more.

This post aims to mesh the three collections together.  We'll be creating a tabbed interface for grabbing Flickr images using Dijit's TabContainer, DojoX's Flickr data store, and basic Dojo code for event handling and node manipulation.

Select a Theme

There are two main steps in adding a theme to the page:  importing the theme stylesheet and adding the theme as a class name to the BODY element.

<style type="text/css">
	/* bring in the claro theme */
	@import "http://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit/themes/claro/claro.css";
	
	/* define styles per the images */
	a.thumb	{ display:inline-block; margin:0 20px 20px 0; }
	
</style>
<body class="claro">

The claro theme is new in Dojo 1.5 and happens to be my favorite.

Import Dojo From CDN, parseOnLoad:true

Pulling from Google's CDN makes Dojo load lightning-fast.  Adding a djConfig attribute with parseOnLoad:true instructs Dojo to scour the page looking for widgets.

<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js" type="text/javascript" djConfig="parseOnLoad:true"></script>

Alternatively you could configure Dojo by using the djConfig JavaScript variable:

djConfig = {
	parseOnLoad: true
};

Either way will suffice.

Create the HTML Structure: Tab Container and Search Form

Setting up the form comes first.  The form is going to be very simple, containing only a search box and a submit button.  Each node (FORM, INPUT, and BUTTON) is converted to its Dijit widget equivalent to provide extra functionality and themed display.

<!-- search will be here -->
<form dojoType="dijit.form.Form" id="searchForm">
	<input dojoType="dijit.form.ValidationTextBox" id="searchBox" missingMessage="Please provide a term to search" placeholder="search term..." required="true" />
	<button type="submit" dojoType="dijit.form.Button" id="searchButton">Submit Search</button>
</form>

The search box becomes a dijit.form.ValidationTextBox which allows me to require a value and display an error message if no term is provided.  I've also used the placeholder attribute to display a message in the search box when there is no value.  Dojo's internal awesomeness adds JavaScript support for the same functionality if the user's browser doesn't support placeholder yet.

The second piece is placing the TabContainer and its initial content pane in the page.  The initial ContentPane content will simply be a "welcome" tab so that there's always one tab within the container:

<!-- will set the eventual dimensions for the tab container -->
<div style="width:675px;height:400px">
	<!-- will host all tabs and their content panes -->
	<div dojoType="dijit.layout.TabContainer" id="tabContainer" style="width:100%;height:100%;">
		<!-- welcome pane: title is tab name, make this tab selected -->
		<div dojoType="dijit.layout.ContentPane" title="Welcome Pane" selected="true">
			<p>
				Welcome to the Flickr Search data store and Tab Container example.  
				Submit your search and watch the tab load!
			</p>
		</div>
	</div>
</div>

Future tabs will be closable.

Require Dojo/Dijit/DojoX Dependencies

Before we can create our widgets and use Dojo classes, we need to load them:

/* require necessary classes */
dojo.require('dijit.layout.TabContainer');
dojo.require('dijit.layout.ContentPane');
dojo.require('dijit.form.Button');
dojo.require('dijit.form.Form');
dojo.require('dijit.form.ValidationTextBox');
dojo.require('dojox.data.FlickrStore');
dojo.require('dijit.Tooltip');

Dojo's internal functionality will also direct dependencies of dependencies to be loaded.  If you aren't familiar with Dojo class loading, be sure to read The Beauty of dojo.require().

Dojo, Dijit, DojoX:  Make It Happen

Now that the theme is in place, HTML structure is there, and we've required the necessary classes, we can code our application-specific JavaScript.  There's a decent amount of JavaScript need to make this work so let's break it down.

Start by creating an object which will hold all of our open tabs and an instance of dojox.data.FlickrStore which will be used to query Flickr:

/* settings */
var tabSubjects = {};
var flickrStore = new dojox.data.FlickrStore();

Then collect the form elements as well as the TabContainer:

/* collect proper elements */
var searchForm = dijit.byId('searchForm');
var searchBox = dijit.byId('searchBox');
var searchButton = dijit.byId('searchButton');
var tabContainer = dijit.byId('tabContainer');

Add a "submit" event listener to the TabContainer...

/* connect click event to search */
dojo.connect(searchForm,'onSubmit',function(e) {

...which stops the normal form submission:

//stop!
dojo.stopEvent(e);

...grabs the value of the search box:

//store value - set to lower case to save to caching object
var value = searchBox.get('value').toLowerCase();

If a value is present, make sure there isn't currently a search tab open for that term.  If there is, focus on it.  If this is a new search term, direct the dojox.data.FlickrStore instance to search and return images for the given term.  Upon search, a new tab for this term is created with a default title and content string, added to the TabContainer, and this new tab is selected:

//if a value exists...
if(value) {
	//if the tab isn't already there...
	if(!tabSubjects[value]) {
		//do the search...
		flickrStore.fetch({
			query: { tags: value },
			onBegin: function() {
				//create the tab
				tabSubjects[value] = new dijit.layout.ContentPane({ 
					title:value, 
					content:'Searching for ' + value + '...', 
					closable:true,
					onClose: function() {
						//remove this from our saved tabs when closed
						tabSubjects[value] = null;
						return true;
					}
				});
				//add to tabcontainer and select
				tabContainer.addChild(tabSubjects[value]);
				tabContainer.selectChild(tabSubjects[value]);
			},

When the search is complete, we clear the contents of the tab's pane and loop through each returned image, injecting it into the content pane.  Lastly, we create a tooltip for the image which displays the image name when the user focuses on the image:

	onComplete: function(items) {
		//if we got items...
		if(items.length) {
			//clear the tab's content'
			tabSubjects[value].set('content','');
			//cycle through each image returned, inject into new tab, add tooltip
			dojo.forEach(items,function(item,i) {
				//create the link's ID for the tooltip
				var id = new Date().getTime() + '_' + i;
				var a = dojo.create('a',{ 
					href: flickrStore.getValue(item,'link'),
					className: 'thumb',
					target: '_blank',
					id:  id,
					innerHTML: '<img src="' + flickrStore.getValue(item,'imageUrlSmall') + '" alt="' + flickrStore.getValue(item,'title') +'" />'
				},tabSubjects[value].domNode);
				//tooltip!
				new dijit.Tooltip({ label: flickrStore.getValue(item,'title'), connectId: id });
			});
		}
		else {
			//provide "no images" content
			tabSubjects[value].set('content','There were no images available for this term.');
		}
		//empty the search box
		searchBox.set('value','');
		
	}
});

Here's the complete JavaScript block for this app:

/* require necessary classes */
dojo.require('dijit.layout.TabContainer');
dojo.require('dijit.layout.ContentPane');
dojo.require('dijit.form.Button');
dojo.require('dijit.form.Form');
dojo.require('dijit.form.ValidationTextBox');
dojo.require('dojox.data.FlickrStore');
dojo.require('dijit.Tooltip');

/* when all classes have loaded... */
dojo.ready(function() {
	
	/* settings */
	var tabSubjects = {};
	var flickrStore = new dojox.data.FlickrStore();
	
	/* collect proper elements */
	var searchForm = dijit.byId('searchForm');
	var searchBox = dijit.byId('searchBox');
	var searchButton = dijit.byId('searchButton');
	var tabContainer = dijit.byId('tabContainer');
	
	/* connect click event to search */
	dojo.connect(searchForm,'onSubmit',function(e) {
		//stop!
		dojo.stopEvent(e);
		//store value
		var value = searchBox.get('value').toLowerCase();
		//if a value exists...
		if(value) {
			//if the tab isn't already there...
			if(!tabSubjects[value]) {
				//do the search...
				flickrStore.fetch({
					query: { tags: value },
					onBegin: function() {
						//create the tab
						tabSubjects[value] = new dijit.layout.ContentPane({ 
							title:value, 
							content:'Searching for ' + value + '...', 
							closable:true,
							onClose: function() {
								//remove this from our saved tabs when closed
								tabSubjects[value] = null;
								return true;
							}
						});
						//add to tabcontainer and select
						tabContainer.addChild(tabSubjects[value]);
						tabContainer.selectChild(tabSubjects[value]);
					},
					onComplete: function(items) {
						//if we got items...
						if(items.length) {
							//clear the tab's content'
							tabSubjects[value].set('content','');
							//cycle through each image returned, inject into new tab, add tooltip
							dojo.forEach(items,function(item,i) {
								//create the link's ID for the tooltip
								var id = new Date().getTime() + '_' + i;
								var a = dojo.create('a',{ 
									href: flickrStore.getValue(item,'link'),
									className: 'thumb',
									target: '_blank',
									id:  id,
									innerHTML: '<img src="' + flickrStore.getValue(item,'imageUrlSmall') + '" alt="' + flickrStore.getValue(item,'title') +'" />'
								},tabSubjects[value].domNode);
								//tooltip!
								if(flickrStore.getValue(item,'title')) { new dijit.Tooltip({ label: flickrStore.getValue(item,'title'), connectId: id }); }
							});
						}
						else {
							//provide "no images" content
							tabSubjects[value].set('content','There were no images available for this term.');
						}
						//empty the search box
						searchBox.set('value','');
						
					}
				});
			}
			//if it does exist, focus on it
			else {
				tabContainer.selectChild(tabSubjects[value]);
			}
		}
	});
});

How long would you say this took to write?  Half hour?  Hour? Three hours?  Nope.  This mini-application only took me about 15 minutes to write!  DojoX also features a class for pulling images from Picasa.

Nice, huh?  Meshing Dojo, Dijit, and DojoX classes is a breeze thanks to Dojo's tightly knit class system.  I challenge you to create a simple application and see what you can do in an hour.  Use Theme Tester as a helper should you need inspiration!  And always share what you've created when you're done!

Recent Features

  • By
    How I Stopped WordPress Comment Spam

    I love almost every part of being a tech blogger:  learning, preaching, bantering, researching.  The one part about blogging that I absolutely loathe:  dealing with SPAM comments.  For the past two years, my blog has registered 8,000+ SPAM comments per day.  PER DAY.  Bloating my database...

  • By
    Interview with a Pornhub Web Developer

    Regardless of your stance on pornography, it would be impossible to deny the massive impact the adult website industry has had on pushing the web forward. From pushing the browser's video limits to pushing ads through WebSocket so ad blockers don't detect them, you have...

Incredible Demos

Discussion

  1. Woow, seems Dojo got some amazing features and i also wanted to use it, keep in dong more stuff

  2. Ben Krembs

    So when are you going to do some object-oriented Dojo examples?

    Anonymous, nested callbacks and inline script only gets one so far.

  3. Excellent website , Great blog keep posting more.

  4. Alex

    I dosen’t work for me can someone email be the codes at a1exru1es1@gmail.com. Please!!!

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!