Showing posts with label CSS. Show all posts
Showing posts with label CSS. Show all posts

Sunday, September 29, 2013

Handlebars & jQuery UI Accordion (Sort Of) Part 2

We will be picking up from where we left off from the most recent post. Our friends in "the business" have come up with a new request since we went out for celebratory pizza. This is what they want now....

"We really like what we see but...there could be a lot of items on the screen. It would be really nice if a user entered a value in a text, and the text box stayed open. This would be a good indication to the user that they made a selection...a visual cue that they performed work."

Can we do this? Yes we can. The problem with this request is that now they are asking for a behavior that was not an intended behavior for the jQuery UI accordion. At times, we are at the mercy of the business and must give them what they want. This post will demonstrate how we can do this. Nevertheless, this is a very good exercise of creating a pseudo-accordion. It looks like a jQuery UI accordion, acts like a jQuery UI accordion, but there is no actual call to the jQuery accordion.

This example will be a little different from the previous as I will be showcasing a different screen mock up. Nevertheless, it's not much of a stretch. Let's dive in an start with our model class:
public class Item
{
   public string ItemId { get; set; }
   public string ItemDescription { get; set; }
   public string PackQuantity { get; set; }
   public string PackSize { get; set; }
}

Let's write the controller:
public class PanelController : Controller
{
   public ActionResult Index()
   {
      return View();
   }

   [HttpGet]
   public object GetItemData()
   {
      List<Item> items = new List<Item>()
      {
         new Item() { ItemId = "1", ItemDescription = "Item A", PackQuantity = "", PackSize = "5" },
         new Item() { ItemId = "2", ItemDescription = "Item B", PackQuantity = "", PackSize = "10" },
         new Item() { ItemId = "3", ItemDescription = "Item C", PackQuantity = "", PackSize = "15" }
      };

      var data = items.GroupBy(x => x.ItemDescription)
                      .Select(y => new { Headers = y.Key, Contents = y.Select(z => z).ToList() });
      var json = JsonConvert.SerializeObject(data);
      return json;
   }
}
The interesting thing to note is the return type on the GetItemData method. Usually we would write return Json(data, JsonRequestBehavior.AllowGet);, above is just showing a different approach. Let's now write the view for the Index method. We will be doing this in steps, first the template, followed by the css, finally, the JavaScript.
@{
   ViewBag.Title = "Panel";
}
<br />
<div id="tmplTarget"></div>

<script id="itemsTmpl" type="text/x-handlebars-template">
   <div id="itemsContent">
      {{#each this}}
         <div>
            <h3>
               <span class="ui-icon ui-icon-plus" style="display: inline-block;" />
               <a href="#">{{this.Headers}}</a>
            </h3>
            <div class="tblDiv">
               <table class="itemsContentTbl">
                  <tr>
                     <th class="itemsContentHeader"></th>
                     <th></th>
                     <th>Pack Qty</th>
                     <th>Pack Size</th>
                  </tr>
                  {{#each this.Contents}}
                  <tr>
                     <td class="itemDescription">{{this.ItemDescription}}</td>
                     <td class="tdCkItemDesc">
                        <input type="checkbox" id="ck-{{this.ItemDescription}}"
                                  name="ck-{{this.ItemDescription}}" />
                     </td>
                     <td class="packQty" style="text-align: center">
                        <input type="text" id="txtPackQuantity-oi-{{this.ItemDescription}}
                                  name="txtPackQuantity-oi-{{this.ItemDescription}} />
                     </td>
                     <td class="packSize">{{this.PackSize}}</td>
                     <input type="hidden" id="hdnPackSize-oi-{{this.ItemDescription}}-ps-{{this.PackSize}}"
                               name="hdnPackSize-oi-{{this.ItemDescription}}-ps-{{this.PackSize}}"
                               value="{{this.PackSize}}" />
                  </tr>
                  {{/each}}
               </table>
            </div>
         </div>
      {{/each}}
   </div>
</script>

Not all of the classes will be styled, some are just used as hooks for jQuery. However, note the span tag in the template. We need all of the headers to have the plus icon when the page loads. Since we are not using the accordion we need to leverage the jQuery UI css. The ui-icon and ui-icon-plus classes will allow us to achieve this. Also, in order for the icon to be displayed correctly, the span element must be rendered as an inline-block. It is quite common when using the accordion to see the icons smashed into the text, inline-block is the solution. And again, just inlining the css for illustrative purposes.
Let's take care of the AJAX call and the template so we can see what we are working with.
<script src="~/Scripts/jquery-1.8.2.js"></script>
<script src="~/Scripts/jquery-ui-1.8.24.js"></script>
<script src="~/Scripts/handlebars.js"></script>
<script type="text/javascript">
   $(function() {
      $.ajax({
         url: '@Url.Action("GetItemData")',
         contentType: 'application/json; charset=utf-8',
         dataType: 'json',
         cache: false,
         success: function(data) {
            var source = $('#itemsTmpl').html();
            var template = Handlebars.compile(source);
            $('#tmplTarget').html(template(data));
         },
         error: function(jqXHR, textStatus, errorThrown) {
            alert(errorThrown);
         }
      });
   });
</script>

 
We'll come back to the jQuery code later. For now, let's take care of some styling.
.itemsContentTbl {
   border-collapse: collapse;
   /* center table */
   margin: 10px auto 10px auto
}

.itemsContentTbl th {
   background: #002a5f;
   color: white;
   padding-left: 20px;
   padding-right: 20px;
   border: 1px solid black;
   text-align: center;
}

.itemsContentTbl td {
   border: 1px solid black;
   text-align: center;
   vertical-align: middle;
}
Let's have a look....

 
Ok we have two issues we need to clean up. The checkbox has a border around it, which was introduced by the css class itemsContentTbl td. Also, we should center the textbox.
In the Handlebars template, we just need to add classes to the checkbox and textbox inputs. Add the classes ckItemDesc for the checkbox and txtPackQty for the textbox. The css for those classes:
.ckItemDesc {
   border: none !important;
   padding-left: 30px;
}

.txtPackQty {
   width: auto;
   margin-left: 60px;
   margin-right: 30px;
}


 
Everything else will be done with jQuery. All of the following code should be placed in the AJAX success callback. We do not want to display the item descriptions in the html table so we have to hide those.
$('.itemsContentHeader, .itemDescription').hide();

 
Style the headers for our panel/accordion.
$('h3').addClass('ui-accordion-header ui-helper-reset ui-state-default ui-corner-all');

 
Style the divs below the header.
$('.tblDiv').addClass('ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom');

 
The divs that we just styled have to be hidden upon page load so the accordion/panel appears to be closed.
$('.tblDiv').hide();
 
Now we need to take care of the checkbox. When a user enters a value into the textbox, we would like that to automatically check the check box. If a checkbox is clicked, we will use that as an indication to keep that panel open, essentially freezing it. You may say, well, if a user manually checks the checkbox and does not enter a value into the textbox, it will still freeze the panel right? Correct. This may not be ideal but a user could check it as a reminder to come back and decide if they want to enter a value into the checkbox. The checkbox is optional, things will work just as well without it. Anyway, the code:
$('input:text[id^="txtPackQuantity"]').change(function () {
   $(this).parent().prev().children().attr('checked', 'checked');
});

Right now the panel control is not responding to mouse clicks. The next part is where all the heavy lifting comes in. The toggle function in jQuery is exactly what we need to make this work. The toggle function is passed in two functions, one for the first mouse click, and the second function for the second mouse click. As we have been doing, we will be harnessing jQuery UI css classes to make all the magic happen. Since the span element in the template has the icon, that span will be the target for the toggling.
$('span').toggle(
   function() {
      $(this).addClass('ui-icon-minus');
      $(this).parent().next().removeClass('ui-accordion-header ui-helper-reset ui-state-default ui-corner-all').addClass('ui-accordion-header ui-helper-reset ui-state-default ui-state-active ui-corner-top');
      $(this).parent().next().show('slow');
   },
   function() {
      var ckBox = $(this).parent().parent().find('.ckItemDesc');
      if ($(ckBox).is(':checked')) {
         $(this).removeClass('ui-icon-plus');
         $(this).addClass('ui-icon ui-icon-minus');
         return;
      } else {
         $(this).removeClass('ui-icon-minus');
         $(this).addClass('ui-icon-plus');
         $(this).parent().next().removeClass('ui-accordion-header ui-helper-reset ui-state-default ui-state-active ui-corner-top').addClass('ui-accordion-header ui-helper-reset ui-state-default ui-corner-all');
         $(this).parent().next().hide('slow');
      }
   }
);

The first time the plus icon is clicked, we add the minus icon. Then we remove the css classes that are used when a panel is not displayed and add the css classes for when a panel is displayed/active. Finally, since that div is hidden, we just show it.

On the second click, if the checkbox is checked, we need to freeze the panel. To do that, it's just simply a matter of removing the plus icon css class and adding the minus icon css class. Using the return keyword will break out of the code.

If the checkbox is not clicked, we need to remove the minus icon css class, add the plus icon css class, remove the css classes indicating the panel is active, add the css classes indicating the panel is not active, and finally, hide that div.

Now we should be able to have multiple panels open, freeze panels, and expand collapse panels where the text box does not have any data.
 

Below is all the jQuery code together.
<link href="~/Content/themes/base/jquery.ui.all.css" rel="stylesheet" />
<script src="~/Scripts/jquery-1.8.2.js"></script>
<script src="~/Scripts/jquery-ui-1.8.24.js"></script>
<script src="~/Scripts/handlebars.js"></script>
<script type="text/javascript">
   $(function() {
      $.ajax({
         url: '@Url.Action("GetItemData")',
         contentType: 'application/json; charset=utf-8',
         dataType: 'json',
         cache: false,
         success: function(data) {
            var source = $('#itemsTmpl').html();
            var template = Handlebars.compile(source);
            $('#tmplTarget').html(template(data));

            // do not want to show content headers and descriptions
            $('.itemsContentHeader, .itemDescription').hide();

            // style the headers
            $('h3').addClass('ui-accordion-header ui-helper-reset ui-state-default ui-corner-all');

            // style divs below header
            $('.tblDiv').addClass('ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom');

            // close the divs
            $('.tblDiv').hide();

            // checking checkbox will be cue to freeze panel, check when field changes
            $('input:text[id^="txtPackQuantity"]').change(function() {
               $(this).parent().prev().children().attr('checked', 'checked');
            });

            // toggle plus minus
            $('span').toggle(
               function() {
                  $(this).addClass('ui-icon-minus');
                  $(this).parent().next().removeClass('ui-accordion-header ui-helper-reset u-state-default ui-corner-all').addClass('ui-accordion-header ui-helper-reset ui-state-default ui-state-active ui-corner-top');
                  $(this).parent().next().show('slow');
               },
               function() {
                 var ckBox = $(this).parent().parent().find('.ckItemDesc');
                 if ($(ckBox).is(':checked')) {
                   $(this).removeClass('ui-icon-plus');
                   $(this).addClass('ui-icon ui-icon-minus');
                   return;
                 } else {
                    $(this).removeClass('ui-icon-minus');
                    $(this).addClass('ui-icon-plus');
                    $(this).parent().next().removeClass('ui-accordion-header ui-helper-reset ui-state-default ui-state-active ui-corner-top').addClass('ui-accordion-header ui-helper-reset ui-state-default ui-corner-all');
                    $(this).parent().next().hide('slow');
                 }
               }
            );
         },
         error: function(jqXHR, textStatus, errorThrown) {
            alert(errorThrown);
         }
      });
   });
</script>

Monday, September 23, 2013

Handlebars & jQuery UI Accordion Part 1

This post is modeled after something I have recently done at work. Here is the rough description....

The application is an ASP.NET Web Forms application that is at least five years old. The DevExpress tool suite is used heavily throughout the site. The business wanted to make a presentation change of a grid, thing is, the DevExpress grid could not do it. I was tasked with handling this so I went with Handlebars.js and the Accordion control from jQuery UI. It was necessary to use templating as the data was dynamic. This ended up being a very nasty task due to various button click handlers and the nasty postback behavior of ASP.NET.

For this walkthrough, I'm going with ASP.NET MVC just to simplify things. Make sure to add Handlebars through NuGet to the solution. Let's start with the models we will be using.....
public class Material
{
   public string MaterialDescription { get; set; }
   public int RequestQty { get; set; }
   public int PackQty { get; set; }
   public int PackSize { get; set; }
   public int TotalApproved { get; set; }
   public int AvailableInv { get; set; }
}

I'm adding a view model to send to the client because generally, I do not like returning anonymous objects. When I can, I always like to be explicit and declare the type. The view model:
public class IndexViewModel
{
   public string Headers { get; set; }
   public IEnumerable<Material> Contents { get; set; }
}

The Index method of whatever controller you are using can be left as is. Add the view. The view will contain the target div for the accordion as well as the Handlebars template. The view:
@model IEnumerable<YourSolution.Models.IndexViewModel>
@{
   ViewBag.Title = "Handlebars Accordion";
}


<div id="target"></div>

<script id="testTmpl" type="text/x-handlebars-template">
   <div id="test">
      {{#each this}}
         <h3><a href="#">{{this.Headers}}</a><h3>
         <div>
            <table class="table">
               <tr>
                  <th>Request Qty</th>
                  <th>Pack Qty</th>
                  <th>Pack Size</th>
                  <th>Total Approved</th>
                  <th>Available Inv</th>
               </tr>
               {{#each this.Contents}}
                  <tr>
                     <td>{{this.RequestQty}}</td>
                     <td>{{this.PackQty}}</td>
                     <td>{{this.PackSize}}</td>
                     <td>{{this.TotalApproved}}</td>
                     <td>{{this.AvailableIn}}</td>
                  </tr>
               {{/each}}
            </table>
         </div>
      {{/each}}
   </div>
</script>

Handlebars is pretty slick and easy to use. To loop through a collection is fairly straightforward, just use #each and this, where this of course refers to the current item. This also shows a nested data structure and how to perform nested looping. We have two properties in the view model, Headers and contents. In the first each, we are just writing out the Headers property. To access any properties you just use dot syntax. When you want to access the nested object, just embed another each, and you will have access to the nested property/object.
Let's move on to the JavaScript. For simplicity, I am putting it in the view as well.
<script type="text/javascript">
   $(function() {
      $.ajax({
         url: '/AccordionTmpl/GetData',
         type: 'GET',
         contentType: 'application/json; charset=utf-8',
         dataType: 'json',
         success: function(data) {
            var source = $('#testTmpl')html();
            var template = Handlebars.compile(source);
            $('#target').html(template(data));

            $('#test').accordion({
               active: false,
               header: 'h3',
               collapsible: true,
               icons: {
                  "header": "ui-icon-plus",
                  "headerSelected": "ui-icon-minus"
               }
            });
         },
         error: function(response) {
            alert(response.responseText);
         }
      });
   });
</script>

If you've done any templating this should look familiar. Grab the html of the template, compile it, feed the data into the template, take the html and add it to the target. The accordion configuration is pretty basic as well.
There is one css class in our template. We will add some styling to the table, headers and rows.
.tbl {
   border-collapse: collapse;
   /* center the table */
   margin: 10px auto 10px auto;
}

.tbl th {
   background: #002a5f;
   color: white;
   padding-left: 20px;
   padding-right: 20px;
   border: 1px solid black;
}

.tbl td {
   border: 1px solid black;
   text-align: center;
}
We have to go back to the server and write the method to return data:
[HttpGet]
public ActionResult GetData()
{
   List<Material> materials = new List<Material>()
   {
      new Material(){MaterialDescription = "Big Bang Theory Sheldon Shirt", RequestQty = 3, PackQty = 3, PackSize = 3, TotalApproved = 3, AvailableInv = 6},
      new Material(){MaterialDescription = "Big Bang Theory Leonard Shirt", RequestQty = 2, PackQty = 2, PackSize = 2, TotalApproved = 2, AvailableInv = 7},
      new Material(){MaterialDescription = "Big Bang Theory Penny Shirt", RequestQty = 1, PackQty = 1, PackSize = 1, TotalApproved = 1, AvailableInv = 9},
      new Material(){MaterialDescription = "Big Bang Theory Kripke Shirt", RequestQty = 1, PackQty = 1, PackSize = 1, TotalApproved = 1, AvailableInv = 10},
   };

   IEnumerable<IndexViewModel> data = materials
                                            .GroupBy(x => x.MaterialDescription)
                                            .Select(y => new IndexViewModel()
                                            {
                                               Headers = y.Key, 
                                               Contents = y.Select(z => z).ToList()
                                            });

   return Json(data, JsonRequestBehavior.AllowGet);
}

Let's run and see what we got...
 

Not too shabby. We did some good work, should probably reward ourselves with some pizza.

*One hour later*

It seems our friends in the business saw what we did and they have a request. Since the list of items can be quite a few, they would like it if more than one panel could stay open so a user can remember where they were.

This can be done even though the accordion was not designed to do this. The solution, initially, seems very simple. The answer lies in the structure of the accordion. You have to change the target of the accordion. If you look at the documentation for the accordion, the structure of your html has to look like this:

<div id="accordion">
   <h3>First Header</h3>
   <div>First Content Panel</div>
   <h3>Second Header</h3>
   <div>Second Content Panel</div>
</div>

Our structure is different but still worked. We are going to change the target for the accordion from $('#test) to $('#test > div'). Our target is now the child div of the test div. The only other change we need to make is to add some css to the anchor tag, right after the accordion declaration. Make the changes:
$('#test > div').accordion({
   active: false,
   header: 'h3',
   collapsible: true,
   icons: {
      "header": "ui-icon-plus",
      "headerSelected": "ui-icon-minus"
   }
});

$('a').css({ "display": "inline-block" });

Now to run...
 
Looks good! The css that was added to the anchor tag is very, very important. If you are ever using the accordion and your icons are mashed in with the text, applying that css to the anchor will solve it. Another solution is the following:
<h3>
   <span style="display: inline-block;" />
   <a href="#">{{this.Headers}}</a>
</h3>

Really the only difference is to what element you apply the css. Again, just inlining the css for illustrative purposes.

In the next post, we will revisit this scenario as our friends from the business have another request.

Monday, July 15, 2013

Fly Out Menu

Navigation can grow quickly. We have become quite accustomed to dropdown lists and fly out menus for some time. Many solutions can be quite complicated and cumbersome. This post will examine building a fly out navigation menu....with no jQuery.

First step, laying out the HTML structure:
<nav>
   <ul class="top-nav">
      <li><a href="#">Home</a></li>
      <li><a href="#">About</a>
         <ul class="sub-nav">
            <li><a href="#">First</a></li>
            <li><a href="#">Second</a></li>
            <li><a href="#">Third</a></li>
            <li><a href="#">Fourth</a></li>
         </ul>
      &lt/li>
      <li><a href="#">Projects</a>
         <ul class="sub-nav">
            <li>a href="#">Project 1</a>
               <ul class="sub-nav1">
                  <li><a href="#">Project 1.1</a></li>
                  <li><a href="#">Project 1.2</a></li>
                  <li><a href="#">Project 1.3</a></li>
                  <li><a href="#">Project 1.4</a></li>
               </ul>
            </li>
            <li>a href="#">Project 2</a>
               <ul class="sub-nav1">
                  <li><a href="#">Project 2.1</a></li>
                  <li><a href="#">Project 2.2</a></li>
                  <li><a href="#">Project 2.3</a></li>
                  <li><a href="#">Project 2.4</a></li>
               </ul>
            </li>
            <li><a href="#">Project 3</a></li>
            <li><a href="#">Project 4</a></li>
         </ul>
      </li>
      <li><a href="#">Contact Us</a></li>
   </ul>
</nav>

Here is what the structure looks like with no CSS applied:




Now to start applying some style. I will include some screenshots so we can see how the menu starts to take shape.

First thing we are going to do is set the color for the background, black:
nav {
   margin-top: 20px;
   background: black;
}



Let's add some color to the hyperlinks.

a:link {
   color: silver;
}


The next block of css will be going after all of the list items in the top-nav class.
.top-nav > li {
   display: inline-block;
   width: 20%;
   position: relative;   
}
Ugh. Looks terrible. What happened is that display: inline-block took all the li elements and rendered them on the same line. This will help with aligning things left to right. Something else to note, the nested ul items, by default a browser will indent those items by 40px. Setting the position to relative sets the positioning context for the 2nd level menu.

Next we will go after all the anchor elements in the list items of the top-nav class.
.top-nav > li a {
   text-decoration: none;
   font-size: 24px;
   line-height: 1.5em;
   text-align: center;
   display: block;  
}


Text-decoration: none removed all the underlines of the anchor elements as well as the bullets. Line-height will center the text vertically. Text-align: center will center the text horizontally. Display: block will render the anchors as block level elements.

Next will be some css that targets hovering over the anchor items for the top-nav, sub-nav, and sub-nav1 classes. The text will simply turn red (look at the Clients tab) and the background of the fly out items (all the Sub Items) will be set to black.
.top-nav > li > a:hover,
.sub-nav > li > a:hover,
.sub-nav1 > li > a:hover {
   color: red;
   background: black;
}


Things will start to take shape with the next block of css. We will target all the sub-lists. The screenshot will not show much due to the sub-lists being hidden.
.sub-nav {
   position: absolute;
   width: 100%;
   visibility: hidden;
   padding-left: 0;
   padding-right: 0;
   margin-left: 0;
   margin-right: 0;
}


Definitely an improvement, starting to look like a proper navigation menu. Visibility: hidden is obviously hiding the lists. Earlier I mentioned that by default a browser will indent lists by 40px, setting the padding and margin to 0 for left will correct that. Setting the width to 100% will make the sub-items the same with as the parent list items. Very, very important...position: absolute will position the ul items directly under the list items. For example, the ul items First, Second, Third, and Fourth will be placed directly under the About item.

We have content that is hidden, let's start showing it. The css below will show sub-nav content when list items of the top-nav class are hovered.
.top-nav li:hover .sub-nav {
   visibility: visible;
}


We have some color and positioning to take care. The next css block will target the list items of the sub-nav class. This code will align the list items nicely and set up the fly out items.
.sub-nav > li {
   display: block;
   width: 100%;
   position: relative;
   background: black;
}


Boom! Look at that, all nice and pretty. Display: block renders each item as a block. Setting the width to 100% will size it to the parent. Setting position to relative is what will assist in the fly out items. And of course, setting the background to black will set the background of the list items to black.

The Clients tab is still a mess. The .sub-nav > li code did help line things up. Let's take a look at it.


The Item1-4 tabs are lined up. The css block below will assist in hiding and positioning the nested items.
.sub-nav1 {
   position: absolute;
   width: 100%;
   visibility: hidden;
   padding-left: 0;
   padding-right: 0;
   margin-left: 0;
   margin-right: 0;
}


The sub-items that will fly out to the right are hidden. Width is set to 100% will size to the parent so things line up. Again, setting the padding and margin left and right will remove the 40px the browser inserts for list items.

Remember the .sub-nav > li css code? It has position: relative set. This is extremely important for the flyouts. The .sub-nav1 code has position set to absolute. The parent is relative and the child is absolute. This limits positioning. An child of a relative parent can be positioned absolutely within that block. Basically, if relative positioning was not set on the parent, setting the child to absolute would cause a drastic change in position, it would not end up where we want it to be. (My head still hurts from that)

Since we are at the meat of the issue, let's break this next part down line by line so we get a really good idea of what is going on. We will hover over the Item 2 list item and make it visible.
.sub-nav li:hover .sub-nav1 {
   visibility: visible;
}


Obviously the positioning is incorrect. If you hover over Item 1 you will not see the sub-items at all, since they are being covered by the black background.

We need to get all the sub-items moved over to the right. To do this, add left: 100%. This will push the items over to the right.
.sub-nav li:hover .sub-nav1 {
   visibility: visible;
   left: 100%;
}


Very nice. The fly out list items are one level down from where we want them to be, we have to move them up. To accomplish this, set top to 0%.
.sub-nav li:hover .sub-nav1 {
   visibility: visible;
   left: 100%;
   top: 0%;
   position: absolute;
}


Looking good! All that remains is to style the list items that are flying out, those are in the .sub-nav1 class. As we have seen many times, set the width to 100% so it is the same size as its parent, and display the list items as blocks. Lastly, set the background to black.
.sub-nav1 > li {
   display: block;
   width: 100%;
   background: black;
}

 



Once you get this far, it's just a pattern now. You could keep on building the navigation menu out to whatever you need.

Friday, June 28, 2013

HTML Tables: Splitting Cells

Today is an examination of building a table that is not so straightforward. At times, you may need to split header rows or table cells to display data in a certain way. This post will tackle how to create such a table. I am not a CSS wizard by any means but the styling is reasonably close.

Normally I like to provide explanations for what is going on. Given that this is a rather lengthy post, I am going to omit that this time. This should be looked at as a template of how to build a more complex table. If someone actually wants to hear the rational, just let me know.

Let's break this in to several chunks just to see the code a little bit more clearly. Of course, all of the code will be in a table tag. The style used for the table is width 977px and margin-top 10px. First, let's look at the header.

   <tr class="trHeader">
      <td class="tdNoStyle"></td>
      <td rowspan="2" style="width: 157px;">Pizza</td>
      <td rowspan="3" style="width: 157px;">Assetts You May Give</td>
      <td colspan="2" style="width: 269px;">Your bake if you eat</td>
      <td rowspan="3" style="width: 253px;">Expectations & Exceptions</td>
   </tr>
   <tr class="trHeader">
      <td class="tdNoStyle"></td>
      <td rowspan="2" style="width: 137px; vertical-align: bottom">In-House</td>
      <td rowspan="2" style="width: 131px; vertical-align: bottom">Out-Of-House</td>
   </tr>
   <tr class="trHeader">
      <td class="tdNoStyle"></td>
      <td rowspan="2" style="width: 157px;">Planned Event</td>
   </tr>
   <tr class="trHeader">
      <td class="tdNoStyle"></td>
      <td style="width: 293px;"></td>
      <td style="width: 137px;">Provisions</td>
      <td style="width: 131px;">Provisions</td>
      <td style="width: 253px;"></td>
   </tr>


The first section:

   <tr>
      <td></td>
      <td class="col1bkGrnd">If you meant pizza</td>
      <td class="rowStyle1">Pizza/Sandwich options and services</td>
      <td class="rowStyle1">$20 slice*</td>
      <td class="rowStyle1">30% sandwich</td>
      <td class="rowStyle1">-----none-----</td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd">pizza, sandwich</td>
      <td class="rowStyle5">Pizza/Sandwich options and services</td>
      <td class="rowStyle5">20% insurance</td>
      <td class="rowStyle5">30% insurance</td>
      <td class="rowStyle5">-----none-----</td>
   </tr>
   <tr>
      <td></td>
      <td rowspan="2" class="col1bkGrnd">toppings or veggies</td>
      <td class="rowStyle4"></td>
      <td class="rowStyle4"></td>
      <td class="rowStyle4"></td>
      <td class="rowStyle4"></td>
   </tr>
   <tr>
      <td></td>
      <td class="rowStyle1">Pizza/Sandwich options and services</td>
      <td class="rowStyle1">$20 slice*</td>
      <td class="rowStyle1">30% sandwich</td>
      <td class="rowStyle1">-----none-----</td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd, col1BrdrBtm">needs</td>
      <td class="rowStyle4">Pizza/Sandwich options and services</td>
      <td class="rowStyle4">20% insurance</td>
      <td class="rowStyle4">30% insurance</td>
      <td class="rowStyle4">-----none-----</td>
   </tr>



The second section:

   <tr>
      <td></td>
      <td class="col1bkGrnd"></td>
      <td class="rowStyle1">Premium slices and sandwiches are highly recommended</td>
      <td class="rowStyle1">$20 slice*</td>
      <td class="rowStyle1">30% insurance</td>
      <td class="rowStyle1">-----none-----</td>
   </tr>
   <tr>
      <td></td>
      <td rowspan="2" class="col1bkGrnd">For a private visit</td>
      <td class="rowStyle4">Special visit</td>
      <td class="rowStyle4">$20 slice*</td>
      <td class="rowStyle4">30% insurance</td>
      <td class="rowStyle4">-----none-----</td>
   </tr>
   <tr>
      <td></td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3">Artichoke: Not</td>
      <td class="rowStyle3">Artichoke: Not</td>
      <td class="rowStyle3"></td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd">and private tour</td>
      <td class="rowStyle3">Special visit and tour</td>
      <td class="rowStyle3">included</td>
      <td class="rowStyle3">included</td>
      <td class="rowStyle3">-----none-----</td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd">or show</td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3">Pepperoni: $20</td>
      <td class="rowStyle3">Pepperoni: 30%</td>
      <td class="rowStyle3"></td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd"></td>
      <td class="rowStyle1"></td>
      <td class="rowStyle1">slice*</td>
      <td class="rowStyle1">insurance</td>
      <td class="rowStyle1"></td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd"></td>
      <td class="rowStyle4">Preventive damage/death/dismemberment</td>
      <td class="rowStyle4">No charge</td>
      <td class="rowStyle4">30% insurance</td>
      <td class="rowStyle4">-----none-----</td>
   </tr>




The third section:

   <tr>
      <td></td>
      <td rowspan="2" class="col1bkGrnd">If you more pizza to</td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3">Preferred: $10</td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3"></td>
   </tr>
   <tr>
      <td></td>
      <td class="rowStyle3"></td>
      <td rowspan="2" class="rowStyle3">slice* at retail </td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3"></td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd">treat your addiction</td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3">30% insurance</td>
      <td rowspan="2" class="rowStyle3">31 day supply retail/93 day supply</td>
   </tr>
   <tr>
      <td></td>
      <td rowspan="2" class="col1bkGrnd">condition</td>
      <td rowspan="2" class="rowStyle3">Premium pizza</td>
      <td class="rowStyle3">slice* at mail</td>
      <td rowspan="2" class="rowStyle3">at retail, mail not</td>
   </tr>
   <tr>
      <td></td>
      <td rowspan="2" class="rowStyle3">Non-preferred: $45</td>
      <td rowspan="2" class="rowStyle3">mail order</td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd"></td>
      <td class="rowStyle3"></td>
      <td rowspan="2" class="rowStyle3">covered</td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd"></td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3">slice* at retail, $90</td>
      <td class="rowStyle3"></td>
   </tr>
   <tr>
      <td></td>
      <td rowspan="2" class="col1bkGrnd">More information</td>
      <td class="rowStyle1"></td>
      <td class="rowStyle1">slice* at mail</td>
      <td class="rowStyle1"></td>
      <td class="rowStyle1"></td>
   </tr>
   <tr>
      <td class="rowStyle2"></td>
      <td class="rowStyle2"></td>
      <td class="rowStyle2"></td>
      <td class="rowStyle2"></td>
      <td class="rowStyle2"></td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd">about pizza</td>
      <td rowspan="2" class="rowStyle2">Preferred pizza</td>
      <td class="rowStyle2">$15 slice at retail,</td>
      <td class="rowStyle2"></td>
      <td class="rowStyle2"></td>
   </tr>
   <tr>
      <td></td>
      <td rowspan="2" class="col1bkGrnd">pizza addiction</td>
      <td rowspan="2" class="rowStyle2">$30 slice* at mail</td>
      <td class="rowStyle2"></td>
      <td class="rowStyle2"></td>
   </tr>
   <tr>
      <td class="rowStyle2"></td>
      <td class="rowStyle2"></td>
      <td class="rowStyle2"></td>
      <td class="rowStyle2"></td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd">available at www</td>
      <td class="rowStyle2"></td>
      <td class="rowStyle2"></td>
      <td class="rowStyle2"></td>
      <td class="rowStyle2"></td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd">.pizzaproblem.com</td>
      <td class="rowStyle4"></td>
      <td class="rowStyle4"></td>
      <td class="rowStyle4"></td>
      <td class="rowStyle4"></td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd">/public/pizza</td>
      <td class="rowStyle3"></td>
      <td rowspan="2" class="rowStyle3">$45 slice* at retail</td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3"></td>
   </tr>
   <tr>
      <td></td>
      <td rowspan="2" class="bol1bkGrnd">addiction/help</td>
      <td rowpsan="2" class="rowStyle3">Non-Preferred pizza</td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3"></td>
   </tr>
   <tr>
      <td></td>
      <td rowspan="2" class="rowStyle3">$10 copay* at mail</td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3"></td>
   </tr>
   <tr>
      <td></td>
      <td rowspan="2" class="rowStyle3">/morepizza/index.</td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3"></td>
   </tr>
   <tr>
      <td></td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3"></td>
      <td class="rowStyle3"></td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd">html.</td>
      <td class="rowStyle1"></td>
      <td class="rowStyle1"></td>
      <td class="rowStyle1"></td>
      <td class="rowStyle1"></td>
   </tr>
   <tr>
      <td></td>
      <td class="col1bkGrnd"></td>
      <td class="rowStyle2"></td>
      <td class="rowStyle2"></td>
      <td class="rowStyle2">30% insurance</td>
      <td rowspan="2" class="rowStyle2">$400 minimum order</td>
   </tr>
   <tr>
     <td></td>
     <td class="col1bkGrnd"></td>
     <td rowspan="2" class="rowStyle2">Specialty pizza</td>
     <td rowspan="2" class="rowStyle2">20% insurance</td>
     <td rowspan="2" class="rowStyle2">at retail, mail not</td>
   </tr>
   <tr>
     <td></td>
     <td class="col1bkGrnd"></td>
     <td rowspan="2" class="rowStyle4">per month</td>
   </tr>
   <tr>
     <td></td>
     <td class="col1bkGrnd, col1BrdrBtm"></td>
     <td class="rowStyle4"></td>
     <td class="rowStyle4"></td>
     <td rowspan="2" class="rowStyle4">covered</td>
   </tr>




And the CSS classes:

.trHeader {
   background: #A9A9A9;
}

.thHeader > .tdNoStyle {
   border: none;
   background-color: rgb(239, 238, 239) !important;
}

.trHeader > td {
   border-left: #C0C0C0 1px solid;
   border-right: #C0C0C0 1px solid;
   padding-left: 5px;
}

.btnBorderHeader {
   border-bottom: #C0C0C0 1px solid;
}

.col1bkGrnd {
   background: #C0E8FB;
   padding-left: 5px;
   border-left: #70AFD9 1px solid;
   border-right: #70AFD9 1px solid;
}

.col1BrdrBtm {
    background: #C0E8FB;
    padding-left: 5px;
    border-left: #70AFD9 1px solid;
    border-right: #70AFD9 1px solid;
    border-bottom: #C0E8FB 1px solid;
    border-bottom: #70AFD9 1px solid;
}

.rowStyle1 {
    border-right: #70AFD9 1px solid;
    border-bottom: #70AFD9 1px solid;
    background: #EFF9FF;
    padding-left: 5px;
}

.rowStyle2 {
    border-right: #70AFD9 1px solid;
    vertical-align: bottom;
    padding-left: 5px;
}

.rowStyle3 {
    background: #EFF9FF;
    border-right: #70AFD9 1px solid;
    padding-left: 5px;
}

.rowStyle4 {
    border-bottom: #70AFD9 1px solid;
    border-right: #70AFD9 1px solid;
    padding-left: 5px;
}

.rowStyle5 {
    border-right: #70AFD9 1px solid;
    padding-left: 5px;
}