Wednesday, January 26, 2011

jQuery : Creating a tag cloud widget

Tag clouds are pretty common these days on most of the websites that we come across. Tag clouds are useful because not only are they better organised as compared to displaying plain lists, but they also provide visual cues to the user of the frequency of the a particular keyword relative to the other keywords in the cloud.

In this post, I am going to create a simple jQuery widget to create a tag cloud.

Lets look at the requirements first
For any dom element, we need to display a list of anchor elements.
For each anchor element, the relative font size should be directly proportional to the frequency of occurence of the element.

Here is how the requirements will translate into technical terms.

  1. Create an unordered list such that each list item represents a tag.
  2. The unordered list should be placed inside the selected container.
  3. The font-size of each list item is based upon the relative frequency of the tag. The tag with the minimum frequency should have a minimum font size of 1. The Tag of the maximum frequency should have a font size of 2em.

Lets look at the HTML markup first. In fact, since this is a cloud widget, we dont actualy need anu html except for a container that can hold he contents of the cloud.


Ok, that was simple. Thats because all the logic of building the list and its corresponding elements lies in widget.

Lets start with our standard way of creating a widget

(function($){
var tagcloudPrototype={
 options:{
 },
 _create:function(){
  console.log('create');
  var widget=this;
  var $domElement=widget.element;
  var options=widget.options;
 }
};

$.widget('ui.tagcloud',tagcloudPrototype);
})(jQuery);

As can be seen in the above code, all the functionality of the widget lies in the _create function itself which will be called once during widget initialization.

Let us create a few options for the user of our widget, such as providing the css styles for the links in the cloud. our widget basically requires as an argument, an array 'tag' objects that have a particular structure. We will define the structure of the tag in the options element so that it can be configured by the user as per his requirement.

As per our design, we will require that for each tag, three things must be given

  1. name : The name of the tag as it will appear as an anchor.
  2. freq : The number denoting the frequency of the tag.
  3. uri : The uri to which the user will be redirected to once he clicks on the tag anchor.

The values in the array can be obtained via an ajax query or any other way. Here is a sample array

var cloudtags = [
   { name: "Java", freq: "50" , url:"#"},
   { name: "CSS", freq: "53" , url:"#"},
   { name: "XHTML", freq: "27" , url:"#"},
   { name: "jQuery", freq: "71" , url:"#"},
   { name: "MySQL", freq: "35" , url:"#"}
   ];

And here is our options object.

options:{
  tagStructure:{name:'name',freq:'freq',url:'url'},
  linkClass:'tag-static',
  linkHoverClass:'tag-hover',
  tags:[]
 }

As you can see, each tag in the array follows the same structure. Ill show later how you can use the widget if the names in your tag sructure are different from the default. The default are 'name','freq' and 'url'.

Now, lets see what needs to be done in the create function. Sicne we need the size of the tags to be dependent upon the value of the frequency, we will simply scale down the maximum and minimum frequeny between 1 and 2. ie. in our sample cloudtag array, the min value is 27 and the max value is 71. So, we need to use 1em as the font size of the tag with name 'XHTML' and 2em as the font size of the tag with name 'jQuery'.

Once computed, for each element in teh array, we will store the value of the fontSize as a property of that particular tag object array and then we will use it later to set the value of the font-size css property of the anchors.

Here is the code in the _create() function that we will use to scale down the frequency values and store it on the tag object.

var widget=this;
  var $domElement=widget.element;
  var options=widget.options;
  
  var tags=options.tags;
  var name=options.tagStructure.name;
  var freq=options.tagStructure.freq;
  var url=options.tagStructure.url;
  
  var max=0,min;
  $.each(tags,function(index, tag){
   max = Math.max(max,tag[freq]);
  });
  min=max;
  $.each(tags,function(index, tag){
   min = Math.min(min,tag[freq]); 
  });
  
  
  var divisionUnit=(max-min)/10;
  
  $.each(tags, function(index, tag){
   var fontUnit=Math.round((tag[freq]-min)/divisionUnit);
   tag.fontSize=(fontUnit<10)?'1.'+fontUnit+'em':'2.0'+'em';
  });

Now, I am only left with a simple task of creating a new unordered list and adding list items with anchors to it. This list item should reside in the selected container, i.e. the current dom element of the widget. Here is how we do it.

$domElement.append("
    "); var $tagList = $('ul',$domElement); $.each(tags, function(index, tag){ $tagList.append('
  • '+ '' + tag[name]+''+ '
  • '); });

    As seen above, there is nothing special going on. All i do is iterate over each of the elements of the tags array, and crete list items and anchor tags with the calculated font-size for each of the tag. Now the last thing that I would do is to apply the user specified styles to the anchors. And thats very trivial.


    $('a',$domElement).hover(function(){   $(this).toggleClass(options.linkHoverClass);
      });
    
    $tagList.addClass('tag-cloud-ul');
      $('a',$domElement).hover(function(){
       console.log($(this));
       $(this).toggleClass(options.linkHoverClass);
       
      });
    

    Here is the entire widget as one big blob!

    (function($){
    var tagcloudPrototype={
     options:{
      tagStructure:{name:'name',freq:'freq',url:'url'},
      linkClass:'tag-static',
      linkHoverClass:'tag-hover',
      tags:[]
     },
     _create:function(){
      console.log('create');
      var widget=this;
      var $domElement=widget.element;
      var options=widget.options;
      
      var tags=options.tags;
      var name=options.tagStructure.name;
      var freq=options.tagStructure.freq;
      var url=options.tagStructure.url;
      
      //Scaling down the frequency to a range of 1 to 10 and determining the font size
      var max=0,min;
      $.each(tags,function(index, tag){
       max = Math.max(max,tag[freq]);
      });
      min=max;
      $.each(tags,function(index, tag){
       min = Math.min(min,tag[freq]); 
      });
      
      var divisionUnit=(max-min)/10;
      
      $.each(tags, function(index, tag){
       var fontUnit=Math.round((tag[freq]-min)/divisionUnit);
       tag.fontSize=(fontUnit<10)?'1.'+fontUnit+'em':'2.0'+'em';
      });
      
      //Creating the unordered list
      $domElement.append("
      "); var $tagList = $('ul',$domElement); //Adding list items and anchors for each tag in the array $.each(tags, function(index, tag){ $tagList.append('
    • '+ '' + tag[name]+''+ '
    • '); }); //Styling $tagList.addClass('tag-cloud-ul'); $('a',$domElement).hover(function(){ console.log($(this)); $(this).toggleClass(options.linkHoverClass); }); } }; $.widget('ui.tagcloud',tagcloudPrototype); })(jQuery);

      Here is the CSS that I am using.

      #tagContainer{
          width:200px;
          height:100px;
          overflow:hidden;
          background-color:#6c6c6c;
         }
         
         .tag-cloud-ul{
          list-style:none;
          margin:0px;
          margin-left:10px;
          margin-top:10px;
          padding:0px;
          width:100%;
          height:100%;
         }
         
         .tag-cloud-li{
          float:left;
          padding:5px;
          height:30px;
          margin:0px;
         }
         
         .tag-static{
          text-decoration:none;
          color:white;
         }
         
         .tag-hover{
          color:blue;
         }
      

      Okey. Thats our widget, now lets see how to use it. Ill show you two ways. the first one shown here uses the default tag structure.

      var cloudtags = [
         { name: "Java", freq: "50" , url:"#"},
         { name: "CSS", freq: "53" , url:"#"},
         { name: "XHTML", freq: "27" , url:"#"},
         { name: "jQuery", freq: "71" , url:"#"},
         { name: "MySQL", freq: "35" , url:"#"}
         ];
         
         $('#tagContainer').tagcloud({tags:cloudtags});
      

      The one shown below does uses a custom structure for the tags.

      var cloudtags = [
         { tagName: "Java", frequency: "50" , url:"#"},
         { tagName: "CSS", frequency: "53" , url:"#"},
         { tagName: "XHTML", frequency: "27" , url:"#"},
         { tagName: "jQuery", frequency: "71" , url:"#"},
         { tagName: "MySQL", frequency: "35" , url:"#"}
         ];
         
         $('#tagContainer').tagcloud(
         {tags:cloudtags},
         {tagStructure:{name:'tagName',freq:'frequency',url:'url'}}
         );
      

      We're done! As simple as that! Once again, cheers to the jQuery folks!
      And of course, suggestions for improvement are most welcome :)

      Happy Programming :)
      Signing Off
      Ryan

      4 comments:

      Anonymous said...

      Hey, I am checking this blog using the phone and this appears to be kind of odd. Thought you'd wish to know. This is a great write-up nevertheless, did not mess that up.

      - David

      Anonymous said...

      Awesome post. Do you mind if I ask what your source is for this information?

      Unknown said...

      @David, Thanks for the information. I am not sure why its not rendering on mobiles properly. Thanks for the tip though, ill see what i can do about it.

      @Anonymous friend.
      As I usually do, i might have read a few articles from some css tips website. I usually check out nettuts and csstricks. Bit i dont think i saw any widget sort of thing anywhere, so i figured, lets make it. Writing widgets kinda gives me the kicks, as i am just learning to write this stuff! Thanks for your appreciation :)

      Anonymous said...

      Merci d'avoir un blog interessant