How to deal with IE9 html table bug where extra column shows up for some rows with partial view?
Asked Answered
C

3

5

I see this question has already been asked around a IE9 but which is adding extra columns in random rows of a html table. The root issue seems to be a IE 9 bug that is fixed in IE 10 (but i have a lot of IE9 users)

It states it usually happens with tables built via ajax but i am seeing this on regular pages that output html tables.

There is a workaround Javascript solution but the answer assumes that you were building a table with Javascript (from an ajax call). I am using a partial view (or in some cases just rendering a regular formatted html table directly on a single page) so I wanted to find out if there is a solution to prevent this UI issue on IE9 when you are simply rendering direct html on page.

I want to avoid having to literally have no white space in my actually code as that will be very hard to maintain.

Caron answered 10/1, 2014 at 20:5 Comment(6)
I have the issue not just on partial view. In many cases i am just rendering an html table on a regular page. I updated the question to be more explicit so partialview is only 1/2 the question nowCaron
How is this problem related to ASP? Isn't it purely HTML problem? You are harming yourself because many users (including me) who are not interested in ASP has this tag in ignore tags.Personalize
I experienced something very similar sometime back, and as I remember it was a simple css trick that solved the issue for me. If you can post these things it would be better to understand the problem and see if the solution I used applies here or not: screenshot of how your table renders, style related to your table, and how its getting created (the code).Fleeta
Is it possible for you to paste the html of two table rows? One working and the other one not working. I'd like from the real source not the one provided by the browser dev tools, because the first thing it comes to my mind is the syntax or browser hiding/showing an empty cell.Bogor
The html of a valid an invalid row are identify when looking through viewsource . . that is the whole browser bug, right :)Caron
@Caron If you've added that last line to your question in reference to my answer, it's not relevant. My answer only affects markup that's sent to the browser, so it has no negative effect on maintenance. I added a link to a test project on dropbox below. You can see it for yourself.Frosted
F
2

This is possible. For partial views it's simpler, because you can capture the output of Html.Partial directly, modifying it before the response is written to the output stream.

In order to do that, you'd create an extension method, which could look something like this:

public static class HtmlExtensions
{
    public static HtmlString PartialIE9TableFix(this HtmlHelper helper,
        string view, object model = null)
    {
        var partialOutput = helper.Partial(view, model).ToString();
        partialOutput = Regex.Replace(partialOutput, @"/td>\s+<td",
                            "/td><td", RegexOptions.IgnoreCase);

        return MvcHtmlString.Create(partialOutput);
    }
}

As you can see, it's capturing the output of Html.Partial directly, and then performing the replacement on that. You'd use it in your view like so:

@Html.PartialIE9TableFix("YourPartial")

However, to do this for actual views requires a lot more work, and certainly a lot more care when using it. In order to do this, we actually need to capture, and modify, the response stream before it's sent to the client.

The IE9TableFixFilter below is very heavily based on code from Minify HTML with .NET MVC ActionFilter.

using System;
using System.IO;
using System.Text;

public class IE9TableFixFilter : Stream
{
    public IE9TableFixFilter(Stream response, Func<string, string> filter)
    {
        this.response = response;
        this.filter = filter;
    }

    public override bool CanRead { get { return true; } }
    public override bool CanSeek { get { return true; } }
    public override bool CanWrite { get { return true; } }
    public override void Flush() { response.Flush(); }
    public override long Length { get { return 0; } }
    public override long Position { get; set; }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return response.Read(buffer, offset, count);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        return response.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
        response.SetLength(value);
    }

    public override void Close()
    {
        response.Close();
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        //byte[] data = new byte[count];
        //Buffer.BlockCopy(buffer, offset, data, 0, count);
        string s = Encoding.Default.GetString(buffer);

        s = filter(s);

        byte[] outData = Encoding.Default.GetBytes(s);
        response.Write(outData, 0, outData.GetLength(0));
    }

    private Stream response;
    private Func<string, string> filter;
}

The majority of code here is filling in implementations for abstract members of Stream. The important part is what's going on in the Write method.

The version of Write from the article first makes a copy of the bytes of the stream without actually using them. There's no mention there if this is for some specific reason, but it seems useless to me, so I commented those lines out.

Next up, we need to create a simple ActionFilter to apply the response filter to:

using System.Text.RegularExpressions;
using System.Web.Mvc;

public class IE9TableFixFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var response = filterContext.HttpContext.Response;
        response.Filter = new IE9TableFixFilter(response.Filter,
            s => Regex.Replace(s, @"/td>\s+<td", "/td><td", RegexOptions.IgnoreCase));
    }
}

Now that's all done, I would strongly recommend that you don't apply this filter globally, instead choosing to decorate it on actions that require its use. The reason for that is because it will naturally incur some performance penalty, so it's best to be explicit about when it's actually needed. You won't need the partial extension method when using this. So simply decorate your actions to use it:

[IE9TableFixFilterAttribute]
public ActionResult Index()
{
    return View();
}

Update

To make the filter attribute more efficient, you could actually just apply it to browsers that contain the user-agent string MSIE 9.0:

public class IE9TableFixFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var request = filterContext.HttpContext.Request;
        var response = filterContext.HttpContext.Response;

        if (request.UserAgent.Contains("MSIE 9.0"))
        {
            response.Filter = new IE9TableFixFilter(response.Filter,
                s => Regex.Replace(s, @"/td>\s+<td", "/td><td", RegexOptions.IgnoreCase));
        }
    }
}
Frosted answered 10/1, 2014 at 20:31 Comment(2)
@Caron I've uploaded a working solution to dropbox for you to take a look at. You can find it here. I've setup a view and a partial view, both containing table markup that will cause problems with IE9. Check both the Index and About pages to see the rendered markup using both the partial extension and the action filters. I've enabled the IE9 check on the filter by default, so try running it with IE9 and another browser to see the difference.Frosted
thanks for the detailed response. I am surprised there is not a simpler solution given I would imagine this would affect all users of table and IE9 (lots of customers) . but i guess the simpler solution is to get everyone to upgrade to IE10 :). Either way i have accepted your solutionCaron
C
5

I faced this same problem and ended up on this page looking for a solution. In my case the problem was with HTML rendered by a Repeater control with ASP.Net 4.

I inspected the code generated by the server with Charles and there was no problem with the HTML.

The last sentence in the question lead me to my own simple solution.

I want to avoid having to literally have no white space in my actually code as that will be very hard to maintain.

I simply put comments between the opening of the TR tag and the first TD tag as well as between each TD and the closing TR tag:

<tr><!--
 --><td class="cell">
    Cell 1  
    </td><!--
 --><td class="cell">
    Cell 2  
    </td><!--
 --><td class="cell">
    Cell 3  
    </td><!--
 --><td class="cell">
    Cell 1  
    </td><!--
 --></tr>

This solution means we don't have to use an extension and it won't be hard to read or maintain the code in the future.

Chinn answered 7/3, 2014 at 22:48 Comment(1)
this proved to be the easiest thing for me since my content is loaded using ng-repeat with AngularJS.Hyperkeratosis
F
2

This is possible. For partial views it's simpler, because you can capture the output of Html.Partial directly, modifying it before the response is written to the output stream.

In order to do that, you'd create an extension method, which could look something like this:

public static class HtmlExtensions
{
    public static HtmlString PartialIE9TableFix(this HtmlHelper helper,
        string view, object model = null)
    {
        var partialOutput = helper.Partial(view, model).ToString();
        partialOutput = Regex.Replace(partialOutput, @"/td>\s+<td",
                            "/td><td", RegexOptions.IgnoreCase);

        return MvcHtmlString.Create(partialOutput);
    }
}

As you can see, it's capturing the output of Html.Partial directly, and then performing the replacement on that. You'd use it in your view like so:

@Html.PartialIE9TableFix("YourPartial")

However, to do this for actual views requires a lot more work, and certainly a lot more care when using it. In order to do this, we actually need to capture, and modify, the response stream before it's sent to the client.

The IE9TableFixFilter below is very heavily based on code from Minify HTML with .NET MVC ActionFilter.

using System;
using System.IO;
using System.Text;

public class IE9TableFixFilter : Stream
{
    public IE9TableFixFilter(Stream response, Func<string, string> filter)
    {
        this.response = response;
        this.filter = filter;
    }

    public override bool CanRead { get { return true; } }
    public override bool CanSeek { get { return true; } }
    public override bool CanWrite { get { return true; } }
    public override void Flush() { response.Flush(); }
    public override long Length { get { return 0; } }
    public override long Position { get; set; }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return response.Read(buffer, offset, count);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        return response.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
        response.SetLength(value);
    }

    public override void Close()
    {
        response.Close();
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        //byte[] data = new byte[count];
        //Buffer.BlockCopy(buffer, offset, data, 0, count);
        string s = Encoding.Default.GetString(buffer);

        s = filter(s);

        byte[] outData = Encoding.Default.GetBytes(s);
        response.Write(outData, 0, outData.GetLength(0));
    }

    private Stream response;
    private Func<string, string> filter;
}

The majority of code here is filling in implementations for abstract members of Stream. The important part is what's going on in the Write method.

The version of Write from the article first makes a copy of the bytes of the stream without actually using them. There's no mention there if this is for some specific reason, but it seems useless to me, so I commented those lines out.

Next up, we need to create a simple ActionFilter to apply the response filter to:

using System.Text.RegularExpressions;
using System.Web.Mvc;

public class IE9TableFixFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var response = filterContext.HttpContext.Response;
        response.Filter = new IE9TableFixFilter(response.Filter,
            s => Regex.Replace(s, @"/td>\s+<td", "/td><td", RegexOptions.IgnoreCase));
    }
}

Now that's all done, I would strongly recommend that you don't apply this filter globally, instead choosing to decorate it on actions that require its use. The reason for that is because it will naturally incur some performance penalty, so it's best to be explicit about when it's actually needed. You won't need the partial extension method when using this. So simply decorate your actions to use it:

[IE9TableFixFilterAttribute]
public ActionResult Index()
{
    return View();
}

Update

To make the filter attribute more efficient, you could actually just apply it to browsers that contain the user-agent string MSIE 9.0:

public class IE9TableFixFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var request = filterContext.HttpContext.Request;
        var response = filterContext.HttpContext.Response;

        if (request.UserAgent.Contains("MSIE 9.0"))
        {
            response.Filter = new IE9TableFixFilter(response.Filter,
                s => Regex.Replace(s, @"/td>\s+<td", "/td><td", RegexOptions.IgnoreCase));
        }
    }
}
Frosted answered 10/1, 2014 at 20:31 Comment(2)
@Caron I've uploaded a working solution to dropbox for you to take a look at. You can find it here. I've setup a view and a partial view, both containing table markup that will cause problems with IE9. Check both the Index and About pages to see the rendered markup using both the partial extension and the action filters. I've enabled the IE9 check on the filter by default, so try running it with IE9 and another browser to see the difference.Frosted
thanks for the detailed response. I am surprised there is not a simpler solution given I would imagine this would affect all users of table and IE9 (lots of customers) . but i guess the simpler solution is to get everyone to upgrade to IE10 :). Either way i have accepted your solutionCaron
B
0

Other than clearing white-spaces, you may try one of the following fixes:

  • CSS: td { white-space: nowrap; } if applicable!

  • Force your tables to have fixed layout:

<table style="table-layout: fixed" width="600">
  <colgroup>
    <col width="100"><col width="300"><col width="200">
  </colgroup>
  <tr height="20">
    <td>...</td>
    <td>...</td>
    <td>...</td>
  </tr>
</table>
  • If your web application already supports IE8, you can force IE9 users to use IE8 rendering engine by using: <meta http-equiv="X-UA-Compatible" content="IE=8" />
Bowknot answered 19/1, 2014 at 13:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.