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
    Page Visibility API

    One event that's always been lacking within the document is a signal for when the user is looking at a given tab, or another tab. When does the user switch off our site to look at something else? When do they come back?

  • By
    Regular Expressions for the Rest of Us

    Sooner or later you'll run across a regular expression. With their cryptic syntax, confusing documentation and massive learning curve, most developers settle for copying and pasting them from StackOverflow and hoping they work. But what if you could decode regular expressions and harness their power? In...

Incredible Demos

  • By
    Truly Responsive Images with responsive-images.js

    Responsive web design is something you hear a lot about these days. The moment I really started to get into responsive design was a few months ago when I started to realise that 'responsive' is not just about scaling your websites to the size of your...

  • By
    Retrieve Google Analytics Visits and PageViews with PHP

    Google Analytics is an outstanding website analytics tool that gives you way more information about your website than you probably need. Better to get more than you want than not enough, right? Anyways I check my website statistics more often than I should and...

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!