Creating and using a tag cloud
A tag cloud is a list of tags styled to reflect the frequency of their use. Popular tags are highlighted in some way, perhaps by being displayed in a larger font size than less frequently used tags. Here you learn how to collect the tags, count them, and then use the tag cloud as a way to search the photo gallery.
This topic is part of a series designed to show how to create an image gallery tag cloud using IndexedDB. Previous articles described the database needs, showed how to create the database and its objects, and how to save tags and other details into the database.
In this article:
- Retrieving and summarizing the tags
- Creating the tag cloud
- Using the tag cloud to search for images
- Loading the selected image
- Summary
Retrieving and summarizing the tags
At this point, you know that transactions are the key to getting to data stored in IndexedDB object stores. You also know that indexes allow you to sort the data. The next example shows how to use the IdxTagByWord index to retrieve the tags and count their uses:
oTagWords = { };
var hTransaction = hDB.transaction( "ImageTags", "readonly" );
var hObjectStore = hTransaction.objectStore( "ImageTags" );
var hIdxWords = hObjectStore.index( "IxTagsByWord" );
var hRequest = hIdxWords.openCursor();
hRequest.onsuccess = function( evt ) {
var oCursor = evt.target.result;
if ( oCursor ) {
var sTagWord = oCursor.value.TagWord;
if ( sTagWord in oTagWords ) {
oTagWords[ sTagWord ]++;
} else {
oTagWords[ sTagWord ] = 1;
}
oCursor.continue();
} else {
writeTagCloud( oTagWords );
}
}
In this example, the IxTagsByName index opens a cursor on the ImageTags object store. Because the index is organized by the TagWord field, the cursor sorts the tags alphabetically. This means that all uses of a given tag are grouped together.
The tag associated with each record is examined; if the tag has not been seen before, it's added as an attribute of the oTagWords object. If the tag has been seen previously, the value of the corresponding attribute is incremented by one.
When each record has been examined, the cursor is set to null and the oTagWords object contains the results. Each attribute corresponds to a tag in the database and the value of that attribute is the total number of times the tag is used. After the cursor goes out of scope, the results are passed to a function that creates the tag cloud.
Creating the tag cloud
To create the tag cloud, a span element is created for each tag; the span element is then appended as a child node of a div element designed to display the tag cloud, as shown here:
function writeTagCloud( oTagWords );
{
try
var oSpace = document.createTextNode( " " );
var oCloud = getElementObject( "divTagCloud" );
// Clear any previous contents.
while ( oCloud.hasChildNodes() ) {
oCloud.removeChild( oCloud.childNodes[ 0 ] );
}
// Create and add span elements for each tag
for (var sTagAttr in oTagWords )
{
var oTagElement = document.createElement( "span" );
oTagElement.textContent = sTagAttr;
oTagElement.className = getTagStyle(oTagWords[ sTagAttr ]);
oTagElement.addEventListener( "click", showTagImages, false );
oCloud.appendChild( oTagElement );
// Add white space between each span.
oCloud.appendChild( oSpace.cloneNode() );
}
} catch (ex) {
handleError( ex.message );
}
}
function getTagStyle( iTagCount ) {
var sClassName;
if ( iTagCount > 40 ) { sClassName = "tagSize5"; } else
if ( iTagCount > 30 ) { sClassName = "tagSize4"; } else
if ( iTagCount > 20 ) { sClassName = "tagSize3"; } else
if ( iTagCount > 10 ) { sClassName = "tagSize2"; } else
sClassName = "tagSize1";
return sClassName
}
Each span element is assigned a style based on the number of times the tag appears in the database. In addition, an event handler is assigned to the click event of each span element. A space is added between each span element to ensure that the tags will wrap naturally inside the divTagCloud container.
The assignment of the style depends on the application and the expected number of tags. In this example, five styles are available for highlighting heavily used tags. New styles are used for every ten uses of a given tag, up to a maximum of 40.
The styles can be quite simple, as shown in the following example:
#divTagCloud {
border : 1px solid #000000;
}
#divTagCloud span { padding : 2px; }
.tagSize1 { font-size : x-small; }
.tagSize2 { font-size : small; }
.tagSize3 { font-size : medium; }
.tagSize4 { font-size : large; }
.tagSize5 { font-size : x-large; }
Obviously, the styles can be more complex, too.
Using the tag cloud to search for images
When the tag cloud was generated, an event handler was added to the span element representing each tag. This event handler uses the selected tag to create a list of links to images associated with that tag.
To determine the tag itself, the textContent property of the span element is extracted using the event object passed to the event handler.
This value is then combined with the IdxTagsByWord index to determine the matching images. After the matching images have been located, an anchor (link) object is created using the details from the database.
This example shows the entire process:
The following example shows the entire process: -
function showTagImages( evt ) {
try {
if ( hDB == null ) {
updateResults( "Can't show results; the database is not open." );
} else {
// Clear previous results and create new list for results
var oResults = getElementObject( "divTagResults" );
while ( oResults.hasChildNodes() ) {
oOResults.removeChild( oResults.childNodes[ 0 ] );
}
var oList = document.createElement( "ul" );
oResults.appendChild( oList );
// Grab the selected tag from the object that generated the event.
oTagObject = evt.target;
var sTagWord = oTagObject.textContent;
// Find the IDs of the images using the selected tag.
var hTransaction = hDB.transaction( [ "ImageDetails", "ImageTags" ], "read" );
var hObjectStore = hTransaction.objectStore( "ImageTags" );
var hIndex = hObjectStore.index( "IxTagsByWord" );
var hRequest = hIndex.openCursor( sTagWord );
hResult._parentList = oList;
hRequest.onerror = handleRequestEvent;
hRequest.onsuccess = function( evt ) {
var oCursor = evt.target.result;
if ( oCursor ) {
// For each matching ID, locate the corresponding image record.
hImageStore = hTransaction.objectStore( "ImageDetails", "readonly" );
hDetails = hImageStore.index( "IxImagesByID" );
hReqImage.openCursor( oCursor.value.ImageID );
hReqImage._parentList = this._parentList;
hReqImage.onerror = handleRequestEvent;
hReqImage.onsuccess = function( evt ) {
// Now that we have the image details, create an anchor (link)
// element and a list item, and then add them to the list.
var oCursor = evt.target.result;
if ( oCursor ) {
var oRecord = oCursor.value;
var oListElement = document.createElement( "li" );
var oLinkElement = document.createElement( "a" );
// Base the target URL on the current page
var oLocation = window.location;
var sTarget = oLocation.protocol + "//" +
oLocation.host + oLocation.pathname;
sTarget += "?newimage=" + oRecord.FileName;
oLinkElement.textContent = oRecord.ImageTitle;
oLinkElement.href = sTarget;
oListElement.appendChild( oLinkElement );
this._parentList.appendChild( oListElement );
}
}
}
}
} catch (ex) {
handleError( "List Results Exception: " + ex.message);
}
}
This example performs a number of tasks:
- The result display area (divTagResults) is cleared of any child elements that might exist from a previous use. A new list object is created and added to the display area.
- The selected tag is used to open a cursor on the ImageTags object store; this index limits the set of records where the TagWord attribute matches the selected tag. A handle to the new list is passed as a custom request property.
- When the cursor is opened, the ImageID associated with each matching result opens the corresponding record in the ImageDetails object store. A handle to the new list is passed as a custom request property.
- When the image details for each matching image are returned, they are used to create an li element and an anchor (or link) element; these objects are added to the new list using the custom request property.
- The target of the anchor element, (specified in the href property) is the webpage running the script; however, the filename of the target image is added as a query variable.
When all requests have completed, the display area contains an unordered list of anchor elements that display links to the images associated with the selected tag.
Loading the selected image
If the user clicks a link in the newly assembled list of matches, the page is reloaded with a query variable that contains the filename of the selected image.
An earlier example described the doPageSetup
function, which is called when the DOMContentLoaded event is triggered. A few simple changes allow this function to load the requested image using the query variable, as shown in the following example:
function doPageSetup() {
var sFilename == "";
if ( document.location.search. != "" ) {
var sSearchVal = document.location.search.subString(1);
var aTokens = sSearchVal.split( "=" );
var iIndex = aTokens.indexOf( "newimage" );
if ( iIndex != -1 ) {
sFilename = aTokens[ iIndex ];
}
var oImage = document.getElementById( 'iGalleryImage' );
if ( sFilename == "" ) {
sFilename = reduceFilename( oImage.src );
} else {
// assume images are stored in a child directory named 'images'
oImage.src = 'images/' + sFilename;
}
getImageDetails( sFilename );
}
The revised version uses basic string parsing to create an array of tokens passed as the query string. The resulting array is searched for the query variable that specifies a new image (newimage). If this variable is not found, details are retrieved for the current image.
If the variable is found, the filename passed with the variable is assigned to the src property of the gallery image in the webpage and details for the new image are retrieved.
Summary
This article showed how to access and use data stored in IndexedDB object stores to create, display, and manage a tag cloud. It also demonstrated how a carefully designed database simplifies complex processes by making them easier to perform. Because the tags were separated from the individual details associated with an image, they were (relatively) easy to collect and summarize.
This article also showed how to persist data between requests, which is an important concern when working with asynchronous APIs, such as IndexedDB.
For more info about IndexedDB, see:
- Building Offline Experiences with HTML5 AppCache and IndexedDB
- IE Test Drive Demo: IndexedDB Cookbook
- IndexedDB API Reference
- Indexed Database (IndexedDB) specification