Emulating CSS Child Selectors in IE6


We all hate IE 6. If you don’t hate IE 6, then you just haven’t worked with it enough or you haven’t worked with newer browsers enough to realize what you’re missing. Alas, it still holds such a large market share that it cannot be ignored, so let’s put the whining aside and dig in…

The problem is simple: IE 6 does not support the CSS child selector. It will ignore the following CSS:

table.green > tr > td {
  background-color: green;
  padding: 20px;
}

If you cannot redesign your layout and/or stylesheet to avoid the need for child selectors, then your only option is to emulate the child selector in some way. There are several methods for doing this, and many of them are well documented across the internet, but I will explain them all right here along with their pros and cons. These are the methods I will cover:

  • jQuery
  • CSS Expressions
  • Use More CSS Classes
  • Dean Edwards’ Script
  • CSS Descendant Selector

jQuery

The first possibility is to use jQuery, a very well designed JavaScript library that uses CSS selectors to manipulate the DOM. You can import the jQuery library and then do something like this:

$("table.green > tr > td").css("background-color", "green").css("padding", "20px");
Drawbacks:
  • Requires jQuery or other JavaScript library that supports CSS selectors.
  • Requires the user to have JavaScript enabled.
  • Separates styling logic from CSS.

CSS Expressions

IE supports a CSS construct called a CSS expression that allows you to inject JavaScript into your CSS styles. You can use them to emulate all kinds of CSS selectors and properties that are not supported by IE 6, but they have some major drawbacks that are explained momentarily. This is how you would use CSS expressions to emulate the child selector:

/* CSS selector recognized by everything but IE 6 */
table.green > tr > td {
  background-color: green;
  padding: 20px;
}

/* CSS selector recognized only by IE 6 */
* html table.green tr td {
  background-color: expression(/green/.test(this.parentNode.parentNode.className)? "green" : "transparent");
  padding: expression(/green/.test(this.parentNode.parentNode.className)? "20px" : "0");
}
Drawbacks:
  • CSS expressions are difficult to read and understand.
  • Experts recommend that you avoid CSS expressions due to performance issues.
  • Requires you to know what the “fallback” style should be (in this case, a transparent background and 0 padding).

Use More CSS Classes

This is the brute force approach. You can simply apply a CSS class to every element that you want to style:

CSS:
table.green td.green {
  background-color: green;
  padding: 20px;
}
HTML:
<table class="green">
  <tr>
    <td class="green">A table cell.</td>
  </tr>
</table>
Drawbacks:
  • Tightly couples your CSS with your HTML, which defeats the purpose of decoupling them in the first place.
  • If your CSS developer is a different person than your HTML/content developer, then this can cause a lot more back-and-forth communication.

Dean Edwards’ Script

Dean Edwards, a respected expert in the JavaScript community, has written a script that attempts to turn IE 6 into a standards-compliant browser. The idea is that you write your CSS files as if you only had to deal with standards-compliant browsers like Firefox, then you import Dean’s script and your problems are solved. His script parses your CSS files for certain constructs (such as the CSS child selector) and manipulates the DOM with JavaScript in order to apply the styling. I welcome you to give it a try, but I have had quite a few issues with it and have decided not to use it.

Drawbacks:
  • Requires the user to have JavaScript enabled.
  • Doesn’t always work correctly, especially when it comes to PNG support.

CSS Descendant Selector

The final method for emulating the CSS child selector is to regress to the descendant selector and then “clear” the styling by using another descendant selector:

table.green tr td { /* Specificity: 0,0,1,3 */
  background-color: green;
  padding: 20px;
}

/* Clear the styling of any table cell that is not a direct descendant of table.green */
table.green tr * td { /* Specificity: 0,0,1,3 */
  background-color: transparent;
  padding: 0;
}

Notice that the specificity of the two selectors is the same. This helps to prevent the “clearing” selector from interfering with other CSS selectors.

This solution seems nice and clean, but it has some issues with nested classes. Take the following HTML as an example:

<table class="green">
  <tr>
    <td>
      <table class="green">
        <tr>
          <td>This is a table cell.</td>
        </tr>
      </table>
    </td>
  </tr>
</table>

This is the result (note that I have added a border to help you see the padding):

Not exactly what we were hoping for, right? That’s because the second CSS selector takes precedence over the first one even though they have the same specificity due to their order in the CSS file. Luckily, there is an easy fix for this. We can just switch them around:

/* Clear the styling of any table cell that is not a direct descendant of table.green */
table.green tr * td { /* Specificity: 0,0,1,3 */
  background-color: transparent;
  padding: 0;
}

table.green tr td { /* Specificity: 0,0,1,3 */
  background-color: green;
  padding: 20px;
}

Now let’s take another look:

Much better! Now we have a solution that works in all browsers!

Drawbacks:
  • Requires you to know what the “fallback” style should be (in this case, a transparent background and 0 padding).
  • Requires you to reverse the ordering of your CSS declarations, which is slightly less readable.

Conclusion

I have presented these options to you so you can choose the method that makes the most sense in your project. However, if you need some guidance, then I would suggest the final method (CSS Descendant Selector) because it only has one real drawback which I have always found to be completely manageable.

Good luck!

ASP.NET MVC Ajax Redirect


I’ve had several occasions where I have needed to make an Ajax request to a secure action on the server (i.e. an action that requires the user to be logged in). The problem is…sometimes the user’s session will timeout between the time they access the secure page and the time they make the secure Ajax request. My action notices this and redirects the user to the login page…but if its an Ajax request then a simple 302 redirect just won’t work. Here’s a method that will.

public class MyBaseController : System.Web.Mvc.Controller
{
    protected override RedirectResult Redirect(string url)
    {
        return new AjaxAwareRedirectResult(url);
    }
}

public class AjaxAwareRedirectResult : RedirectResult
{
    public AjaxAwareRedirectResult(string url)
        : base(url)
    {
    }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context.RequestContext.HttpContext.Request.IsAjaxRequest())
        {
            string destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);

            JavaScriptResult result = new JavaScriptResult()
            {
                Script = "window.location='" + destinationUrl + "';"
            };
            result.ExecuteResult(context);
        }
        else
            base.ExecuteResult(context);
    }
}

Now you can use the controller’s Redirect() function as usual…and it will automatically detect if it needs to perform a 302 redirect or an Ajax redirect.

Cheers!

ASP.NET MVC Render Partial View to String


I have run into a situation where I would like to render a partial view to a string and then return it as part of a JSON response like so:

return Json(new {
    statusCode = 1,
    statusMessage = "The person has been added!",
    personHtml = PartialView("Person", person)
});

The ability to do something like this would open up a ton of amazing possibilities, so I really scoured the internet looking for a solution. Unfortunately, no one seems to have come up with a clean solution for it, so I dug into the MVC code and came up one…and because I’m such a nice guy, you get to copy it for free. 😉

public abstract class MyBaseController : Controller {

    protected string RenderPartialViewToString()
    {
        return RenderPartialViewToString(null, null);
    }

    protected string RenderPartialViewToString(string viewName)
    {
        return RenderPartialViewToString(viewName, null);
    }

    protected string RenderPartialViewToString(object model)
    {
        return RenderPartialViewToString(null, model);
    }

    protected string RenderPartialViewToString(string viewName, object model)
    {
        if (string.IsNullOrEmpty(viewName))
            viewName = ControllerContext.RouteData.GetRequiredString("action");

        ViewData.Model = model;

        using (StringWriter sw = new StringWriter()) {
            ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
            ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
            viewResult.View.Render(viewContext, sw);

            return sw.GetStringBuilder().ToString();
        }
    }
}

Now you can simply do this:

public class MyController : MyBaseController {

    public ActionResult CreatePerson(Person p) {
        if (ModelState.IsValid) {
            try {
                PersonRepository.Create(p);
                return Json(new {
                    statusCode = 1,
                    statusMessage = "The person has been added!",
                    personHtml = RenderPartialViewToString("Person", p)
                });
            }
            catch (Exception ex) {
                return Json(new {
                    statusCode = 0,
                    statusMessage = "Error: " + ex.Message
                });
            }
        }
        else
            return Json(new {
                statusCode = 0,
                statusMessage = "Invalid data!"
            });
    }
}

Also note that you can modify these functions to render a View (rather than a PartialView) with this small change:

ViewEngineResult viewResult = ViewEngines.Engines.FindView(ControllerContext, viewName);

Enjoy!