Thursday, February 24, 2011

Why validators error messages not displayed in Google Chrome?

I asked myself several times. Why are validators error messages not displayed in Google Chrome? It is weird. Not a single validation error message is displayed in the page as soon as the following CSS is added. It works well with IE, Safari and Mozilla-type browsers but fails on Google Chrome.

  div.sink {padding:20px; width: 190px;border:1px silver solid;}
  div.sink input[type="text"]{width: 190px;}
  div.sink input[type="submit"]{width: 50px;}

I am sure that all the validators are fired and the page as a result reports invalid but there is no error message. There is no doubt that the problem is somehow related to the above CSS. To reduce the complexity of the page and easily to nail down the problem, I marked up a simple login page for investigation.

login page

With a bit modification, the page looks like this - very simple, only text boxes, RequiredFieldValidators and a login button. There is no code behind (no OnClick event) and they are straightly marks up.

<head>
<style type="text/css">

div.login {padding:20px; width: 190px; border:1px silver solid;}
div.login input[type="text"], div.login input[type="password"]{ width: 190px; }
div.login input[type="submit"]{width: 50px;}

</style>
</head>
<body>
<form id="form1" runat="server">
<div class="login">

  User Name:<br />
  <asp:TextBox id="txtUsername" runat="server" MaxLength="30"></asp:TextBox><br />     
  <asp:RequiredFieldValidator ID="RequiredFieldValidatorUsername" runat="server" 
       ControlToValidate="txtUsername" Display="Dynamic" 
       ErrorMessage="A username is required."></asp:RequiredFieldValidator><br />

  Password:<br />
  <asp:TextBox id="txtPassword" runat="server"  TextMode="Password" MaxLength="180" ></asp:TextBox><br />     
  <asp:RequiredFieldValidator ID="RequiredFieldValidatorPassword" runat="server" 
       ControlToValidate="txtPassword" Display="Dynamic" 
       ErrorMessage="A password is required."></asp:RequiredFieldValidator><br />

 <asp:Button ID="btnLogin" runat="server" Text="Log In" /><br />

</div>
</form>
</body>

With the above markup, the error message is shown at least for the User Name, unlike my problem page. The funny is that in this simple example, only the last validator won't be able to display error message if I keep adding more the similar control and validator. For sure, everything works normal if the CSS is removed.

No error message for the only and one validator

no error message for the only and one validator
No error message for the last validator
screen 1

no error message for the last validator - screen 1
No error message for the last validator
screen 2

no error message for the last validator - screen 2
No error message for the last validator
screen 3

no error message for the last validator - screen 3

The CSS is not complex. As a matter of face, it is very simple. What it does is to set the width for the input box or button in the page. To resolve this, I can simply add the width back to the control inself by removing the CSS. However, I cannot do it with my original page. Most controls are from user controls which won't allow me to set width. In addition, If I manage to add the width to them, the changes will affect other pages across the entire project unless I do it programically instead of static. It means I have to go in every single user control to add some code so that it can accept width change. I don't think that I want to go this route.

After playing around for some time, I finally found out the problem. The problem is the width setting in the first line.

  div.login {padding:20px; width: 190px; border:1px silver solid;}
  div.login input[type="text"], div.login input[type="password"]{ width: 190px; }
  div.login input[type="submit"]{width: 50px;}

Adding 4 more pixels fixes the problem!

  div.login {padding:20px; width: 194px; border:1px silver solid;}
  div.login input[type="text"], div.login input[type="password"]{ width: 190px; }
  div.login input[type="submit"]{width: 50px;}

It appears to me that Google Chrome browser cares about the outer width of the DIV when a CSS is specified. Most browsers may ignore it if the outer DIV is not width enough for its child components. From this instance, I learn that I need to pay attention to declare my CSS in order to prevent this from happening.

Saturday, February 12, 2011

Resource interpreted as image but transferred with MIME type text/html

One of my pages failed on Google Chrome. Because of the subject error, I could not debug it with Chrome debugger. The script won't be loaded into the debugger if the page is interpreted as an image. I wonder why and what caused it and I could not ignore it.

After hours looking into the code, I finally found the problem. In my case, I forget to include a JavaScript library reference in one of my user controls. As soon as I added it back, the error is gone and the page can be loaded into the Chrome debugger as other normal pages. Everything works fine.

Although the error message from Chrome is confusing, it at least alerts me that there is something wrong in my page. I am glad I found the problem and fixed it.

Why page events get called twice?

There are a lot of discussions about why the Page events get called twice or even multiple times. Most the solutions from Google search suggested to set AutoEventWireup to false. By default, it is true. Is it really helpful? For sure, a lot of people will disagree. Me either. The solution of AutoEventWireup doesn't stand for all cases. To me, most of the time, this issue is related to image resource.

Consider the following HTML code in the ASP.NET page markup.

<img src="" id="img1" alt="an image" style="display:none" />   cross

The HTML is valid but it is the trouble maker in ASP.NET Web form, which causes the page events being called twice because the src attribute is empty. Removing the src attribute entirely will fix the problem.

Let's consider another image related scenario. What if you want to set a background color in a table cell but mistakenly put background attribute instead of background-color?

<TD background="#008080">   cross

In this case, page events are also being called twice. But if HTML color name is used, everything will work fine. Thus the following HTML code won't cause trouble although it is not correct.

<TD background="Teal">    tickquestion mark

I am not quite sure how or when CalliHelper.EventArgFunctionCaller will bind the above scenarios to the page events. None of them are declared to run as server. But I do know that if the page events are being called twice, the first thing I will check is the markup, especially those HTML codes related to image resource.

Using From Target Attribute

[ Scenario ] [ The Solution ] [ Implemented with JavaScript ] [ Implemented with JQuery ] [ The Code ]

Problem Statement

A page may have several links and buttons to produce different forms of outout. Every single piece of data defined in the page including the input supplied by the user is needed for postback process. Is there a simple way to redirect the output in another window without overriding the current page so that the current page content stays perisistent?

Solution 1: Can we use target attribute of the element?

For a hyperlink, you may think using target attribute to accomplish this. With this approach, you have to gather all the needy input yourself from the current page into the querystring of the URL before posting data back to the server. This will introduce you another problem: querystring size limitation. Different plaforms and browser types have different limitations. Least to say, there is some work to be done in the client side. In addition, your server page must be configured and coded to handle HTTP GET to process the request.

For input button, we are out of luck. There isn't a target attribute defined in the specification. Thus, the action is completely relying on the form action.

Solution 2: How about AJAX?

Another approach to resolve this is using partial page rendering that would provide better user experience by eliminating the full page round trip overhead. Does it really matter in our case if we need every single piece of data defined or entered by the user in that page? It may save some overhead on some resources being loaded. Other than that, every data specified in that page is still posted back to the server for process. What we want is the output in another window so that the user may continue to use the same data to generate another form of output in a separate window and so on. Indeed, using AJAX is a bit complicated here. Of course, you may use some library like JQuery to set up your AJAX calls. For ASP.NET, you can simply accomplish this by using ScriptManager and UpdatePanel. Unfortunately, all of them require you to re-architect your page to accommodate the changes. To me, it is too much work. I need something simpler and make changes as least as possible in the page.

The Ultimate and Simple Solution: by using target attribute of the form.

The solution is to use the old trick defined in HTML with a little Javascript assistance.

In order to post data back to the server, we need a form tag. We usually don't specify the target attribute. In this case, the data is returned to the current page by default. If a target attribute is specified in the form tag, the output will be automatically routed to the specific frame or window by the browser when the data is returned by the server. For example,

Route data to a new window:

<form name="form1" id="form1" target="_blank" method="post" action="Default.aspx" onsubmit="..." >
...
</form>
</pre>

Route data to an iframe:

<form name="form1" id="form1" target="myIframe" method="post" action="Default.aspx" onsubmit="...">
...
</form>
<iframe name="myIframe" id="myIframe" width="400" height="300"></iframe>

Note that the name attribute of the iframe is required in this case. By specification, we should specify the_name_of_the_frame_or_window to the target attribute, not the_id_of_the_frame_or_window. But the id works for some Webkit type of browsers like Google Chrome.

With this simple solution in mind, I finally come up a way to unpuzzle the above problem with a very minor change in the page. The following is the solution presented in ASP.NET. The technique can be applied to elsewhere such as a simple HTML/CGI program.

What I need to do is dynamically adding a target attribute to the form before the data is being posted back to the server.

JavaScript Solution

First, let's define the script which does the injection. The JavaScript function may look like the following.

  function changeFormTarget() {
    var formObj = document.getElementById('form1');
    formObj.setAttribute('target', '_blank');
  }

What the above code does is, before posting data back to the server for process, it injects the target attribute into the form element by producing the following HTML code, which instructs the browser where to output the next document.

<form name="form1" id="form1" target="_blank" method="post" action="Default.aspx" onsubmit="...">

Then, we hook this function to the onclick event of the element before form submission. In ASP.NET, simply add OnClientClick event on the control to instruct the ASP.NET engine to process the client script first before postback.

<asp:LinkButton ID="lnkRptToNewTarget" runat="server" OnClientClick="changeFormTarget()" OnClick="RptToNewTarget_Click">Generate Report to New Target</asp:LinkButton><br />
<asp:Button ID="btnRptToNewTarget" runat="server" OnClientClick="changeFormTarget()" OnClick="RptToNewTarget_Click" Text="Generate Report to New Target"  />

The above markup will result in the following HTML code:

<a onclick="changeFormTarget();" id="lnkRptToNewTarget" 
   href="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("lnkRptToNewTarget", "", true, "", "", false, true))">Generate Report to New Target</a><br />
<input 
   type="submit" name="btnRptToNewTarget" id="btnRptToNewTarget"
   value="Generate Report to New Target" 
   onclick="changeFormTarget();WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("btnRptToNewTarget", "", true, "", "", false, false))" />

As you can see, with the very minimal changes in the page, a new window will be spawned off from the current page when the result is back.

JQuery Solution

If you're using JQuery, the solution is even simpler without changing any element in the page. Simply put the following script and then the onclick event will be automatically wired to the appropriate elements. In my example, simply ask JQuery to scan my elements and then wire them up with my supplied onclick event.

  $('#lnkRptToNewTarget,#btnRptToNewTarget').click(function() {
    $('form').attr('target', '_blank');
  });

With JQuery, basically the page remains intact without any markup or element being changed. The result is the same as JavaScript approach. Of course, you can write your own auto event wire up to achieve what JQuery does but there will be a lot of work.

Sometimes a simple solution works like a charm and it also eliminates the time to re-test and shortens the development time. I hope you will find this piece of information somehow useful.

The Code

If you want to test it yourself and see how this works, here is my simple backend event handler for output. In real life, the handler could be in another process which generates PDF or other non-HTML types of documents, and then call Response.Redirect() or Response.TransmitFile() to return the document to the client.

  protected void RptToNewTarget_Click(object sender, EventArgs e) {
    Response.Write(
      string.Format("Your report <b>{0}</b> is generated.", this.txtReportName.Text));
    Response.End();
  }

Here is the markup for JQuery approach. You can simply alter it to JavaScript solution that I discussed above.

<form id="form1" runat="server" defaultfocus="txtReportName">
<div>
  Enter Report Name: <br />
  <asp:TextBox ID="txtReportName" runat="server"></asp:TextBox>
  <asp:RequiredFieldValidator ID="RequiredFieldValidatorTxtReportName" runat="server" 
       ErrorMessage="Please enter the report name." Display="Dynamic" ControlToValidate="txtReportName"></asp:RequiredFieldValidator><br />
  <asp:LinkButton ID="lnkRptToNewTarget" runat="server" OnClick="RptToNewTarget_Click">Generate Report to New Target</asp:LinkButton><br />
  <asp:Button ID="btnRptToNewTarget" runat="server" OnClick="RptToNewTarget_Click" Text="Generate Report to New Target"  />
</div>
</form>

<script type="text/javascript">
  $('#lnkRptToNewTarget,#btnRptToNewTarget').click(function() {
    $('form').attr('target', '_blank');
  });
</script>