Monday, September 12, 2011

ASP.NET MVC 3 - jQuery template tag inside Url.Action method

In this post we're going to deal with handling the jQuery template tag inside the Action method of the UrlHelper class(Url.Helper). To get a basic idea on jQuery templates and its implementation in ASP.NET MVC 3, have a glimpse on my previous posts ASP.NET MVC 3 and jQuery templates part1 and part2. Also for easier understanding I'll be using the same example explained in the post part2.

Let us consider a scenario to display list of members in a table format where each row represents each member and their related details represented in the subsequent columns. Let the final column represent a view hyperlink which onclick calls the action method MemberDetails of the ProjectMemberDetails with the id(EmployeeId) as parameter.

So our focus is to create a hyperlink with href something like "..../ProjectMemberDetails/MemberDetails/E1001", where "E1001" corresponds to the employee id. Also note that my map route is specified in such a way that the URL accepts id parameter.

Snippet 1: (Global.asax.cs - Default map route)

routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}/{id}", // URL with parameters
    new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

Following is my controller holding the related actions.

Snippet 2: (Controller)

public class ProjectMemberDetailsController : Controller
{
    public ActionResult ProjectMemberListForTemplateCheck(string experience)
    {
        return View("JqueryTemplateTagInAction");
    }

    [HttpPost]
    public JsonResult ProjectMemberListForTemplateCheckJson(string experience)
    {
        SampleData sampleData = new SampleData();

        if (string.IsNullOrEmpty(experience) || experience.Equals("All"))
        {
            return Json(sampleData.ProjectMemberList, JsonRequestBehavior.AllowGet);
        }
        else
        {
            return Json(sampleData.GetProjectMembersWithExpGreaterThan(int.Parse(experience)), JsonRequestBehavior.AllowGet);
        }
        
    }

    public ActionResult MemberDetails(string id)
    {
        SampleData sampleData = new SampleData();

        return View("MemberDetails", (object)sampleData.GetProjectMemberDetails(id));
    }
}

Snippet for SampleData is same as in ASP.NET MVC 3 and jQuery templates part2.

Now just concentrate on the template part for building the hyperlink.

Snippet 3: (template part with template tag inside @Url.Action(....))

<script id="memberDetailTemplate" type="text/x-jquery-tmpl">
    <tr>
        <td>
            ${EmployeeName}
        </td>
        <td>
            ${Experience}
        </td>
        <td>
        {{if Designation == 'Tech Lead'}}
        <span style="color:Green; font-weight: bold;">${Designation}</span>
        {{else}}
        <span style="color:Blue;">${Designation}</span>
        {{/if}}            
        </td>
        <td style="text-align:center;">
            <a href="@Url.Action("MemberDetails", "ProjectMemberDetails", new { id = "${EmployeeId}" })">View</a>
        </td>
    </tr>  
</script>


The idea is when the view engine renders the page, the href should be rendered like "..../ProjectMemberDetails/MemberDetails/${EmployeeId}", which on applying templ function with data, the "${EmployeeId}" should be replaced with the employee id.

Upon executing the application with the above template as specified in the Snippet 3, the href is rendered as "..../ProjectMemberDetails/MemberDetails/%24%7BEmployeeId%7D".

What went wrong here? Have close look again. Yes, the string "${EmployeeId}" is url encoded and represented as is, which the jQuery template (.tmpl method)  is unable to detect and parse it. So once the specific part of the URL is rendered as "${EmployeeId}" instead of "%24%7BEmployeeId%7D", the jQuery template will parse and evaluate the template tag correctly and we'll get the expected result.

In order to achieve this, we need to apply the url decode(for attaining the reverese effect) over the resultant url action(Url.Action(....)).

Snippet 4:

<a href="@HttpUtility.UrlDecode(Url.Action("MemberDetails", "ProjectMemberDetails", new { id = "${EmployeeId}" }))">View</a>


Following is the full version of the code.

Snippet 5:

@model string
@{
    ViewBag.Title = "Jquery template tag inside Url.Action";
}
<h2>
    Jquery template tag inside Url.Action</h2>
@if (false)
{
    <script type="text/javascript" src="../../Scripts/jquery-1.6.2-vsdoc.js"></script>
}
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>

<script type="text/javascript" src="@Url.Content("~/Content/MVC3TestApp2/Scripts/jquery.tmpl.js")"></script>
<script id="memberDetailTemplate" type="text/x-jquery-tmpl">
    <tr>
        <td>
            ${EmployeeName}
        </td>
        <td>
            ${Experience}
        </td>
        <td>
        {{if Designation == 'Tech Lead'}}
        <span style="color:Green; font-weight: bold;">${Designation}</span>
        {{else}}
        <span style="color:Blue;">${Designation}</span>
        {{/if}}            
        </td>
        <td style="text-align:center;">
            <a href="@HttpUtility.UrlDecode(Url.Action("MemberDetails", "ProjectMemberDetails", new { id = "${EmployeeId}" }))">View</a>
        </td>
    </tr>  
</script>
<script type="text/javascript">
    $(document).ready(function () {
        $.ajax({ type: "POST", url: '@Url.Action("ProjectMemberListForTemplateCheckJson", "ProjectMemberDetails")', success: jsonProjectMemberListSuccessCallback });
    });

    function jsonProjectMemberListSuccessCallback(data) {
        var targetElement = $('#memberData');
        targetElement.empty();

        $("#memberDetailTemplate").tmpl(data).appendTo('#memberData');
    }
</script>
<div id="loadingFeedback" style="display: none; color: Red; font-weight: bold;">
    <p>
        Loading details....
    </p>
</div>
@using (Ajax.BeginForm(new AjaxOptions
{
    LoadingElementId = "loadingFeedback",
    OnSuccess = "jsonProjectMemberListSuccessCallback",
    Url = Url.Action("ProjectMemberListForTemplateCheckJson", "ProjectMemberDetails")
}))
{
    <table border="1" cellpadding="0" cellspacing="0" >
        <thead>
            <tr>
                <th>
                    Member Name
                </th>
                <th>
                    Experience
                </th>
                <th>
                    Designation
                </th>
                <th>
                    View Details
                </th>
            </tr>
        </thead>
        <tbody id="memberData">
        </tbody>
    </table>
    <p>
        @Html.Label("", "Member with experience greater than : ")
        @Html.DropDownList("experience", new SelectList(new[] { "All", "1", "2", "3", "4", "5", "6", "7", "8", "9" }, (Model ?? "All")))
        <input type="submit" name="Submit" value="Submit" />
    </p>   
     
}

Creative Commons License
This work by Tito is licensed under a Creative Commons Attribution 3.0 Unported License.