Creating Dynamic Layouts

an article added by: Sonja Lande at 06012007


In: Categories » » AJAX » Creating Dynamic Layouts

This article solves an age-old problem, as it looks at creating a dynamic user interface that shifts its layout depending on what resolution or user agent is displaying or viewing the user interface. Problem You want to create a dynamic user interface that is laid out as logically and as legibly as possible, is spread across a wide variety of differing user agents (including varying resolutions, browsers, devices, and so on), and makes sensible use of the screen real estate available. Theory Think about what happens when you surf to an HTML page such as http://www.eweek.com.

Note I’m not critiquing eWeek specifically, but only as an example, because almost every Web site has the same flaw. The reality is that neither is better, because each has advantages and disadvantages. Yet neither is optimal. When the Web initially started to become popular, most computing devices had the same screen resolution, and people rarely surfed the Internet on cell-phone devices. Now computing devices are extremely powerful, but vary to an extreme in their screen resolution. Personal computing needs can vary from a low of 800×480 to a high of 4500×1200 for a dual screen. It doesn’t make sense to fix the screen resolution. The proportions used in the small browser are one column, one column, and one column. However, the bigger browser uses one column, two columns, and two columns. What you are doing is readjusting the space so that in the smaller window, only one column is illustrated. In the bigger window, more columns can be illustrated. Solution This article explains the solution in pieces, starting with the overall calling HTML page that calls the sizing algorithm, and then the sizing algorithm itself. The following code comprises the overall HTML page.

   Source: /website/ROOT/ajax articles/dhtml/dynamiclayout.html
 <html>
 <head>
 <title>Dynamic Layout</title>
 <script language="JavaScript"  src="/scripts/jaxson/common.js"></script>
 </head>
 <script language="JavaScript"  type="text/javascript">
 var flexbox;
 function updateClientArea() {
 flexbox.update();
 }
 function InitializePage() {
 flexbox = new FlexBox("content");
 flexbox.setCharacteristics({ col1 : { width :  200, maxCols : 1},
 col2 : {width : 100, maxCols : 4},
 col3 : { width :200}});
 flexbox.setContentCallback( {
 updateContent : function( cell, childElement,  colCount, characteristic) {
 childElement.innerHTML = "Boxes (" +  colCount +
 ") id(" + cell.id + ")"}});
 flexbox.update();
 window.onresize = updateClientArea;
 }
 </script>
 <body  onload="InitializePage()">
 <div id="content">
 <div id="col1"></div>
 <div id="col2"></div>
 <div id="col3"></div>
 </div>
 </body>
 </html>

Looking at the code for the HTML page, you can see that a type called FlexBox is instantiated and assigned to the global variable flexbox. The type FlexBox is the sizing algorithm. The action of instantiating and calling the FlexBox type is carried out in the function InitializePage, which is called by the body.onload event.2 You want to instantiate the FlexBox type in the body.onload event, because that is the first safe location for code to operate on a complete DOM model. When FlexBox is instantiated, it needs a constructor parameter; in the case of the example, that is the buffer "content". The buffer "content" is an ID reference to an HTML element that serves as the basis of the HTML elements that will be resized. In the example, the ID content references a div element, which contains child div elements. In this sizing algorithm, a div element references child div elements. In your implementation of this article, you don’t need to do that. This sizing algorithm illustrates that an HTML element contains a number of content blocks, which serve as the columns that will be resized. When you assign an instance of FlexBox to flexbox, FlexBox first initializes itself and reorders the div elements into a table structure. Executing the FlexBox constructor causes it to reorder the div elements to the following HTML:

   <div id="content">
   <table>
   <tr>
   <td  id="col1"><div id="col1"></div></td>
   <td  id="col2"><div  id="col2"></div></td>
   <td  id="col3"><div  id="col3"></div></td>
   <tr>
   <table>
   </div>

The reordering of the HTML to a table element is a necessity. Two modes proportionally align elements on an HTML page: absolute coordinates and relative coordinates. By default, HTML uses relative coordinates, which the browser calculates. In relative coordinate mode, you only need to define which elements follow the other elements. Using absolute coordinates solves the problems of aligning the three child div elements, but the rest of the HTML page might be oddly aligned, as some elements might not use absolute coordinates. The simple way to align content on an HTML page using relative coordinates is to use a table. Thus, the sizing algorithm looks at the parent div element, creates a table, and creates a table cell for each child div element. Some readers might think that it would have been easier to define table rather div elements and then have the algorithm work with the table elements directly. It is true that using a table element would have been easier, but this algorithm illustrates how you can replace HTML elements with new HTML elements. Getting back to the source code of the calling HTML page, the method setCharacteristics is called once flexbox has been assigned. You pass an object definition with a number of embedded objects to setCharacteristics. The purpose of setCharacteristics is to define the proportional column widths of the child div elements. Thus, the embedded object has a number of properties that correspond one-to-one with the IDs of the child div elements. In the example, the embedded object defines the following restrictions:

• col1: Each column should be 200 pixels wide, and there is amaximum of only a single column.

• col2: Each column should be 100 pixels wide, and there is a maximum of four columns.

• col3: Each column should be 200 pixels wide, and there can be as many columns as there is space. To understand the restrictions, you need to understand the nature of the sizing algorithm. The idea of the sizing algorithm is to define proportions that allow a client application to order content appropriately. By increasing the width of the browser, you can assemble more blocks of content side by side. The readability of the HTML page is improved, because there is less scrolling, and the HTML page resembles that of a newspaper. The difference with the HTML page is that unlike a newspaper, the number of columns displayed depends on the size of the client window. The advantage is that if you look at the HTML page from the UMPC device or from a wide-screen notearticle, you still get a good look and feel. This article and the sizing algorithm focus on the awareness of giving the HTML page feedback that a table cell now has room for one, two, or three columns of content. How that content is displayed depends on the HTML page and is beyond the scope of the sizing algorithm. Getting back to the source code of the calling HTML page, the function setContentCallback is called after calling setCharacteristics.

The purpose of the function setContentCallback is to define a user callback that is updated with the new information of how many user-restricted columns can fit in a cell. In the example, the client code displays the parameters in the table cell. To reproportion the table, you call the method update. The first time update is called, it is from the body onload event. The body onload event is fired only once and won’t be called again if the client resizes the client browser. To have the update method called whenever the browser is resized, the window.onresize event is assigned. In the example, you assign window.onresize to the function updateClientArea, which makes a single method call to flexbox.update, which in turn updates the proportions of the table. Now that the theory of the sizing algorithm has been explained from the abstract level, let’s discuss the details of the sizing algorithm. The following code shows the complete implementation of the sizing algorithm. Note that the sizing algorithm is a custom implementation, and you might choose a different strategy in your own situation:

   function FlexBox(parentIdentifier) {
   if (typeof(parentIdentifier) ==  "string") {
   this.parentIdentifier =  document.getElementById(parentIdentifier);
   }
   else {
   this.parentIdentifier = parentIdentifier;
   }
   this.table  = document.createElement("table");
   this.table.border  = 1;
   var  tablerow = this.table.insertRow(-1);
   var  tempArray = new Array();
   for(  var c1 = 0; c1 < this.parentIdentifier.childNodes.length; c1 ++) {
   tempArray[  c1] = this.parentIdentifier.childNodes[c1];
   }
   for  (var c1 = 0; c1 < tempArray.length; c1 ++) {
   var  child = tempArray[c1];
   if  (child.nodeName.toLowerCase() == "div") {
   var  cell = tablerow.insertCell(-1);
   cell.appendChild(child);
   if(  child.id) {
   cell.id  = child.id;
   }
   }
   }
   this.parentIdentifier.appendChild(this.table);}
   FlexBox.prototype.setCharacteristics =  function(characteristics) {
   this.characteristics = characteristics;
   FlexBox.prototype.setContentCallback =  function( cbContent) {
   this.cbContent = cbContent;
   FlexBox.prototype.update = function() {
   var row = this.table.rows[0];
   var totalCells = row.cells.length;
   var availableLength =  document.body.clientWidth;
   for( var index in this.characteristics) {
   this.characteristics[ index].cols = 0;
   }
   var Increment;
   if( this.characteristics.updateAlgorithm) {
   Increment =  this.characteristics.updateAlgorithm;
   }
   else {
   Increment  = function( ref) {
   var  takenWidth = 0;
   for(  var index in ref.characteristics) {
   takenWidth  += ref.characteristics[ index].cols *
   ref.characteristics[  index].width;
   }
   var  didIncrement = false;
   for(  var index in ref.characteristics) {
   var  obj = ref.characteristics[ index];
   if((takenWidth  + obj.width) < availableLength) {
   if(  obj.maxCols) {
   if(  obj.cols < obj.maxCols) {
   obj.cols  ++;
   didIncrement  = true;
   }
   }
   else  {
   obj.cols  ++;
   didIncrement  = true;
   }
   }
   }
   if(  didIncrement) {
   Increment(ref);
   }
   }
   Increment(this);
   for (var c1 = 0; c1 < row.cells.length; c1  ++) {
   if( row.cells[ c1].id) {
   var id = row.cells[ c1].id;
   if (this.characteristics[id] &&  this.characteristics[id].width) {
   row.cells[ c1].width = this.characteristics[  id].width *
   this.characteristics[ id].cols;
   if( this.cbContent &&  this.cbContent.updateContent) {
   this.cbContent.updateContent(
   row.cells[ c1], row.cells[ c1].childNodes[ 0],
   this.characteristics[ id].cols,
   this.characteristics[ id]);
   }
   }
   }
   }
   }

For the most part, the code not shown in bold is support code for the bold code. The first bold code section implements the logic used to convert the div and child div elements into a table that contains the child div elements. The second bold code section implements the logic to proportion the table cells. Let’s begin by dissecting the first code block and the manipulation of the table and child div elements. You can manipulate the DOM in one of two ways: You can use the innerHTML property, or you can manipulate objects using methods. This article manipulates objects using methods, because that approach is simpler. The child div elements could contain some fairly sophisticated HTML code that you don’t want to serialize and deserialize. Using objects and moving them in the DOM doesn’t corrupt the child div elements. Note the following items from the code:

Use the method document.createElement to instantiate an HTML element: The returned HTML element instance is instantiated but is not part of the HTML page and needs to be added.

If an element represents a specific HTML element such as a table, then associate the methods of the HTML element with that element instance: In the example, a table is instantiated, and tables reference rows.

Use a temporary variable to store elements before they are reordered: Remember earlier when I said that the sizing algorithm doesn’t need to convert div elements to table? Before looking at the explanation of the sizing algorithm, notice in the first bold code block how elements are saved to a temporary array (tempArray) before being added back to the HTML document as child elements of a table. This step of saving references temporarily is absolutely crucial when you’re manipulating an object document model. If you don’t save temporarily, you could experience some very funky side effects. This extra step was added to the article to illustrate how to manipulate a document properly.

Add an HTML element instance to the HTML page hierarchy using a DOM method such as appendChild: If the instance passed to appendChild is an element already located in the HTML page, then call a removeChild before calling appendChild. The calling of removeChild happens transparently. The second bold code block implements the algorithm to partition the table. The logic is a brute-force technique that distributes the widths among the table cells and sees if the distribution is more or less than the available width. The best way to illustrate the logic is to go through the example HTML page. The restrictions of the example HTML page were outlined in bullet form a couple of pages back. The logic reads the restrictions and for the first iteration, attempts to place a single column in each table cell. The widths of each column are added together and tested against the width of the client area. If the added width is less than the client area width, then another iteration is executed. In the example, the first table cell can only contain a single column width. Another iteration is carried out, and the second table cell column count is incremented. The pattern of iterations results in a column count pattern that resembles the following:

   1 1 1
   1 2 1
   1 2 2
   1 3 2
   1 3 3
   1 4 3
   ...

You use the brute-force technique, because you want to distribute the columns equally among all of the table cells. If you want to use your own distribution algorithm, then the object instance passed to the method setCharacteristics needs to have a data member updateAlgorithm. When implementing your own algorithm, keep in mind that you want to distribute the column widths among the table cells. Remember the following things:

• With today’s technology, a wide variety of devices have extreme differences in screen resolution.

• Fixed-width or completely percentage-based HTML pages look good only on specific screen dimensions. If a screen is beyond those dimensions, the HTML page looks bad.

• When accommodating different screen resolutions, consider the page layout dynamically in the horizontal and vertical dimensions. Most pages only consider the HTML page as being dynamic in the vertical dimension.

• Don’t use absolute coordinates when using algorithmic proportions, because that requires adjusting all of the elements on the entire HTML page. Use relative coordinates, which imply HTML table elements.

• When moving content in a horizontal and vertical fashion, use a distribution algorithm that distributes the content evenly on the HTML page.

legal notice

Our website is not responsible for the information contained by this article. Web-articles is a free articles resource.
Suggestion: If you need fresh, daily updated content for your website, feel free to use our service. Click here for more information.

Useful tools and features

Link to this article from your page    Send this article to you or to a friend
If you like this article (tutorial), please link to it from your web page using the information above.

related articles

1. Testing a Dynamic Contract with Ajax
Coding the Contract Using Test-Driven Development Techniques Coding the contract using agile and test-driven development techniques requires writing a number of tests and implementing aMock URL layer. Problem You want to code the contract using these development techniques. Solution To demonstrate, let’s define a use case, implement the use case as a contract, write a test case(s) to implement the contract, implement the contract in the Mock URL, and finally...

2. Testing the Client Side Logic
Problem You want to effectively test your application’s client-side logic. Theory Testing GUI code tends not to be a productive task because of the complications that arise. The main complication is how to test the correctness of a user interface. Imagine a situation where clicking a button causes a table to be filled with data. Now imagine that when a check box is checked and the button is clicked again, a different table is filled with content. The fact that clicking the same button results in two ...

3. Understanding JavaScript and Types
Understanding JavaScript and Types Problem You want to work around the fact that JavaScript does not have types declared for its variables. Theory JavaScript code does not have any variables with a declared type. The lack of typed variables is apparent when you declare functions. That said, not having typed variable declarations does not mean JavaScript has no types or no type safety. Let’s start out with the simple declaration of a function, as illustrated by the following ex...

4. Coding Using Conventions and Not Configurations
Coding Using Conventions and Not Configurations Problem You want to make your JavaScript constructs more efficient by applying the Rails “convention over configuration” principle to them. Theory You may already be familiar with the programming platform Ruby on Rails, which is used to build Web applications. The focus of this recipe is not Ruby on Rails, but one aspect of Ruby on Rails namely, convention over configuration (see http://en.wikipedia.org/wiki/ Ruby_on_Rails for m...

5. Advantage of parameterless functions in JavaScript
Using Parameterless Functions Problem You want to take advantage of parameterless functions in JavaScript. Theory JavaScript functions for the most part have parameters. You may think that the previous sentence states the obvious after all, without parameters, what data could be passed to a function? JavaScript has the ability to declare functions that have no parameters, even though the caller of the function has passed parameters to the function. For example, let’s look at...

6. JavaScripot Functions
Treating Functions Like Objects Problem You want to take advantage of the fact that functions are objects (remember, everything is an object in JavaScript). Theory Many people think that a function is some keyword used in JavaScript. A function is also an object that can be manipulated. Knowing that a function is an object makes it very interesting from the perspective of writing JavaScript code, because the code can treat the function like another other object. This mean...

7. Implementing an Error and Exception Handling Strategy
Implementing an Error and Exception Handling Strategy Problem You want to implement a clean error and exception handling strategy in your applications, to make them run more smoothly. Theory Of course, you might argue that one error is a dialog box and the other is generated in the JavaScript console. The fact that one browser uses a dialog box to show an error and the other does not is a browser issue, not an error issue. A concise way of classifying the two errors is to ...

8. Understanding the Behavior of Variables When Implementing Recursion
Understanding the Behavior of Variables When Implementing Recursion Problem You want to implement recursion in JavaScript, and you also want to understand how variables will behave under those circumstances. Theory In JavaScript, you do not need to declare the variable type, or even declare the variable. For example, the following code works perfectly: if( counter == 1) { buffer = "counter is 1"; } document.getEle...

9. Using Functions to Initialize and Make Decisions JavaScript
Using Functions to Initialize and Make Decisions Problem You want to use functions to initialize and make decisions. Theory Usually when you write a piece of code where a change of logic needs to take place based on a context, you use a decision structure. For example, say you are implementing a light switch using a program. You turn on the light if the light is off, and you turn off the light if the light is on. The behavior of the program is determined by the conditions. One example behavior t...

10. Understanding the Ramifications of Duck Typed Code
Understanding the Ramifications of Duck Typed Code Problem You want to understand where to best use duck typing and the issues you should be aware of when using it. Theory There is a difference between a value type and a reference type in JavaScript. Even for a reference type, there is a difference between defining the reference as a value or a pure reference. But should you even care about the difference? Is it something that you need to be aware of? It is when you are...