{{page_title}}

{{page_description}}


Many of the blog websites have the Tag Cloud displayed, to visualize the keywords (or tags) used in their blog posts, and usually more important words are shown with bigger font size and/or bolder colour. It will be very cool to have a tag cloud on my JiwhizBlog website as well.

By searching "tag cloud javascript", I found this fantastic open source jQuery plugin for tag cloud: jquery.tagcloud.js by Adam Groves. It is very simple to use. For example, add following code in html page:


With the javascript code:

$.fn.tagcloud.defaults = {
	size: {start:10, end: 20, unit: 'pt'},
	color: {start: '#00CC66', end: '#006600'}
};

$(function () {
	$('#tagcloud a').tagcloud();
});

Then you will get a pretty tag cloud in your page like this:

The html code is straightforward: a <div> block with css class of tagcloud. Inside the div, there is a list of tag strings, which are wrapped by hyperlink <a>. The jquery.tagcloud.js uses rel attribute to indicate the importance of the tag, and I want it to be the number of times this tag is referenced in all of my blog posts. And when user clicks the tag, the application will display a list of blog posts which have this tag in their tag list.

Here I will demonstrate how to implement this feature, starting from the front-end code first.

Thymeleaf HTML Code

To display a tag cloud in the public page side bar (at the right), I just add this html code using Thymeleaf th:each, assuming we will have a java.util.Map object named as tagCloud in the UI model, and the key of the Map is the tag name, value is the tag count:

Tag Cloud

tag

Page Controller Code

In my page controller class, I will use SpringMVC @ModelAttribute feature to pass tagCloud object to UI model:

public class AbstractPublicPageController extends AbstractPageController{
...
    @ModelAttribute("tagCloud")
    public Map addTagCloud() {
        return this.blogPostService.getTagCloud();
    }

...
}

I put this piece of code into AbstractPublicPageController, which is the super class of all controller classes used for public pages with tag cloud. So public pages will have this tagCloud object injected into UI model by @ModelAttribute annotation on the method. It is a very neat feature of Spring MVC.

BlogService Code

Our controller code will call service to get the tag cloud map object. The service code is also very simple: query the repository to get all published BlogPost objects, and count the tags of those posts.

    public Map getTagCloud() {
        Map tagCloud = new HashMap();
        List blogList = this.blogPostRepository.findAllPublishedPostsIncludeTags();
        for (BlogPost blog : blogList) {
            for (String tag : blog.getTags()) {
                if (tagCloud.containsKey(tag)){
                    tagCloud.put(tag,  tagCloud.get(tag) + 1);
                } else {
                    tagCloud.put(tag,  1);
                }
            }
        }

        return tagCloud;
    }

BlogPostRepository Code

Finally, let's see the code in the BlogPostRepository.findAllPublishedPostsIncludeTags(). My BlogPost class has many fields, but here we only care the tags stored for each post object. So I use a repository method annotated with @Query to get all published posts, which only contain tags field.

public interface BlogPostRepository extends MongoRepository{
...
    @Query(value="{ 'published' : true }", fields="{ 'tags' : 1 }")
    List findAllPublishedPostsIncludeTags();
...
}

Using Spring Data MongoDB Query annotation, we can filter out un-wanted properties in the returned Java object. Here the list of returned BlogPost object will only have id and tags, all other fields are null. It should have no problem since in BlogServiceImpl.getTagCloud(), we only use tags.

After all those changes, now you can see a fancy tag cloud in my page at the right side.

List Posts with Specific Tag

Each tag string in the tag cloud is also a hyperlink, and when user clicks, it will navigate to the page with list of blog posts which have the tag in their tag list. You can see the hyperlink is built with Thymeleaf th:href="@{'/blog/tag/' + ${tag.key}}". So I have to implement controller code to suppport the HTTP Get access for list of posts with specific tag, such as http://www.jiwhiz.com/blog/tag/MongoDB.

Here is the code in BlogController to do the job:

@Controller
public class BlogController extends AbstractPublicPageController {
...
    @RequestMapping(value = "/blog/tag/{tag}", method = RequestMethod.GET)
    public String blogWithTag(@PathVariable("tag") String tag, Model uiModel, Pageable pageable) {
        PageWrapper page =
            new PageWrapper(blogPostService.getPublishedPostsByTag(tag, pageable), "/blog/tag/"+tag);
        uiModel.addAttribute("page", page);
        uiModel.addAttribute("message",
                new Message(MessageType.INFO,
                    "Blog posts with tag "+tag+":"));

        return "blog";
    }
...
}

Using SpringMVC @RequestMapping and @PathVariable, I can easily pass input tag to back-end service, and get the result.

It uses PageWrapper to wrap the returned list of blog posts from BlogPostService. See my article of Implement Bootstrap Pagination With SpringData And Thymeleaf to know the details of pagination implementation.

The implementation of blogPostService.getPublishedPostsByTag(tag, pageable) is also very easy, just call BlogPostRepository method findByPublishedIsTrueAndTagsOrderByPublishedTimeDesc(tag, pageable) to get pageable list of BlogPost object which are published and have matching tag. And return list is ordered by publish time. With Spring Data MongoDB, I don't need to write any implementation code. Very nice!

public interface BlogPostRepository extends MongoRepository{
...
   Page findByPublishedIsTrueAndTagsOrderByPublishedTimeDesc(String tag, Pageable pageable);
...
}

Well, another small updates to my JiwhizBlog Application. Appreciate open source community to make this task not boring but fun.