Thursday, April 30, 2015

Responsive Web Design with ASP

Getting off the table, viewport, devicewidth, code-behind and the lifecycle of an ASP page.

Until the nasty gram from Google about de-ranking a clients pages for not properly supporting mobile devices, I was plenty happy. I thought the pages looked fine, and actually adding the viewport header made them look worse on mobile devices. Can't fight google.

I had heard grumblings about tables and how pedestrian they are, but getting off them, it was like losing Flash again. Maybe that is a bit dramatic, but I have learned to coexist with HTML 5 and my Action Script code still has a home, but tables. They really had to go.

Life is simply better without them, F all the matching <tr and </td, good riddance. Tables will live on with html marketing emails, where divs will ruin your day, but divs just blend and they allow for the dynamic wrapping and sizing of the target device for a more robust user experience across devices.

This opened a new can of worms; making device width usable while your page elements are created.  Most static pages can be resolved via CSS, style="width:100%; max-width:640px;"  Yet for complex dynamic pages, you need to know the device capabilities when the page is created serverside.  As an example check out http://aqua8472.com/seeitwork.aspx  The grid elements are created dynamically and based on the device serverside.   In the ASP lifecycle, all server side events process before the page is rendered. That makes sense, the page is essentially static and has no contract back to the server, ajax muddies this, but for all practical purposes, once the server is done with its back end processing, the HTML is rendered and the server has moved on. Now I was facing a chicken and egg scenario, I want to size the elements on my page server-side based on the device. Problem...

To solve this touches on several areas within the page lifecycle. Essentially, I fed my device width via a webservice call and created a session variable with the width for use during server-side processing of the page. Customizing IHttpModule with an event for PostAcquireRequestState
allows for a redirect to a detection page, this detection page via a webservice call creates the session variable. Keep in mind this is only available to asp pages.
 privatevoidOnPostAcquireRequestState(objecto,EventArgsargs)
{
<<snippet>>
HttpApplicationapp=(HttpApplication)o;
BrowserSupport.BrowserInfobi=newBrowserSupport.BrowserInfo(app.Request);
if(bi.mobile)
{
 if(app.Session["deviceWidth"]!=null)
  ProcessASPX(app,newBrowserSupport.BrowserInfo(app.Request));
 else
 {
  if(!sPageRequested.ToLower().Equals("detectdevice.aspx"))
   app.Response.Redirect("/noc/detectdevice.aspx?r="+app.Request.Path,true);
 }
}
else
 ProcessASPX(app,newBrowserSupport.BrowserInfo(app.Request));

Javascript handles the detection of the device width and calls a webservice. Keep in mind, you have created a race condition and you need to wait for the webservice call to complete before redirecting to the original page passed in as a url param from OnPostAcquireState

<metaname="robots"content="noindex,nofollow"/>
<metaname="viewport"content="width=device-width,initial-scale=1"/>
 
<scripttype="text/javascript"lang="javascript"src="/scripts/jquery.js"></script>
 
<scripttype="text/javascript"lang="javascript">
$(document).ready(function(){
 varwindowWidth=(window.innerWidth>0)?window.innerWidth:screen.width;
 jdwebservice.setdevicewidth(windowWidth,
  setDeviceWidthOnSuccess,setDeviceWidthOnFail);
});
 
functionsetDeviceWidthOnSuccess(result){
 if(result){
  varurlp=location.search;
  varoffset=urlp.indexOf('?r=');
  if(offset<0){
   alert('Unabletolocateurlparam:'+urlp);
  }
  else
  {
   window.location=urlp.substring(offset+3);
  }
 }
 else
alert('Wearehavingtroublecommunicatingwithourbackendservers.Contactsupportwithmessagedetectdevice.setDeviceWidth.(result):'+result.get_message());
}
 
functionsetDeviceWidthOnFail(result){
alert('Wearehavingtroublecommunicatingwithourbackendservers.Contactsupportwithmessagedetectdevice.setDeviceWidth.Tryreloadingthispage.'+result.get_message());
}
</script>
 
In the detection page, we want to keep the crawlers happy by notifying them this is a temp 302 redirect and not a 200 success. Response codes can only be set by the server.

protectedvoidPage_Load(objectsender,EventArgse)
{
 Response.Status="302DeviceDetectionRedirect";
 Response.StatusCode=302;
}
The webservice is a simple call and looks like this.

[WebMethod(EnableSession=true)]
publicboolsetdevicewidth(uintdeviceWidth)
{
 boolbResult=false;
 try
 {
  if(Session["deviceWidth"]==null)
   Session.Add("deviceWidth",deviceWidth.ToString());
  else
   Session["deviceWidth"]=deviceWidth.ToString();
  bResult=true;
 }
 catch(ExceptionE)
 {
  Utility.SendEventLogError("("+HttpContext.Current.Request.UserHostAddress+")
  jdWebservices:setdevicewidth,Exception:"+E.Message);
 }
 returnbResult;
}
Now on a page load, for the parent div I set the width at runat server, then on its oninit, use the session device width as below:

<divclass="divIndexHeader"id="ixHeaderDiv"oninit="Div_Init"runat="server">

protectedvoidDiv_Init(objectsender,EventArgse)
{
<snippet>
notice we only mess with mobile devices, desktop are as designed.  
 BrowserSupport.BrowserInfobi=newBrowserSupport.BrowserInfo(Request);
 if(bi.mobile)
 {
  if(Session["deviceWidth"]!=null)
  {
   uintuWindowWidth=0;
   if(!uint.TryParse((string)Session["deviceWidth"],outuWindowWidth))
    thrownewException("UnabletoconvertSession[\"deviceWidth\"]
    toanuint,"+ (string)Session["deviceWidth"]);
   if(uWindowWidth<975)
    ((HtmlGenericControl)sender).Style["Width"]=uWindowWidth+"px";
  }
  else
   thrownewException("Session[\"deviceWidth\"]isnull,
    wasdetectdevicerunfirst?");
 }
}
For embedded elements with width:X%, they all fall in line based on the parent element width.  Having the power of server-side processing, allows for creativity to adjust/remove padding between inline-block or float divs.  
Getting creative, for our low resolution devices, like the iPhone, size your images to take the width of the device, then adjust the padding for higher resolution devices support based on your original layout.

<divid="divTour4"class="divIndexTour"runat="server"oninit="divTour_Init">

if(Session["deviceWidth"]!=null)
 {
  uintuWindowWidth=0;
  if(!uint.TryParse((string)Session["deviceWidth"],outuWindowWidth))
   thrownewException("UnabletoconvertSession...
  if(uWindowWidth<375)
  {
   ((HtmlGenericControl)sender).Style["Width"]=uWindowWidth+"px";
   ((HtmlGenericControl)sender).Style["padding-left"]="0px";
  }
  elseif((uWindowWidth<900)&&(uWindowWidth>376))
  {
   ((HtmlGenericControl)sender).Style["Width"]=(uWindowWidth*.75)+"px";
   ((HtmlGenericControl)sender).Style["padding- 
   left"]=(uWindowWidth*.12)+"px";
  }
 elseif((uWindowWidth<975)&&(uWindowWidth>901))
 {
  uintuPadding=75-((975-uWindowWidth)/2);
   ((HtmlGenericControl)sender).Style["padding-left"]=uPadding+"px";
 }

A good page to check out is http://viewportsizes.com/

No comments:

Post a Comment