Creating A Modern Web Application - Part 2 (Working with HTML5 and CSS3)

28. October 2010 17:36

This is Part 2 in a series on creating a web application utilizing modern techniques in order to deliver a fast, cohesive site. The index for this series can be found at the end of part 1 in this series.

file structure

As you can see on the left, we are starting out with a fairly simple file structure. In order to keep things simple, we are going to assume that this is going to be run from the root of the website.

The first thing I'd like to do is point out the PIE.htc file in the root directory. This comes from the CSS3PIE project and will be used to aid IE6-8 in rendering CSS3 features such as rounded borders and background gradients. There isn't full support for every feature of CSS3, and there are a couple of quirks. However, this is still a far better use of resources to work around these minor IEOLD issues rather than having the additional images and markup needed to do rounded corners and background gradients via other, more traditional methods.

Second, I'd like to point out that I'm starting off with two JavaScript files in place. First is the wonderful html5shiv which adds support for HTML5 tags in IE6-8. The second is css_browser_selector.js, which adds specific classes to the HTML (documentElement) element in order to write clean CSS rules when possible. This functionality is only useful when JavaScript is enabled, so it should be used sparingly. As IE is still the browser king, with several quirky variations on rendering, we'll also be adding in some conditional elements in order to target specific IE versions in our CSS. The other JavaScript file in the head

At this point I am going to point out that I've defined three stylesheets. I've created a general main.css as well as the addition of a main-ienew.css and main-ieold.css. Where the ienew variant will be fore IE9+ and the ieold will target IE6-8 specifically. The main reason for this is because of some current quirkiness in IE9 beta where it will load PIE.htc behavior on elements despite being set not do to so.

example: #ieold .className { behavior: url(/PIE.htc); }

At this point, I'm going to point out the actual markup of our starting page, then go over the CSS that will be in place to support this. This is a demonstration of creating the rounded corners and background gradients only. Other operations will be covered in future articles.

<!DOCTYPE html>
<html>
<head>
  <!-- Meta Data -->
  <meta http-equiv="X-UA-Compatible" content="IE=99,chrome=1" />
  <meta name="description" content="" />
  <meta name="keywords" content="" />
  <link rel="shortcut icon" type="image/ico" href="/favicon.ico" />

  <title>Part 2 - Creating A Modern Web Application</title>

  <!-- Inject browser classes, and add support for HTML5 tags to IEOLD -->
  <script src="assets/scripts/browser-extensions/html5shiv.js"></script>
  <script src="assets/scripts/browser-extensions/css_browser_selector.js"></script>

  <!-- core css markup -->
  <link rel="stylesheet" type="text/css" href="assets/styles/common/main.css" />
  <!--[if gte IE 9]><link rel="stylesheet" type="text/css" href="assets/styles/common/main-ienew.css" /><![endif]-->
  <!--[if lt IE 9]><link rel="stylesheet" type="text/css" href="assets/styles/common/main-ieold.css" /><![endif]-->

  <!-- print css markup -->
  <link rel="stylesheet" type="text/css" media="print" href="assets/styles/common/main-print.css" />  
</head>

Above you see the content through the HEAD section of the document. We first start with the HTML5 DOCTYPE, which in essense tells the browser to use whatever the newest/current rendering engine available has to offer. Next we include some meta elements, the first of which X-UA-Compatible. As of Internet Explorer version 8, the rendering engine for 7 and 8 are included, unfortunately IE6 is not. I suggest using IE Tester, or a stand-alone installer for older IE version testing. The X-UA-Compatible meta attribute can set the IE renderer to be used and is set to 99 in our example to future-proof things. If you come across issues with IE9 or later, this can be helpful to force the older rendering until specific issues can be worked out. You will also notice the chrome=1 portion; which will tell IE browsers with the Chrome Frame plugin installed to utilize the Chrome rendering engine where available. I personally consider the Webkit rendering engine along with the V8 JavaScript engine the gold standard to view against. This may change in the future, but for now, how it renders in Webkit (Chrome/Safari) should be considered how it *should* be rendering. This will give you the least resistance in adjusting for non-compliant browsers.

After the typical meta elements for keywords, description and a link to the favicon. The regular TITLE element is encluded. It's worth noting that within the TITLE element, you should have your specific section of the site first, followed by more generic information. In this case, I'm starting with the specific page's name, followed by the title of the site/series. This can help will SEO, as having the same content in every title dilutes the value of that title across a site.

The first scripts we include are those that *must* be included in the head in order to function properly, and before any stylesheets are loaded. This will reduce the impact of said scripts on the css that comes next. After the main.css, I am including some conditional scripts for IENEW and IEOLD, where IEOLD is anything prior to version 9 (our support target is 6-9), and IENEW is defined as anything from version 9 on (which supports HTML5). After the general stylesheets we're including a css to establish print adjustments. It's important to establish a print media stylesheet that reduces the additional clutter such as the header, footer and page margins in order for the printing experience to be better.

On to the rest of the html file...

<body>
<!--[if IE 6]><div id="ie6" class="ie"><![endif]-->
<!--[if IE 7]><div id="ie7" class="ie"><![endif]-->
<!--[if IE 8]><div id="ie8" class="ie"><![endif]-->
<!--[if IE 9]><div id="ie9" class="ie"><![endif]-->
<!--[if lt IE 9]><div id="ieold"><![endif]-->
<!--[if gte IE 9]><div id="ienew"><![endif]-->
<![if !IE]><div id="noie"><![endif]>

  <article class="grid_4">
    <hgroup>
      <h2>Article/Section Title Goes Here</h2>
    </hgroup>
    <details>
      <summary>This section has some interesting content.</summary>
      <div>
        This section has some content that goes beyond the summary.
      </div>
    </details>
  </article>

<!--[if IE 6]></div><![endif]-->
<!--[if IE 7]></div><![endif]-->
<!--[if IE 8]></div><![endif]-->
<!--[if IE 9]></div><![endif]-->
<!--[if lt IE 9]></div><![endif]-->
<!--[if gte IE 9]></div><![endif]-->
<![if !IE]></div><![endif]>
</body>
</html>

Within the BODY element, the first thing added is a number of DIV elements surrounded by IE's conditional comments. These comments allow for the CSS to target a specific version of IE, or even a non IE browser without the need for JavaScript (as added by the css_browser_selector.js), which can improve the initial rendering. It should be noted that we won't be avoiding JavaScript and progressive enhancement, but one should be mindful of the rendering of a page without JavaScript as this can help with visually impaired users as well as the initial load impression of a given page/site.

Within the browser elements, we see some very semantic markup. An ARTICLE element is a container, which has an HGROUP (Header Group) element followed by a DETAILS element. The DETAILS element's first child is a SUMMARY element which contains the relative information regarding the rest of the DETAILS section. This can be thought of as similar to the relationship of a LEGEND element inside of a FIELDSET. Though the SUMMARY element is meant to be used as an inline element, we'll be displaying it as a block element. These tags may seem very blog oriented, and in a way they are. However, they do make for some very natural containers, as well as being far shorter than adding class names to meaningless nested div tags. There is also a SECTION element that's been added which can be used to contain multiple ARTICLE tags for example. The use of these tags at a higher level allow for very simple CSS rules, and can minimize the risk of a conflict in structure for nested tags from external resources or controls down the road.

Finally we get into the .css files. I'll be stepping through the relevant portions, though you'll be able to download the full demonstration as well as view the demo page online.

h1, h2, h3, h4, h5, h6 {
  margin:0;
  border:0;
  padding:0;  
}

/*sectioning content, (todo: use css grid) */
.grid_4 {
  display:block;
  position: relative;
  width: 400px;
  z-index: 100;
}

First, I reset the margin/border/padding for Heading elements. The next article will have a more complete reset css attached, along with some @font-face declarations to ensure a consistant rendering. Next is the .grid_4 declaration which will be replaced with a generated grid 12 css in the next article as well.

/*section header, gradient with rounded top-left and top-right border*/
hgroup {
  display:block;
  color: #333333;
  padding: 0.5em 1em 0.5em 1em;
  border: 1px solid #999;
  -webkit-border-radius: 0.6em 0.6em 0 0;
  -moz-border-radius: 0.6em 0.6em 0 0;
  border-radius: 0.6em 0.6em 0 0;
  background: #dddddd;
  background: -webkit-gradient(linear,left top,left bottom,color-stop(0.2,#eeeeee),color-stop(0.8,#cccccc));
  background: -moz-linear-gradient(#eeeeee, #cccccc);
  background: linear-gradient(#eeeeee, #cccccc);
  -pie-background: linear-gradient(#eeeeee, #cccccc);
}

Here is where we start digging into the meat of this article. Within the HGROUP we specify the various implementations of border-radius as this attribute is only recently being formalized by the W3C, webkit (Chrome & Safari) and Mozilla (Firefox) browsers created their own vender specific css attributes, so we start with them. They all follow the same format allowing you to specify each corner's value in a clockwise fashion starting with the TOP-LEFT position.

After the border-radius is defined, the background is then specified. How this is done is to first specify a background color that will be used as a fallback value. After this, we specify the vendor specific implementations and finally fallback to a common CSS3 implementation. You can find quite a bit of information on CSS gradients in this article. It's worth noting that the linked article has a few things that are at least mis-represented in regards to IE9, I'll discuss these in more detail when we reach part 5 in this series.

For now I'll note that the -pie-background is required for the CSS3PIE implementation for IEOLD (6-8) and that the z-index for the grid_4 is also related to a quirk in using PIE.htc. You can see the contents of the main-ieold.css file below.

hgroup, details {
  behavior: url(/PIE.htc);
}

rendering preview

Here we have a very short, very simple statement essentially telling the browser to apply the PIE.htc component to the HGROUP and DETAILS elements (currently the only ones using rounded corners or background-gradients. Next, let's take a quick look at the main-ienew.css.

As you can see; the effect is rather nice, you can modify this example for your specific needs. It's definitely much lighter on resources and download speeds by not having to rely on heavy markup and images to accomplish this effect. However, there is one caveat with IE9 currently.

hgroup {
  -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#eeeeee', endColorstr='#cccccc')";
}

details {
  -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#ffffff', endColorstr='#eeeeee')";
}

rendering preview IE9

IE9 supports the rounded corners internally, but the background gradients require falling back to utilizing an ActiveX based filter control via the -ms-filter property. We didn't add this into the main css as we wanted to avoid the potential for IE8 to interpret this attribute. IE9 seems to have a rendering bug where in some cases it will render the background gradient outside of the containing border (with a border-radius) as can be seen to the left.


101 reasons why Java is NOT better than .Net in 2010

27. May 2010 10:19

Okay, I'm getting really tired of seeing posts like this one... So I figured I'd take some time to debunk the thing.

More...

Tags: , , ,

HttpContext Items Collection

19. April 2010 11:07

Just a short little post, I do intend to followup my last post with a post with code on combining/minifying your JavaScript and CSS, this just caught my attention, and wanted to mention it. 

In the process of doing some technical screenings, it is really suprising how many people don't understand or even know about the HttpContext Items Collection in ASP.Net.  More...

Compressing Bytes In .Net

20. August 2009 10:57

Okay, this started when I wanted to store a bit of JSON that gets rendered into the ASP.Net cache.  Each entry would be unique to a given user, and I wanted to save a little memory, each entry would be about 8KB in size.  I looked into this and found the System.IO.Compression namespace that is available in .Net 3.5.  I created a small class with some helper extensions to be able to compress/decompress an array of byte, and handle to/from a native string.  Here's what I came up with.


CompressionExtensions.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.Compression;

namespace Sample
{
    public static class CompressionExtensions
    {
        const int KB = 1024;

        public static byte[] Compress(this byte[] input)
        {
            using (var ms = new MemoryStream(input.Length + 8))
            {
                ms.Write(BitConverter.GetBytes(input.Length), 0, 4); //record original length

                if (input.Length < KB)
                {
                    //if less than 1KB, don't compress original
                    ms.Write(input, 0, input.Length);
                    return ms.ToArray().Take(input.Length + 4).ToArray();
                }

                //compress the original input
                using (var ds = new DeflateStream(ms, CompressionMode.Compress))
                {
                    //send the original input to the compressed stream
                    ds.Write(input, 0, input.Length);

                    //add null padding. the decompress gets wonky without it.
                    ds.Write(new byte[4], 0, 4);
                    ds.Flush();
                    ms.Flush();
                    return ms.ToArray();
                }
            }
        }

        public static byte[] Decompress(this byte[] input)
        {
            if (input == null || input.Length < 4)
                throw new ApplicationException("Invalid compressed bytes, no length header.");
        
            var bitlen = BitConverter.ToInt32(input, 0);

            //if less than 1KB, return the non-compressed original
            if (bitlen < KB) return input.Skip(4).ToArray();

            var buffer = new byte[bitlen];
            using (var ms = new MemoryStream(input.Length - 4))
            {
                ms.Write(input, 4, input.Length - 4);
                ms.Flush();
                ms.Seek(0, SeekOrigin.Begin);
                using (var ds = new DeflateStream(ms, CompressionMode.Decompress))
                {
                    //this always returns a -1 for the first read
                    //    subsequent reads will work properly
                    ds.ReadByte();

                    //read the input stream into the return buffer
                    if (bitlen > ds.Read(buffer, 0, bitlen))
                        throw new ApplicationException("Invalid compressed bytes.");
                }
            }

            //only return the number of bytes originally saved, trim excess
            return buffer;
        }

        public static byte[] ToCompressedBytes(this string input)
        {
            return Encoding.UTF8.GetBytes(input).Compress();
        }

        public static string ToDecompressedString(this byte[] b)
        {
            return Encoding.UTF8.GetString(b.Decompress());
        }
    }
}

I found that without adding some padding of at least two null bytes, the decompression of the data would sometimes give me an invalid final byte. I also push in a 4-byte length header for a sanity check. This also helps me to not compress anything under 1KB in size, where compression really doesn't work very well.

The conversion of the string to UTF8 helps more with strings that will mostly contain values within the ASCII character set. It is worth noting that if there are mostly multibyte characters, the byte array itself could get bloated. For my usage, I'm getting an 8KB string down to around 2KB, you could extend this to support UTF8/Unicode encoding by appending a character to the end before compressing. This may be useful if the string is >2KB and more than 1/3 is multibyte characters, this would take a little more CPU time to inspect the input string in this case.

Your milage may vary. Void where prohibited. Quantities limited. Some restrictions may apply. Batteries not included.

Tags: ,

Apollo.Common in Codeplex

19. December 2008 04:53

The past few weeks, I've been working on Apollo.Common, which is meant to provide some easier to use functionality to enterprise applications.  A lot of this functionality, and more is provided by the MS PnP team's Microsoft Enterprise Library.  However, ent-lib tends to be overly complex, difficult to use, and require a lot of customization before you can get moving.  I want Apollo.Common to be easy to use, implement and deploy with a minimal learning curve. More...

Refactoring

19. November 2008 19:29

Okay, there are two main reasons to refactor.  The first is clarity of code.  Since we have a working base, we can now concentrate on making our logic readable, breaking major blocks of code into separate methods, as well as possibly change how certain calls work.  The second major reason to refactor is for performance or scale.  One needs to first realize that scale and performance aren't always the same thing.  Scaling is about consistency, where performance is about speed.  The two often have the same result, but not always. More...

Tags:

MySite - Part 2 - Theming Support

13. November 2008 14:55

Okay, I lied.  I really wanted to figure out how to get theming to work the way I wanted. As you can see to the right, I created a "Theme" folder, where I have a "Default" and in this case a "Red" theme.  Under the theme's folder, I moved the Content, and Views folders.  My goal was to allow for a fallback to using a View from the Default theme, but still allow for the appropriate masterpage to be applied from the theme's folder if one existed.  In this case, you can see that there is an Index view for the Home controller within the Default theme, but there is no matching view in the Red theme. More...

Tags: , , ,

MySite - Part 1

11. November 2008 22:14

My goal is to create a personal website application that implements the features needed, and wanted for someone who wants to create a personal site that extends beyond just a blogging engine.  I've been wanting to work with ASP.Net MVC and some other technologies, so I'll be focusing on using them in MySite. More...

Tags: , , ,

The Data Hive

11. November 2008 00:19

I've had some ideas bouncing around in the back of my head for a few days now.  One is that when using an application, specifically a large-scale web application, having a traditional RDBMS backend is a point of limitation in terms of scale.  RDBMS systems are really good for being able to aggregate data, and create reporting interfaces, but it isn't so great from being able to use structured object data on the front end.  Now I'm talking about going beyond ORM mappers, and even beyond an Object Database here.  What I see coming in the future is a Data Hive. More...

Tracker1

Michael J. Ryan aka Tracker1

My name is Michael J. Ryan and I've been developing web based applications since the mid 90's.

I am an advanced Web UX developer with a near expert knowledge of JavaScript.