json2dom

27 04 2009

I like jQuery but creating DOM elements on the fly with jQuery is pretty boring. I guess I am not the only one. Just take a look at all this plugins. Well I added one more :)

json2dom let you create dom elements on-the-fly using json representation. The problem with other plugins is that they loose the power of jQuery… on json2dom you still can use all jQuery function while representing the data with json.

To create an element just pass a dictionary to the json2dom function where the key is the tag name. the value will be the content of the tag/element. it can be:

  • string -> text content
  • array -> child elements
  • jQeury -> a jQuery object containing a DOM element
  • dictionary -> key: name of a jQuery method. value: parameters to the method

check the plugin page for some examples.





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.





running selenium tests from the command line

12 01 2008

I wrote a tutorial on how to run selenium tests from the command line. it is very handy you plan to include it on your continuous integration process.

intro

selenium is great tool for writing tests to web applications. and with Selenium IDE, it is a breeze to write tests. here i will describe how to run your selenium tests from the command line (instead of manually starting the server, opening a browser, selecting the test file…). if you dont use selenium yet you should take a look at it first.

i will not go over the advantages over having regression tests… but to be effective it should be easy to run the tests. it is very boring to manually run the tests, so once in a while when i decide to run it i found out that some regression bugs appeared, and they were not easy to spot because they might got into the code a while ago. even worse is when somebody else on team introduced the bug ;)

complete article