Dynamic Grid Layout In JavaScript

by Zack Grossbart on June 6, 2009

You don’t see layout managers much in web applications. Most web pages are positioned with a combination of CSS and <table> tags. Higher end sites are switching to totally CSS based layouts, but it is still a pretty fixed layout. These mechanisms work reasonably well when you know what you are going to show and how big it is ahead of time, but they are lacking when it comes to dynamic contents.

This layout manager was originally developed for a new site I am making called Get The Eye. That site (like this one) is a WordPress blog and I wanted to make each article automatically appear on a grid. I combined JavaScript with PHP to generate a dynamic grid that grows for each new article.

Core Technologies

This program uses the following core technologies:

This sample is a JavaScript application that makes heavy use of the JQuery library. It is a good introduction to basic JQuery. It also requires basic understanding of CSS.

The code in this application is free and is released under the Apache 2.0 license. That means you are welcome to use, copy, and change this code as much as you would like. If you find any bugs, have any comments, or make any improvements I would love to hear from you. This example has been tested in Firefox, IE, Safari, and Chrome.

A simple example

1
2
3
4

In this simple example we line up four items in a two by two grid. Here the items are simple text, but they are just <div> tags and you can put whatever content you want into them. The grid is aligned with a simple call to the alignGrid function. It looks like this:

jQuery(document).ready(function() {
    alignGrid("basicgrid", 2, 5, 5, 1);
});

This single function call tells the JavaScript code what to do by giving it a series of arguments. First we tell the code which item to align by specifying the ID of the tag we are using. Then we give it a little information about how the grid should look. The next arguments specify the number of the columns, the width of each cell, the height of each cell, and the space in between them. All of the units are in EMs, but we’ll look into that later. This makes it very easy to dynamically change the size and shape of the grid.

You can place this function in a <script> tag on the top of an HTML document or in a separate JavaScript file.

The JavaScript

Get the
source code

The alignGrid function is defined in the multigrid.js file. Let’s take a look at how it actually works.

function alignGrid(/*string*/ id, /*int*/ cols, /*int*/ cellWidth, 
    /*int*/ cellHeight, /*int*/ padding) {
    
var x = 0;
var y = 0;
var count = 1;

jQuery("#" + id).each(function() {1
    jQuery(this).css("position", "relative");2
    
    jQuery(this).children("div").each(function() {3
        jQuery(this).css("width", cellWidth + "em");
        jQuery(this).css("height", cellHeight + "em");
        jQuery(this).css("position", "absolute");
        
        jQuery(this).css("left", x + "em");
        jQuery(this).css("top", y + "em");
        
        if ((count % cols) == 0) {4
            x = 0;
            y += cellHeight + padding;
        } else {
            x += cellWidth + padding;
        }
        
        count++;
    });
});
}

This function is calling the most common function in the JQuery library simply called jQuery. You can also use a dollar sign ($) instead, but I spelled it out to make everything a little clearer. You could replace every jQuery with a dollar sign and this would still work.

An EM is a special unit of measure in CSS. It is literally the height and width of an uppercase M in the current fonts. EMs are a much more dynamic unit than pixels because they change size when the font size changes.

The first step is to find the item we are going to align1. Then we set a CSS property for that element telling it to use the relative positioning model2. This is important because we are using absolute positioning within the grid. That means we specify the exactly where each item should go. Making the container relative means each of the grid cells is relative to the grid itself.

Once the grid is properly positioned and styled we loop through the cells3 and position them. We then have a little logic to bump down to the next row when we get to the specified number of columns4.

Basically we are finding each cell and setting CSS properties to tell it exactly where to position itself. The important properties we set are left, top, width, and height. The properties specify where the upper left corner of the cell should be positioned and how large the cell should be. The unit we are using is EMs.

The HTML

Since our JavsScript is doing all of the work the HTML is very simple.

<div id="basicgrid">
    <div class="cell">1</div>
    <div class="cell">2</div>
    <div class="cell">3</div>
    <div class="cell">4</div>
</div>

The multi-grid

The grid is nice for basic uses, but it has no room to grow. As the grid gets larger the items will fill up the entire screen. It needs some way to switch from one set of grid cells to the next; a navigation mechanism. That is what the multi-grid provides. Take a look at the example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

The multi-grid provides three ways to navigate: the arrow buttons on either side, the navigation bar at the bottom, and your keyboard. The multi-grid responds to the right arrow, left arrow, HOME, and END keys on your keyboard. For this reason there can only be one multi-grid per page.

How it works

grid_example

The multi-grid is a view port. Kind of like looking into a room through a keyhole. You only see a small part of the whole. The entire grid is always there, but most of the time only part of it is visible. When you press the next or previous buttons it slides the grid so you are seeing a different part.

This application uses JavaScript and CSS. There is no Flash, Silverlight, or any other plugin. It gets all the data from the server and displays it in a way to looks like multiple pages. Moving within the grid doesn’t need to contact the server.

The rest of this article will show how this mechanism works and how you can implement it for yourself. It isn’t super techy, but it does take some code.

Making it work

You call the multi-grid in almost the same way as the basic grid.

jQuery(document).ready(function() {
    alignMultiGrid(2, 5, 5, 1);
});

The function arguments are almost the same, but the multi-grid just allows one per pages so you don’tt need to specify the tag ID.

The HTML

The multi-grid needs to align the navigation buttons and the navigation bar as well as the cells within the grid. To support that we add a little more structure to our HTML. It looks like this.

<div class="maingridcontainer">
    <div class="multigridcontainer">
        <div class="multigrid">
            <div class="cell">1</div>
            <div class="cell">2</div>
            <div class="cell">3</div>
            <div class="cell">4</div>
            
            <div class="cell">5</div>
            <div class="cell">6</div>
            <div class="cell">7</div>
            <div class="cell">8</div> 
        </div>
    </div>
</div>

The extra <div> tags give us a place to add the extra navigation pieces. If you’re familiar with HTML you’ll notice that there is a lot missing here. There are no buttons and no button bar. It all get added by the JavaScript code. We just need the placeholder for them.

The JavaScript

The alignMultiGrid function is a little long so we’ll look at it in parts. First we need some variables.

var xoffset = padding;
var x = xoffset;    //The X position of the current cell
var y = padding;    //The Y position of the current cell
var cellcount = 1;  //The total number of cells
var rowcount = 1;   //The total number of rows
var maxrows = 2;    //The number of rows in each view of the grid

//The width of the grid
gridwidth = (cols * (cellWidth + padding)) + padding;
                    
//The height of thr grid
gridheight = (maxrows * (cellHeight + padding)) + padding;

//The position of the grid.  This is used in the navigation.
currentgrid = 0;

Once the variables are declared we need to set styles on each of the three <div> tags. We handle each tag separately starting with the outer most tag and working our way down to the children.

jQuery(".maingridcontainer").each(function() {1
    jQuery(this).css("position", "relative");
    jQuery(this).css("width", (gridwidth + 5) + "em");
    jQuery(this).css("height", (gridheight + 5) + "em");
    
    
    jQuery(this).append(<div class=\"nextprev\" id=\"nextcontainer\">" + 2
        "<a href=\"#\" id=\"next\"><img src=\"nav/next.gif\" border=\"0\" /></a>" + 
        "</div>");
    jQuery(this).append("<div class=\"nextprev\" id=\"prevcontainer\">" + 
        "<a href=\"#\" id=\"prev\"><img src=\"nav/prev.gif\" border=\"0\" /></a>" + 
        "</div>");
    
    jQuery("#prevcontainer").each(function() {3
        jQuery(this).css("width", "4em");
        jQuery(this).css("height", "2em");
        jQuery(this).css("position", "absolute");
        
        jQuery(this).css("top", ((maxrows / 2) 
                    * (cellHeight + padding) - 0.5) + "em");4
        jQuery(this).css("left", "0em");
    });
    
    jQuery("#nextcontainer").each(function() {
        jQuery(this).css("width", "4em");
        jQuery(this).css("height", "2em");
        jQuery(this).css("position", "absolute");
        
        jQuery(this).css("top", ((maxrows / 2) 
                    * (cellHeight + padding) - 0.5) + "em");
        jQuery(this).css("left", (gridwidth + 2) + "em");
    });
});

We start by aligning the next and previous buttons. First we find the maingridcontainer1. Then we append the HTML content for the cuttons2. Once we’ve added the content we can find it and style it3. We set each button to be halfway down the grid4.

Now that we’ve added the next and previous buttons we need to add the navigation grid. We don’t know how many cells are in the grid yet so we just add the container for them and we’ll add the cells later.

jQuery(".multigridcontainer").each(function() {
    jQuery(this).css("overflow", "hidden");1
    jQuery(this).css("position", "relative");
    jQuery(this).css("left", "2em");
    jQuery(this).css("width", gridwidth + "em");
    jQuery(this).css("height", (gridheight + 5) + "em");
    
    jQuery(this).append("<div id=\"gridnavcontainer\">" + 
                    "<div id=\"gridnav\" class=\"grid\"></div></div>");2
    jQuery("#gridnavcontainer").each(function() {
        jQuery(this).css("position", "absolute");
        jQuery(this).css("top", (gridheight + 3) +  "em");3
    });
});

This is also the part of the code where we set the style for the <div> tag that directly contains the grid1. We set the overflow property to hidden. This is a fundamental part of the multi-grid. The grid is always there. Every cell is always rendered, some of them are just off the screen. This property tells the browser to only show what is visible and not use scroll bars.

After that we add the container for the navigation bar2 and position it using CSS3.

The last part of the alignment process is to align the cells within the grid. This code looks very similar to the alignGrid function from earlier.

jQuery(".multigrid").each(function() {
jQuery(this).css("position", "relative");

jQuery(this).children("div").each(function() {
    jQuery(this).css("width", cellWidth + "em");
    jQuery(this).css("height", cellHeight + "em");
    jQuery(this).css("position", "absolute");
    
    jQuery(this).css("left", x + "em");
    jQuery(this).css("top", y + "em");
    
    if ((cellcount % cols) == 0) {
        if ((rowcount % maxrows) == 0) {1
            y = padding;
            xoffset += gridwidth;
            x = xoffset;
            gridcount++;
            addNavItem(gridcount);2
            
        } else {
            x = xoffset;
            y += cellHeight + padding;
        }
        
        rowcount++;
    } else {
        x += cellWidth + padding;
    }
    
    cellcount++;
});

We are once again looping through the cells in the grid and setting their position. We also need to adjust for the maximum number of rows in each grid1. We also call a function to add an item to the navigation bar when there is a new grid2.

After this we do a little extra like adding listeners for the button clicks. It is also interesting to note that we’ll use the alignGrid function from earlier to align the items in the navigation bar. You can see those functions by looking at the source code.

The slide

The other interesting function to look at is positionGrid. This function responds to the navigation buttons and positions the grid using the slide effect. The slide is a custom effect, but JQuery makes creating those types of effects very easy.

function positionGrid(/* int */ gridpos) {
    jQuery("#gridnav" + currentgrid).removeClass("gridnavitemselected");
    currentgrid = gridpos;
    jQuery(".multigrid").each(function() {1
        jQuery(this).animate({ 
            left: "-" + (gridwidth * currentgrid) + "em"
            }, 1500 );
    });
    
    if (currentgrid == 0) {2
        jQuery("#prevcontainer").hide(0);
    } else {
        jQuery("#prevcontainer").show(0);
    }
    
    if (currentgrid == (gridcount - 1)) {
        jQuery("#nextcontainer").hide(0);
    } else {
        jQuery("#nextcontainer").show(0);
    }
    
    jQuery("#gridnav" + currentgrid).addClass("gridnavitemselected");3
    
    return false;
}

All of the mechanisms that reposition the grid eventually call this function which contains the slide animation1. Really. That is all there is to it. I just tell JQuery to animate a transition from the current CSS properties to the one I specified and tell it how long to take. That’s all there is to it.

After that I do a little cleaning up like making sure the next and previous buttons are only showing if there is a next or previous page2 and setting the CSS class that gives the selected navigation item the green background3.

Conclusion

Web applications are still catching up to their desktop counter parts. These types of layout managers get us a small step closer to a fully-featured web.

Feel free to use this technique and this code on your site. If you do please leave a comment and let us know. We’d love to hear about you.