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
    Camera and Video Control with HTML5

    Client-side APIs on mobile and desktop devices are quickly providing the same APIs.  Of course our mobile devices got access to some of these APIs first, but those APIs are slowly making their way to the desktop.  One of those APIs is the getUserMedia API...

  • By
    5 HTML5 APIs You Didn&#8217;t Know Existed

    When you say or read "HTML5", you half expect exotic dancers and unicorns to walk into the room to the tune of "I'm Sexy and I Know It."  Can you blame us though?  We watched the fundamental APIs stagnate for so long that a basic feature...

Incredible Demos

  • By
    :valid, :invalid, and :required CSS Pseudo Classes

    Let's be honest, form validation with JavaScript can be a real bitch.  On a real basic level, however, it's not that bad.  HTML5 has jumped in to some extent, providing a few attributes to allow us to mark fields as required or only valid if matching...

  • By
    Table Cell and Position Absolute

    If you follow me on Twitter, you saw me rage about trying to make position: absolute work within a TD element or display: table-cell element.  Chrome?  Check.  Internet Explorer?  Check.  Firefox?  Ugh, FML.  I tinkered in the console...and cussed.  I did some researched...and I...

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!