Saturday, February 18, 2012

MVC Masked Input

One can use jquery to mask the inputs.There are quiet a few good plugins available for this e.g. http://digitalbush.com/projects/masked-input-plugin/

Only drawback to this approach is we need to link jquery function for each input to be masked.
Alternate approach can be creating a data annotation for mask and use it on model/viwModel.

Step 1: Create a mask attribute
Common\CustomAttributes\MaskAttribute.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using System.Web.Mvc;
  6.  
  7. namespace MvcLists.Common.CustomAttributes
  8. {
  9.     [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
  10.     public class MaskAttribute:Attribute,IMetadataAware
  11.     {
  12.         private string _mask = string.Empty;
  13.         public MaskAttribute(string mask)
  14.         {
  15.             _mask = mask;
  16.         }
  17.  
  18.         public string Mask
  19.         {
  20.             get { return _mask; }
  21.         }
  22.  
  23.         private const string ScriptText = "<script type='text/javascript'>" +
  24.                                            "$(document).ready(function () {{" +
  25.                                            "$('#{0}').mask('{1}');}});</script>";
  26.  
  27.         public const string templateHint = "_maskedInput";
  28.  
  29.         private int _count;
  30.  
  31.         public string Id
  32.         {
  33.             get { return "maskedInput_" + _count; }
  34.         }
  35.  
  36.         internal HttpContextBase Context
  37.         {
  38.             get { return new HttpContextWrapper(HttpContext.Current); }
  39.         }
  40.  
  41.         public void OnMetadataCreated(ModelMetadata metadata)
  42.         {
  43.             var list = Context.Items["Scripts"] as IList<string> ?? new List<string>();
  44.             _count = list.Count;
  45.             metadata.TemplateHint = templateHint;
  46.             metadata.AdditionalValues[templateHint] = Id;
  47.             list.Add(string.Format(ScriptText, Id, Mask));
  48.             Context.Items["Scripts"] = list;
  49.         }
  50.     }
  51. }
Step 2:Create a partial view in EditorTemplates and add an extension method for ViewDataDictionary to get the attribute for the property
Views\Shared\EditorTemplates\_mask.cshtml
  1. @using MvcLists.Common.CustomAttributes
  2. @model System.String
  3. @{ var maskedInput = ViewData.GetModelAttribute<MaskAttribute>();
  4.    if (maskedInput != null)
  5.    {
  6.         <div class="editor-label">
  7.             @Html.LabelForModel()
  8.         </div>
  9.         <div class="editor-field">
  10.             @Html.TextBoxFor(m => m, new { id = ViewData.ModelMetadata.AdditionalValues[MaskAttribute.templateHint] })
  11.         </div>
  12.    }
  13. }
Common\MvcExtensions\ViewDataExtensions.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Web;
  5. using System.Web.Mvc;
  6.  
  7. namespace MvcLists.Common.MvcExtensions
  8. {
  9.     public static class ViewDataExtensions
  10.     {
  11.         public static TAttribute GetModelAttribute<TAttribute>(this ViewDataDictionary viewData,bool inherit=false) where TAttribute:Attribute
  12.         {
  13.             if(viewData==null) throw new ArgumentException("ViewData");
  14.             var containerType = viewData.ModelMetadata.ContainerType;
  15.             return
  16.                 ((TAttribute[])
  17.                  containerType.GetProperty(viewData.ModelMetadata.PropertyName).GetCustomAttributes(typeof (TAttribute),
  18.                                                                                                     inherit)).
  19.                     FirstOrDefault();
  20.                     
  21.         }
  22.     }
  23. }
Common\HtmlHelpers\HtmlHelpers.cs
  1. public static IHtmlString RenderScripts(this HtmlHelper htmlHelper)
  2.         {
  3.             var scripts = htmlHelper.ViewContext.HttpContext.Items["Scripts"] as IList<string>;
  4.             if (scripts != null)
  5.             {
  6.                 var builder = new StringBuilder();
  7.                 foreach (var script in scripts)
  8.                 {
  9.                     builder.AppendLine(script);
  10.                 }
  11.                 return new MvcHtmlString(builder.ToString());
  12.             }
  13.             return null;
  14.         }
Step 3: Register this new extension in web.config of Views folder.
Web.config
  1.  
  2.   < system.web.webPages.razor >
  3.     < host factoryType= "System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  4.     < pages pageBaseType= "System.Web.Mvc.WebViewPage" >
  5.       < namespaces >
  6.         < add namespace= "System.Web.Mvc" />
  7.         < add namespace= "System.Web.Mvc.Ajax" />
  8.         < add namespace= "System.Web.Mvc.Html" />
  9.         < add namespace= "System.Web.Routing" />
  10.         < add namespace= "MvcLists.Common.MvcExtensions" />
  11.       </ namespaces >
  12.     </ pages >
  13.   </ system.web.webPages.razor >
Step 4: Use this attribute on Model/ViewModels property.
Models\Person.cs
  1. public class Person
  2.     {
  3.         [Tooltip("Enter First Name")]
  4.         public string FirstName { get; set; }
  5.         [Tooltip("Enter Last Name")]
  6.         public string LastName { get; set; }
  7.         [Tooltip("Enter SSN")]
  8.         [Mask("999-99-9999")]
  9.         public string SSN { get; set; }
  10.         [Tooltip("Enter Age")]
  11.         [Mask("99")]
  12.         public string Age { get; set; }
  13.         [Mask("999-999-9999")]
  14.         [Tooltip("Enter Phone")]
  15.         public string Phone { get; set; }
  16.         [Mask("99999-9999")]
  17.         [Tooltip("Enter Zip Code")]
  18.         public string ZipCode { get; set; }
  19.         [Mask("9999-9999-9999-9999")]
  20.         [Tooltip("Enter Credit Card")]
  21.         public string CreaditCard { get; set; }
  22.  
  23.     }
Step 5: Render the UI.
Views\Person\Index.cshtml
  1. @using (Html.BeginForm("Index","Person"))
  2. {
  3.     @Html.ValidationSummary(true)
  4.     <fieldset>
  5.         <legend>Person</legend>
  6.         <div class="editor-label">
  7.             @Html.LabelFor(model => model.FirstName)
  8.         </div>
  9.         <div class="editor-field">
  10.             @Html.TextBoxFor(model => model.FirstName,new{title=@Html.TooltipFor(x=>x.FirstName)})
  11.             @Html.ValidationMessageFor(model => model.FirstName)
  12.         </div>
  13.         <div class="editor-label">
  14.             @Html.LabelFor(model => model.LastName)
  15.         </div>
  16.         <div class="editor-field">
  17.             @Html.TextBoxFor(model => model.LastName,new{title=Html.TooltipFor(x=>x.LastName)})
  18.             @Html.ValidationMessageFor(model => model.LastName)
  19.         </div>
  20.         @Html.EditorFor(x => x.SSN)
  21.         @Html.EditorFor(x => x.Age)
  22.         @Html.EditorFor(x => x.Phone)
  23.         @Html.EditorFor(x => x.CreaditCard)
  24.         @Html.EditorFor(x => x.ZipCode)
  25.         <p>
  26.             <input type="submit" value="Create" />
  27.         </p>
  28.     </fieldset>
  29. }
Output:

4 comments:

Anonymous said...

where does the IHtmlString RenderScripts comes into place?

Shailesh Dhekne said...

That I generally use to add dynamic script blocks.

Unknown said...

Don't know if you got my last comment but I couldn't see where you use the renderscript and what class the method belongs to.

Shailesh Dhekne said...

I will update my sample code which uses RenderScripts function.