http://www.developer.com/

Back to article

Decision Making Flags: The How and Why


July 12, 2006

Have you ever wanted to easily view information that is critical to your decision making process, yet there are other details that would be nice to know; however, those details are not as important to the final decision? A brief, but common usage scenario probably will illuminate what I'm attempting to convey.

Imagine that you want to search for an airline flight from one city to another on a specific date. There are certain items that are probably more important in your decision of which flight to choose. Such items may include:

  • Airport destination (note: keep in mind you are searching by city and not by specific airports)
  • Flight cost
  • Total travel time (one-way)
  • Departure time
  • Arrival time

These search results adequately answer your initial, primitive question of: "What flights are available from [departure city] to [destination city] on [date]?" However, ultimately you would like to be able to incorporate your preferences in with your final decision, yet your preferences aren't necessarily requirements. Such questions, or preferences, related to the flight may include:

  • What airline is affiliated with the flight?
  • Is a meal or snack provided on the flight?
  • Is there an in-flight movie?
  • Will I have a lay-over anywhere?

As you can imagine, if you were provided with all of this information at once, the user interface could become quite cluttered, perhaps overwhelming, and in the case of your lay-over question, perhaps inadequate. If you displayed our results using an archetypal grid, perhaps you would see the following:

Now, pretend you were provided only with the most important information and from there began your decision making process. You could expect to traditionally see something similar to the following:

Once a user selects an item from the list above, that user could be redirected to a screen that provides details related to the selected flight. That interface could look something like the following:

There are still some problems surrounding this interface. Navigating through various pages to collect the desired information is both cumbersome and inefficient. By implementing an interface that is both readily accessible and intuitive, you can provide an efficient and powerful decision making tool. To accomplish this, you will implement a slightly unique graphical construct. For the purpose of this article, you will refer to this construct as a "Flag" (this name is derived from the shape; it actually reminds me of one of those toy guns where the flag pops out and says "bang"). See how it appears below:

Creating flags within tabular data creates several interesting challenges. Throughout this article, you will address the implementation of flags within tabular data and examine the basics. The remainder of this article will use snippets from the source code that you can get here.

Implementation

Begin by creating the HTML you would initially see on the page. For the sake of simplicity, you will only be examining a single record table. In addition, you will attempt to keep the code simple to focus on the flag implementation. You will notice that you utilize a basic table element that contains the results from your initial question. Each tr element in the table represents one of the results from your search.

<html>
<head>
   <style type='text/css'>
      tr.gridHeader {background-color:#3891A7;font-weight:bold;}
      tr.flagStaff  {background-color:#E7DEC9;}
      td.gridHeader {color:black; border-bottom:solid 2px black;}
      td.spacer     {width:6px;}
   </style>
</head>

<body>
   <table id='RecordsTable' border='0' cellpadding='0'
          cellspacing='0'>
      <tr class='gridHeader'>
         <td class='gridHeader'>Destination</td>
         <td class='spacer'></td>
         <td class='gridHeader'>Cost</td>
         <td class='spacer'></td>
         <td class='gridHeader'>Departure</td>
         <td class='spacer'></td>
         <td class='gridHeader'>Arrival</td>
         <td class='spacer'></td>
         <td class='gridHeader'>Total Travel Time</td>
      </tr>

      <tr class='flagStaff' id='record1'>
         <td>Indianapolis</td>
         <td class='spacer'></td>
         <td>$236</td>
         <td class='spacer'></td>
         <td>4:20 p.m.</td>
         <td class='spacer'></td>
         <td>9:05 p.m.</td>
         <td class='spacer'></td>
         <td>3hr 45min</td>
      </tr>
   </table>
</body>
</html>

Snippet 1

After you have created entries for each result, you need to create the "flag" portion. Each flag contains the informative details of each result. In the example, a flag contains the airline information, whether there is a meal or snack, whether there is an in-flight movie, and flight schedule information to help you see layover related information. To construct the flag, you must implement the following steps:

  1. Create a tr element for each search result. This newly created table row holds the flag related information.
  2. Determine how wide the "flagstaff" should be. In your definition, you will refer to the flagstaff as being the part of the pole from which the flag is not hanging. You implement a flagstaff so that users can view detailed information for each search result without being interrupted by an already visible flag. To create the flagstaff, you simply create a td element and set the colspan property to the number of cells that you wish the flagstaff to occupy.
  3. Determine how wide the "flag" should be. The flag will most likely occupy the remainder width in the table, as is the case in the example. However, there may be times where you want to have flagstaffs on each side of the flag. The flag's area should be defined using another td element and once again utilize the colspan property accordingly.
  4. Finally, you construct the appearance of flag by using a span element. By using a span element, you gain a lot of freedom in designing your flag. The only limit you are really bound to is the width of the area that you set in Step 3. There are key css properties set in the flag class. Such properties are:
    1. display:none: This css property signals that the object should not be rendered. This allows you to load the flag without actually displaying it.
    2. position:absolute; This style attribute ensures that the flag is set in position when you need to display it. In addition, without this property, you would see the table resize.
    3. width:100%: This css property signals that you want the flag to fill up the number of columns previously set.

Using the code you implemented from Snippet 1, you now need to add the code, displayed in blue, from Snippet 2.

<html>
<head>
   <style type='text/css'>
      tr.gridHeader {background-color:#3891A7;font-weight:bold;}
      tr.flagStaff  {background-color:#E7DEC9;}
      tr.flagStaffHover {background-color:#3891A7;color:white;
                         cursor:pointer;}

      td.gridHeader {color:black; border-bottom:solid 2px black;}
      td.spacer {width:6px;}

      span.flag{display:none;position:absolute;width:100%;
                background-color:#3891A7;color:white;}
   </style>
</head>

<body>
   <table id='RecordsTable' border='0' cellpadding='0'
          cellspacing='0'>
      <tr class='gridHeader'>
         <td class='gridHeader'>Destination</td>
         <td class='spacer'></td>
         <td class='gridHeader'>Cost</td>
         <td class='spacer'></td>
         <td class='gridHeader'>Departure</td>
         <td class='spacer'></td>
         <td class='gridHeader'>Arrival</td>
         <td class='spacer'></td>
         <td class='gridHeader'>Total Travel Time</td>
      </tr>

      <tr class='flagStaff' id='record1'>
         <td>Indianapolis </td>
         <td class='spacer'></td>
         <td>$236</td>
         <td class='spacer'></td>
         <td>4:20 p.m.</td>
         <td class='spacer'></td>
         <td>9:05 p.m.</td>
         <td class='spacer'></td>
         <td>3hr 45min</td>
      </tr>

      <tr class='flagStaffHover' id='hiddenRecordFlag1'>
         <td colspan='4'></td>
         <td colspan='5'> <span id='record1Flag' class='flag'>
            <table border='0' cellpadding='0' cellspacing='0'>
               <tr class='selectedRow'>
                  <td align='left'>Airline: </td>
                  <td align='left'>Dodo Airway</td>
                  <td class='flagSpacer'></td>
                  <td align='left'>Movie: </td>
                  <td align='left'>Yes</td>
               </tr>

               <tr class='selectedRow'>
                  <td align='left'>Meal: </td>
                  <td align='left'>Yes</td>
                  <td class='flagSpacer'></td>
                  <td align='left'>Snack: </td>
                  <td align='left'>No</td>
               </tr>

               <tr class='selectedRow'>
                 <td colspan='2'>This is a direct flight</td>
               </tr>
            </table>
         </span></td>
      </tr>
   </table>
</body>
</html>

Snippet 2

Good news! You are done constructing the core HTML that your flags will utilize. However, you are not yet finished. At this point, you have all of the elements in place; however, you need to implement the behavior. The behavior handles the interaction between the user and the elements.

Ultimately, you want to have a flag that drapes over other results when a user hovers over a result. The process of displaying and hiding the flag will be fulfilled via some JavaScript. There are four events that you must watch for to properly display and hide the flag. These events are:

  1. When a user moves their mouse over a search result (a.k.a. flagstaff)
  2. When a user moves their mouse over a result's details (a.k.a. the flag)
  3. When a user leaves a result's details (a.k.a. the flag)
  4. When a user leaves a search result (a.k.a. flagstaff)

Before you implement the code to handle these events, you must identify who will be responsible for employing these events. To accomplish this, you must implement some consistent naming standard that will assist you in assigning the appropriate event handlers. Revisit Snippet 2 and notice that you are using the following naming structure:

  • Each tr element that contains your important details (the odd-numbered table rows) will be given an id that adheres to a consistent naming structure. In this case, each entry will be given the name "record[recordNumber]", where recordNumber is the index of the item within the result set.
  • Each tr element that contains a flag (the even-numbered table rows) will be given a name that uses the following naming structure "hiddenRecordFlag[recordNumber]", where once again recordNumber represents the index of the item within the result set.
  • Finally, each span element that represents a flag will be given a name that adheres to the following structure: "record[recordNumber]Flag", where recordNumber once again represents the index of the item within the result set.

Now that you have implemented a naming schema for the necessary components, you are ready to add the JavaScript that will handle the events accordingly. In the example, you will place the JavaScript in an external .js file and reference it from your HTML page. The JavaScript I am referring to is listed in Snippet 3.

var selectedRowId = null;

// ==========================================================
// Adds the appropriate event handlers to the flagged entries
// ==========================================================
function Initialize()
{
   var table = document.getElementById("RecordsTable");
   for (i=1; i<table.rows.length; i++)
   {
      var tableRow = table.rows[i];

      if (tableRow.id.indexOf("hiddenRecordFlag") == -1)
      {
         tableRow.onmouseover = OnFlagStaffOver;
         tableRow.onmouseout  = OnFlagStaffOut;

         var flag = document.getElementById(tableRow.id + "Flag");
         (flag != null)
         {
            flag.onmouseover = OnFlagOver;
            flag.onmouseout  = OnFlagOut;
         }
      }
   }
}

function OnFlagStaffOver()
{
   ShowFlag(this.id);
}

function OnFlagStaffOut()
{
   HideFlag(this.id);
}

function OnFlagOver()
{
   var rowId = this.id.substring(0, this.id.length-4);
   ShowFlag(rowId);
}

function OnFlagOut()
{
      var rowId = this.id.substring(0, this.id.length-4);
      HideFlag(rowId);
}

// =======================================
// Displays the flag associated with a row
// =======================================
function ShowFlag(rowId)
{
   // Determine if anything needs to be done
   if (rowId == selectedRowId)
      return;

   // Determine if another flag needs to be hidden
   if (rowId != selectedRowId)
   {
      if (selectedRowId != null)
      {
         // Alter the flag staff
         var selectedFlagStaff =
            document.getElementById(selectedRowId);
         selectedFlagStaff.className = "";

         // Hide the flag
         var selectedFlag =
            document.getElementById(selectedRowId + "Flag");
         selectedFlag.style.display = "none";
      }
   }

   // Update the style of the flag staff that is selected
   selectedRowId = rowId;
   var flagStaff = document.getElementById(rowId);
   flagStaff.className = "flagStaffHover";

   // Display the flag of the selected flag
   var flag = document.getElementById(rowId + "Flag");
   flag.style.display = "block";
}

// ====================================
// Hides the flag affiliated with a row
// ====================================
function HideFlag(rowId)
{
   // Update the style of the flag staff that has been exited
   var flagStaff = document.getElementById(rowId);
   flagStaff.className = "flagStaff";

   // Hide the flag
   var flag = document.getElementById(rowId + "Flag");
   flag.style.display = "none";

   // Reset the selectedRowId
   selectedRowId = null;
}

Snippet 3

The comments within the JavaScript explain what is occurring. One thing to note, however, is that in the Initialize function you begin at an index of 1. This may seem a bit preternatural if you are used to 0-based indexing. The reason you do this is to prevent adding event handlers to the column heading row, which would cause a JavaScript error.

To complete your implementation of your flags, you need to perform one more little task. After providing the appropriate linking to your .js file in the head section of the HTML file, you need to add a call to initialize your table from the body element in your HTML code. You can see the additions, in blue, within Snippet 4.

<html>
<head>
   <script type='text/javascript' src='flag.js'></script>

   <style type='text/css'>
      tr.gridHeader {background-color:#3891A7;font-weight:bold;}
      tr.flagStaff  {background-color:#E7DEC9;}
      tr.flagStaffHover {background-color:#3891A7;color:white;
                         cursor:pointer;}

      td.gridHeader {color:black; border-bottom:solid 2px black;}
      td.spacer {width:6px;}

      span.flag{display:none;position:absolute;width:100%;
                background-color:#3891A7;color:white;}

   </style>
</head>

<body onload='Initialize();'>
<!-- Remainder of code from the previous snippet -->
</body>
</html>

Conclusion

Congratulations! You have put together an extremely valuable decision-centric interface. There are numerous ways you could enhance this UI component, including various style enhancements and employing AJAX to populate your hidden results (flags) more efficiently. However, the purpose of this article was to provide an introduction to this complex UI component. I hope you have enjoyed this article and I hope to see this component employed on your site soon!

Downloads

About the Author

Chad Campbell (MCSD, MCTS) is a solutions developer for mid to large-sized organizations. Chad is a thought leader with Crowe Chizek in Indianapolis, Indiana. Chad specializes in Web-based solutions. He can be reached at cacampbell@crowechizek.com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date