Wednesday, November 23, 2016

Phone numbers - make it easy to read with JQuery

When entering and displaying phone numbers, sometimes its annoying to read all numbers together. Because it may be hard to read. If we can add a space in between few numbers to make it masked, it would be ideal for the user to read it quickly.

It is easy with JQuery. Look at the below function.

 function addSpace(control, flPaste) {    
   if (flPaste == false) {  
     var txtVal = $("#" + control).val();  
     if (txtVal.length == 2) {  
       $("#" + control).val($("#" + control).val() + " ");  
       flSpaceAdded = true;  
     }  
     else if (txtVal.length == 5) {  
       $("#" + control).val($("#" + control).val() + " ");  
       flSpaceAdded = true;  
     }  
     else if (txtVal.length == 8) {  
       $("#" + control).val($("#" + control).val() + " ");  
       flSpaceAdded = true;  
     }  
     else if (txtVal.length == 11) {  
       $("#" + control).val($("#" + control).val() + " ");  
       flSpaceAdded = true;  
     }  
   }  
   else if (flPaste == true) {  
     setTimeout(function () {  
       var txtVal = $("#" + control).val();  
       var txtValNew = "";  
       for (var i = 0, len = txtVal.length; i < len; i++) {  
         txtValNew += txtVal[i];  
         if (i == 1) {  
           txtValNew += " ";  
         }  
         else if (i == 3) {  
           txtValNew += " ";  
         }  
         else if (i == 5) {  
           txtValNew += " ";  
         }  
         else if (i == 7) {  
           txtValNew += " ";  
         }  
       }  
       $("#" + control).val(txtValNew);  
     }, 100);  
   }  
 }  


This can be applied to the textboxes by binding the events below;

 //for typing  
 $("#txtadPhone").keyup(function () {  
     addSpace("txtadPhone",false);  
   });    
 // for pasting  
   $("#txtadPhone").bind('paste', function (e) {  
     addSpace("txtadPhone", true);  
   });  

So the textbox will look like this;


So you will have 14 characters for a 10 digit numbers here. Therefore you may need to cater it in the database or else you need to remove the space using a method in the codebehind. For example,


 public static string removeSpace(string str)  
     {  
       if (str == null)  
         return null;  
       var vVal = string.Empty;  
       foreach (var v in str.ToCharArray())  
       {  
         if ((int)v == 32)  
           continue;  
         vVal += v.ToString();  
       }  
       return vVal;  
     }  

This is a static method that remove the space. However, you may not need to apply this but if there is a restriction in the DB side, you can always use this.

Happy Coding... :)

Tuesday, November 1, 2016

Solved : ASP.Net form submit and page expire when navigate back

Each page we create in ASP.Net has at least one form. Those form can be submitted in many ways. Either a postback from a server control or an html button can submit a form using JavaScript.

Look at the below image.




















The page has expired due to a submission and user tries to navigate backward which the server needs the page to be submitted. Simulating the issue in a live environment would be like below;

1. You can have a filtering criteria as this.
2. The "Search" is an anchor tag that has a onClick event bind to it.




3. When user tick on each of the check boxes, the id values are stored in a hidden field and on "javascript:submitForm()" function the page (form) is resubmitted to the server to filter the product list. So the "submitForm" function looks like below;

 <input id="hdnFilterCriteria" name="hdnFilterCriteria" value="4339435,4339434,4339427," type="hidden">  


4. This causes the page to post back with the filtering criteria appended to the hidden field.
5. After the postback, if the user move in to another page and hit browser back button to see the previous page, the following error will display due to the document expiration.


















It is time to see how we can avoid this error. It is not that hard and all you need is to think outside the box. Simply avoid page postbacks / submissions. In this case we uses JQuery AJAX. Let's focus about the solution.

1. Create a class with the all properties included.
 public class SearchAttribute  
 {  
   public string idAttribute { get; set; }    
   public string strFilterCriteria { get; set; }    
   // You can have all the attributes as properties here  
 }  

2. Use the Cache / Session to maintain the search history. You can use Ajax to update the cache. See the web method below;

   [System.Web.Script.Services.ScriptMethod]  
   [WebMethod(EnableSession = true)]  
   public string catProductHiddenValueCaching(int idSearch,string idAttribute, string strFilterCriteria)  
   {  
     var cacheName = HttpContext.Current.Session.SessionID.ToString() + "_SearchAttribute";  
     Dictionary<int, SearchAttribute> searchHistory = new Dictionary<int, SearchAttribute>();  
     if (idSearch > 1)  
     {        
       var cacheSA = HttpContext.Current.Cache.Get(cacheName);  
       if (cacheSA != null)  
         searchHistory = (Dictionary<int, SearchAttribute>)cacheSA;  
     }  
     SearchAttribute sa = new SearchAttribute()  
     {  
       idAttribute = idAttribute,  
       strFilterCriteria = strFilterCriteria  
     };  
     searchHistory.Add(idSearch, sa);  
     HttpContext.Current.Cache.Insert(cacheName, searchHistory);      
     JavaScriptSerializer js = new JavaScriptSerializer() { MaxJsonLength = Int32.MaxValue };  
     return js.Serialize(idSearch.ToString());  
   }  

  • Use Session ID + Cache name to avoid conflicts with shared cache location.
  • Use Dictionary to store search attribute instances of each search
  • Use the Cache.Insert to overwrite the existing cache value.

3. Inside the onclick event of the "Search" button, call this function asynchronously.

     function submitForm(){  
          try{  
         var j = jQuery.noConflict();  
         var inSearch = getParameterByName('inSearch'); // to track the no of search histories  
         if(inSearch){  
             inSearch = parseInt(inSearch);  
             inSearch++;  
           }  
         else  
           inSearch = 1;  
         var urlAjax = 'http://'+window.location.hostname+'/your_service.asmx/setFilderValueCache'; // get the service path with full URL  
         var idAttribute = j("#idAttribute").val();  
         var strFilterCriteria = j("#strFilterCriteria").val();  
         var objPara = {  
           idSearch:inSearch,  
           idAttribute: idAttribute,  
           strFilterCriteria: strFilterCriteria  
         };  
         j.ajax({  
           type: "POST",  
           url: urlAjax,  
           data: JSON.stringify(objPara),  
           contentType: "application/json; charset=utf-8",  
           dataType: "json",  
           success: function (msg) {  
             var ret = JSON.parse(msg.d);     
             var url = window.location.href;    
             var newUrl = queryStringUrlReplacement(url, 'inSearch', inSearch); // append the inSearch as a query string parameter.  
             window.location.href = newUrl;               
           },  
           error: function (err) {  
             alert(err.responseText);  
           }  
         });  
          }catch(e){}  
     }  

  • idSearch is used to update the query string and reload the page with query string parameter.

 function queryStringUrlReplacement(url, param, value)   
     {  
       var re = new RegExp("[\\?&]" + param + "=([^&#]*)"), match = re.exec(url), delimiter, newString;  
       if (match === null) {          
         var hasQuestionMark = /\?/.test(url);   
         delimiter = hasQuestionMark ? "&" : "?";  
         newString = url + delimiter + param + "=" + value;  
       } else {  
         delimiter = match[0].charAt(0);  
         newString = url.replace(re, delimiter + param + "=" + value);  
       }  
       return newString;  
     }  

4. When "Search" is clicked, the relevant cache object is accessed and passed the search criteria to it's parameters inside "Page Load"

 protected void Page_Load(object sender, EventArgs e)  
   {      
     string strFilterCriteria= ((Request.Form["strFilterCriteria"] ?? Request.QueryString["strFilterCriteria"]) ?? string.Empty);  
     string inSearch = Request.QueryString["inSearch"];  
     if (!string.IsNullOrEmpty(inSearch))  
     {  
       int iSearch = int.Parse(inSearch);  
       var cacheName = HttpContext.Current.Session.SessionID.ToString() + "_SearchAttribute";  
       Dictionary<int, SearchAttribute> searchHistory = new Dictionary<int, SearchAttribute>();  
       var cacheSA = HttpContext.Current.Cache.Get(cacheName);  
       if (cacheSA != null)  
         searchHistory = (Dictionary<int, SearchAttribute>)cacheSA;  
       if (searchHistory.Count > 0)  
       {  
         SearchAttribute sa = searchHistory[iSearch];  
         if (sa == null)  
           return;  
         if (!string.IsNullOrEmpty(sa.idAttribute))  
           idAttribute = int.Parse(sa.idAttribute);  
         strFilterCriteria= sa.strFilterCriteria;               
       }  
     }  
    // Use 'strFilterCriteria' variable to store the currently filtered id list and pass it to the relevant method to get the result.  
 }  


Now each time the "Search" is clicked, it stores the search criteria in the cache and reload with inSearch as a query string parameter. There is no page postback and no document expiration happens. Each time it loads a fresh copy with the idSearch in the query string as "?inSearch=1" or "?inSearch=2" ..etc. The number increases on each Click to Search button. Navigate backward will retrieve the relevant result from the cached dictionary.

So, it is easy. Just a simple solution. :)

Happy coding... :)