designing a javascript widget

4 05 2008

I was playing with the idea on how to create javascript widgets. As an experiment I created a simple picture album viewer.

I have been having some problems with declarative widgets. I guess declarative widgets make it easier to add them to a page but it is bad for customizations and more dynamic behavior.

As an exercise I developed a prototype for a Photo Album Viewer. No magic here, you need to know HTML, JavaScript and CSS.

If you are looking for production-ready javascript widgets to use you wont find it here.

Javascript toolkits are great to solve cross-browser issues and usually give a boost in the productivity. But I will use plain Javascript. Tested on Firefox 2 only.

Photo Album Viewer

I want to design a basic Photo Album Viewer. Just one picture is show as the main content and an series of thumbnails from the album appears on the bottom. The user selects photos from the thumbnails clicking on it. A image from the thumbnail is displayed on the main content.

The whole point of developing a widget is to create something that will provide a richer user experience than a plain old HTML. I will focus on usability but will ignore colors, round borders…

Dealing with Images

Before creating the widget itself lets take a look in some issues of this application.

1) Thumbnails selection

The idea is that all photos will be available on its real size and on reduced size(thumbnail). The thumbnail bar was supposed to be "carousel" where you can go over all pictures in the album and load the thumbnails only when needed. To keep it simple I will load all of them in a scroll bar.

2) loading images dynamically.

Loading all images at once is not an option. Images will be loaded only on request. (though I plan to add some kind of pre-loading later). To load images dynamically, just create a "img" element and set it’s "src" attribute. You can register an event for "onload" to be notified when it is completed loaded.

var imgEle = document.createElement("img");
imgEle.onload = function(){...};
imgEle.setAttribute("class","nodisplay");
imgEle.setAttribute("src",photoURL);
containerNode.appendChild(imgEle);

3) Adjusting picture size

The whole picture must fit inside the display area, and of course the width/height ratio must be preserved. To do this some basic geometric calculation were done.

The real picture dimensions are Picture.width and Picture.height. The Picture.maxWidth/maxHeight are the maximum bounding box for the picture. The picture is adjusted to its biggest size without overflowing the bounding box and the margin is adjusted so the image appears in the center.

Picture.prototype.adjust_size = function(){
    var ratioX = this.width / this.maxWidth;
    var ratioY = this.height / this.maxHeight;
    var used,space;
    if(ratioX > ratioY){
      used = this.height / ratioX;
      space = this.maxHeight - used;
      this.node.style.width = this.maxWidth;
      this.node.style.height = used;
      this.node.style.marginTop = space / 2;
    }
    else{
      used = this.width / ratioY;
      space = this.maxWidth - used;
      this.node.style.width = used;
      this.node.style.height = this.maxHeight;
      this.node.style.marginLeft = space / 2;
    }
};

4) Usability

Images take time to load. So I added a "loading ajax" gif so the users know what is going on. I keep track of the image status (not-loaded/loading/loaded). The images are displayed only when completely loaded. Loaded images are cached.

The widget

Ok. Now we have enough material to think on the widget. The widget is nothing more than putting all parts together.

After creating the widget the DOM is supposed to have this structure.

<div>                             <!-- div containing the whole album -->
  <div class="picview">           <!-- display are for the picture -->
     <img src="ajax.gif" ...>     <!-- loading ajax gif  -->
     <span class="lightbox" ...>  <!-- another indication picture is loading -->
                                  <!-- an image NOT being displayed -->
     <img src="/images/pic1.jpg" class="nodisplay" ...>
                                  <!-- the image being displayed -->
     <img src="/images/pic4.jpg" ...>
                                <!-- more images....>
  </div>

  <div class="thumbbar">            <!-- thumbnails -->
     <a ...><img src="/images/thumb1.jpg"></a>
     <a ...><img src="/images/thumb2.jpg"></a>
                                    <!-- more thumbnails -->
  </div>
</div>

Where "…" indicates that some attributes need to be created dynamically (on client side).

So the million dollar question is: Where should be DOM structure be created? Server, client, both?

The problem is that you can not do everything on the server. You need to attach events to the DOM nodes, keep some runtime info of the widget. Most of the time I see widgets are mix a server and client code, this makes things harder to understand and work. If I could choose server only or client only I would go for server. But between sever & client or just client I go for client only.

So on the HTML page I just add a "onload" function to create the widget:

<script type="text/javascript">
  var ALBUM;
  function start(){
     var picview = document.getElementById("picview");
     ALBUM = new Album(document.getElementById("album"));
  }
</script>

Ok. So now I just need to pass some data. What is the best way to pass data to javascript? No not HTML. JSON of course:

<div id="album" class="data">
    [{thumb:'/media/images/thumb1.jpg',photo:'/media/images/pic1.jpg'},
    {thumb:'/media/images/thumb2.jpg',photo:'/media/images/pic2.jpg'},
    {thumb:'/media/images/thumb3.jpg',photo:'/media/images/pic3.jpg'},
    {thumb:'/media/images/thumb4.jpg',photo:'/media/images/pic4.jpg'},
    {thumb:'/media/images/thumb5.jpg',photo:'/media/images/pic5.jpg'},
    {thumb:'/media/images/thumb6.jpg',photo:'/media/images/pic6.jpg'},
    {thumb:'/media/images/thumb7.jpg',photo:'/media/images/pic7.jpg'}]
</div>

Of course this content would be created dynamically.

So instead of writing some HTML on server than have javascript to parse, modify, and attach events I just pass some data to the javascript widget. The widget is responsible for creating the HTML (easier than parsing and modifying). Another advantage is that you don’t need to add more code to make the widget work programmatically.

Take a look in the full example.