Saturday, September 18, 2010

Building a tagcloud with jQuery and ASMX Webservices

Generating tagclouds is nothing new. People have been generating tagclouds server-side since the seventies. Lately more and more tagclouds are being generated client-side.

There is nothing wrong with generating tagclouds server-side. Telerik has a great tagcloud server control. Generating tagclouds server-side can bring some overhead though, so depending on the scenario and the requirements you might decide to do it client-side. There are a ton of fancy ready-to-use jQuery tagcloud plug-ins out there. None of them met my requirements perfectly, so I decided to do it myself.

In this post you can find my own implementation. It's simple, straightforward and a decent base to start experimenting yourself.

Making the tags available through an ASMX Webservice

In my implementation I use an ASMX Webservice to expose a TagCollection. The TagCollection is an object which contains a List<Tag>. The TagCollection also has a MaxWeight property which we will need on the client-side to calculate the relative weight.

   1:  public class TagCollection {
   2:      public TagCollection() { }
   3:   
   4:      public TagCollection(List<Tag> items, int maxWeight) {
   5:          this.Items = items;
   6:          this.MaxWeight = maxWeight;
   7:      }
   8:   
   9:      public List<Tag> Items { get; set; }
  10:      public int MaxWeight { get; set; }
  11:  }


A Tag is a simple object with two properties: Value and Weight.

   1:  public class Tag {
   2:      public Tag() { }
   3:   
   4:      public Tag(string value, int weight) {
   5:          this.Value = value;
   6:          this.Weight = weight;
   7:      }
   8:   
   9:      public string Value { get; set; }
  10:      public int Weight { get; set; }
  11:  }


In the ASMX WebService I created the GetTagCollection method which returns a TagCollection. Don't forget to uncomment the [System.Web.Script.Services.ScriptService] declaration and to add the [ScriptMethod(ResponseFormat = ResponseFormat.Json)] declaration to the GetTagCollection method.

   1:  [WebService(Namespace = "http://TagCloudWithJquery.com")]
   2:  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   3:  [System.Web.Script.Services.ScriptService]
   4:  public class TagService : System.Web.Services.WebService { 
   5:      [WebMethod]
   6:      [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
   7:      public TagCollection GetTagCollection() {
   8:          var items = new List<Tag>() {
   9:                  new Tag(".NET", 20),
  10:                  new Tag("CodeSnippets", 15),
  11:                  new Tag("...", 10),
  12:                  new Tag("ASP.NET", 18)                
  13:              };
  14:          var maximumWeight = items.Max(i => i.Weight);
  15:   
  16:          var tagCollection = new TagCollection(items, maximumWeight);        
  17:   
  18:          return tagCollection;
  19:      }
  20:  }


Consuming the ASMX Webservice

Consuming the ASMX Webservice with jQuery is relatively simple. If you are new to consuming ASMX Webservices with jQuery, I advice you to read this excellent article.

When the document is ready I make a call to the ASMX Webservice and when the call is successful all the work gets passed to the onTagCloudSuccess function.

   1:  $(document).ready(setupTagCloud);
   2:   
   3:  function setupTagCloud() {
   4:      $.ajax({
   5:          type: "POST",
   6:          contentType: "application/json; charset=utf-8",
   7:          url: "Services/TagService.asmx/GetTagCollection",
   8:          data: "{}",
   9:          dataType: "json",
  10:          success: onTagCloudSuccess
  11:      });
  12:  }


Don't forget to add a script reference to the latest version of jQuery.

Generating the tagcloud

All the work happens in the onTagCloudSuccess function. In this function I iterate over all the items in the TagCollection. For each item I calculate its relative weight using the TagCollections MaxWeight property. Depending on the relative weight the tag gets a different css class. This logic can be found in the getCloudItemClass function.

Finally I append a new listitem to the unordered list 'items' using the tags value and the calculated css class.

   1:  function onTagCloudSuccess(data, textStatus) {      
   2:      var maxWeight = data.d.MaxWeight;
   3:   
   4:      $.each(data.d.Items, function(i, item) {
   5:          var itemWeight = item.Weight;
   6:          var relativeItemWeight = itemWeight / maxWeight;
   7:          var itemClass = getCloudItemClass(relativeItemWeight);            
   8:   
   9:          $("#items").append("<li class='" + itemClass + "'>" + item.Value + "</li>");
  10:      });       
  11:  }
  12:   
  13:  function getCloudItemClass(weight) {
  14:      if (weight < 0.35) {
  15:          return "light";
  16:      } else if (weight < 0.7) {
  17:          return "medium";
  18:      } else {
  19:          return "heavy";
  20:      }       
  21:  }


Don't forget to add an unordered list with the id set to 'items' to your page.

So far this looks something like this.



I used following css to make the unordered list look like a tagcloud.

   1:  #items li {               
   2:      display: inline-block;         
   3:      margin: 4px;
   4:  }
   5:   
   6:  .heavy { font-size: 60px ; color:Red; }
   7:  .medium { font-size: 30px; color:Orange; }
   8:  .light { font-size: 14px ; color:Yellow; }


Setting the display to inline-block on the listitems makes the unordered list go horizontal instead of vertical. The heavy, medium and light classes are used to give the tagclouditems a style that matches with their heaviness.

The final result looks like this.



Different ways of marking up a tagcloud can be found in this article.

Get the source

You can find the full source here.

Feedback

As always I want to hear your feedback. Please tell me how this implementation can be improved.

2 comments:

  1. var feedbackitems = new List() {
    new Tag("Great Article", 80),
    new Tag("Comprehensive", 80),
    new Tag("Cristal Clear", 80),
    new Tag("Keep on Posting!!!", 90) };

    ReplyDelete