Create a Dynamic Flickr Image Search with the Dojo Toolkit
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!
Woow, seems Dojo got some amazing features and i also wanted to use it, keep in dong more stuff
So when are you going to do some object-oriented Dojo examples?
Anonymous, nested callbacks and inline script only gets one so far.
Excellent website , Great blog keep posting more.
I dosen’t work for me can someone email be the codes at a1exru1es1@gmail.com. Please!!!