Showing posts with label jQuery templates. Show all posts
Showing posts with label jQuery templates. Show all posts

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>   
     
}

Sunday, August 14, 2011

ASP.NET MVC 3 and jQuery templates - Part2

In part 1, we saw the basic implementation of jQuery template with the help of html file. In this part we'll look into a simple implementation using ASP.NET MVC 3. Following few steps will define the steps I've followed to create the sample which helps readers to dive into the code with better understanding.

First created an empty ASP.NET MVC3 web application. Then created a model by adding an Employee class in the Models folder.

Snippet 1(Model - Employee)

public class Employee
{
    public string EmployeeId { get; set; }
    public string EmployeeName { get; set; }
    public int Experience { get; set; }
    public string Designation { get; set; }
}

For the purpose of this example have created a SampleData class in the Models folder.

Snippet 2 (SampleData)

public class SampleData
{
    #region Class Level Variables
    private List<Employee> projectMemberList;
    #endregion

    #region Properties
    public List<Employee> ProjectMemberList
    {
        get
        {
            if (projectMemberList == null)
            {
                LoadProjectMemberList();
            }
            return projectMemberList;
        }
        set { projectMemberList = value; }
    }
    #endregion

    #region Public Methods
    public IEnumerable<Employee> GetProjectMembersWithExpGreaterThan(int experience)
    {
        return ProjectMemberList.Where(emp => emp.Experience > experience);
    }
    #endregion

    #region Private Methods
    private void LoadProjectMemberList()
    {
        projectMemberList = new List<Employee>();

        projectMemberList.Add(new Employee { EmployeeId = "E1001", EmployeeName = "Member1", Experience = 7, Designation = "Project Lead" });
        projectMemberList.Add(new Employee { EmployeeId = "E1002", EmployeeName = "Member2", Experience = 4, Designation = "SSE" });
        projectMemberList.Add(new Employee { EmployeeId = "E1003", EmployeeName = "Member3", Experience = 9, Designation = "Tech Lead" });
        projectMemberList.Add(new Employee { EmployeeId = "E1004", EmployeeName = "Member4", Experience = 3, Designation = "SE" });
        projectMemberList.Add(new Employee { EmployeeId = "E1005", EmployeeName = "Member5", Experience = 5, Designation = "Team Lead" });

    }
    #endregion

}

Next in the Controllers folder have created a ProjectMemberDetailsController.

Snippet 3 (ProjectMemberDetailsController)

public class ProjectMemberDetailsController : Controller
{
    
    public ActionResult ProjectMemberList()
    {
        return View();
    }

    [HttpPost]
    public JsonResult ProjectMemberList(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);
        }

    }
}

Finally added a ProjectMemberList.cshtml file under the Views/ProjectMemberDetails folder.

Snippet 4 (ProjectMemberList)   

@model String
@{
    ViewBag.Title = "ProjectMemberList";
}
<h2>
    ProjectMemberList</h2>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>
<script src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.js"
    type="text/javascript"></script>

<script id="memberDetailTemplate" type="text/html">
    <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>
    </tr>  
</script>
<script type="text/javascript">
    $(document).ready(function () {
        $.ajax({ type: "POST", url: "ProjectMemberDetails/ProjectMemberList", 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("ProjectMemberList")
}))
{
    <table border="0" cellpadding="0" cellspacing="5">
        <thead>
            <tr style="font-weight: bold;">
                <td>
                    Member Name
                </td>
                <td>
                    Experience
                </td>
                <td>
                    Designation
                </td>
            </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>   
   
}

The ProjectMemberList view will automatically use the /Views/Shared/_Layout.cshtml as the layout/master page which is specified in the /Views/_ViewStart.cshtml file.

In order to run invoke the specific action by default, have modified the RegisterRoutes method of MvcApplication class in the Global.asax.cs file as follows:

Snippet 5 (Default Route - Global.asax.cs)

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

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

}

After doing the above changes, when the application is run, the URL ProjectMemberDetails/ProjectMemberList is invoked which in turn invokes the ProjectMemberList action in the ProjectMemberDetailsController. The ProjectMemberList() action returns the related view and this execution happens when the page is first rendered. When the page is rendered, the script we specified in the $(document).ready(...); will be executed. Since we've made a jQuery AJAX call over there with type "POST" and URL("ProjectMemberDetails/ProjectMemberList"), it causes an AJAX call over our action ProjectMemberList(string experience) with attribute [HttpPost] which returns the required result in JSON format. The jQuery script, handles the returned result with the success callback method we specified, which renders the data using the jQuery template on the client side.

The main thing need to be noted is the ProjectMemberList(string experience) action in the ProjectMemberDetailsController which returns the result in the JSON format(JsonResult). Also our view is designed in such a way that the specific action is invoked only using the AJAX call.

You might have also observed that, a predefined dropdown list and a submit button are accompanied inside the AJAX Form represented by @using (Ajax.BeginForm(..)){...} with relevant AjaxOptions as parameter. This AJAX Form ensures that every submit made through the Form will be made as AJAX request. Hence if any submit is made through the submit button click, the ProjectMemberList action specified in the AjaxOptions is invoked passing the option selected in the dropdown list. It is the ASP.NET MVC framework that renders the parameter value, since the parameter specified is same as the name of the dropdown list.

Summary
As a general practice, in the above specified scenarios, the first time when the view is rendered (as the AJAX call won't happen), we'll be using the partial view for rendering the templated region and as we used AJAX Forms, further submit will be directed as AJAX calls. But here, I've specified an example of making an AJAX request from jQuery inside the $(document).ready(...);, because several developers having questions about raising the AJAX request during the initial rendering of the view, which is also answered here.

References
http://api.jquery.com/jQuery.ajax/


Update - 05Nov2011 (Representing the implementation with {{each}} template tag and handling date)

Based on the question asked, I'll specify the modification need to be done to the snippets above in order to see the implementation of {{each}} template tag and displaying date values.

Scenarios included : Have added two more columns Technical Expertise and Date of Joining for display and modified the sample data based on that.
In the case of technical expertise, I receive the list of string values and display each item in the list inside the div element.
In the case of joining date, the received date need to be converted to Date() type in javascript and then formatted accordingly. There exists several ways to achieve that. For this sample I'll use the way specified in one of the stackoverflow.com questions. Here are the refered links - http://stackoverflow.com/questions/206384/how-to-format-a-json-date and http://blog.stevenlevithan.com/archives/date-time-format.

Following are the changes need to be done in the above specified snippets to view the updated example.

Changes in Snippet 1(Model - Employee)
Add two more properties.

public class Employee
{
    public string EmployeeId { get; set; }
    public string EmployeeName { get; set; }
    public int Experience { get; set; }
    public string Designation { get; set; }
    public List<string> TechnicalExpertise { get; set; }
    public DateTime JoiningDate { get; set; }
}

Changes in Snippet 2 (SampleData)
Alter the LoadProjectMemberList methods as follows.

private void LoadProjectMemberList()
{
    projectMemberList = new List<Employee>();

    projectMemberList.Add(new Employee { EmployeeId = "E1001", EmployeeName = "Member1", Experience = 7, Designation = "Project Lead", TechnicalExpertise = new List<string> { "ASP.NET", "C#", "Silverlight" }, JoiningDate = DateTime.Now.AddYears(-4) });
    projectMemberList.Add(new Employee { EmployeeId = "E1002", EmployeeName = "Member2", Experience = 4, Designation = "SSE", TechnicalExpertise = new List<string> { "ASP.NET", "C#" }, JoiningDate = DateTime.Now.AddYears(-2) });
    projectMemberList.Add(new Employee { EmployeeId = "E1003", EmployeeName = "Member3", Experience = 9, Designation = "Tech Lead", TechnicalExpertise = new List<string> { "ASP.NET", "C#", "Silverlight" }, JoiningDate = DateTime.Now.AddYears(-6) });
    projectMemberList.Add(new Employee { EmployeeId = "E1004", EmployeeName = "Member4", Experience = 3, Designation = "SE", TechnicalExpertise = new List<string> { "ASP.NET", "VB.NET" }, JoiningDate = DateTime.Now.AddYears(-3) });
    projectMemberList.Add(new Employee { EmployeeId = "E1005", EmployeeName = "Member5", Experience = 5, Designation = "Team Lead", TechnicalExpertise = new List<string> { "ASP.NET", "C#" }, JoiningDate = DateTime.Now.AddYears(-4) });

}

Changes in Snippet 4 (ProjectMemberList)
Download and add the "date.format.js" file from http://blog.stevenlevithan.com/archives/date-time-format. Then add the script reference in the page.
In our case it is


<script type="text/javascript" src="@Url.Content("~/Content/MVC3TestApp2/Scripts/date.format.js")"></script>

Replace the related matching items with the following snippets.
Replacement 1:

<script id="memberDetailTemplate" type="text/html">
    <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>
        {{each(i,technology) TechnicalExpertise}}
            <div style="font-style:italic;border-width : 1px; border-style: dotted;">
            ${i + 1}. ${technology}
            </div>
        {{/each}}
        </td>
        <td>
            ${FormattedDate}
        </td>
    </tr>  
</script>

Replacement 2:

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

    targetElement.empty();
    
    for (var i in data) {
        var date = eval(data[i].JoiningDate.replace(/\/Date\((\d+)\)\//gi, "new Date($1)"));
        var formattedDate = date.format("m/dd/yyyy");
        data[i].FormattedDate = formattedDate;
    }

    $("#memberDetailTemplate").tmpl(data).appendTo('#memberData');
}


Replacement 3:

<table border="0" cellpadding="0" cellspacing="5">
    <thead>
        <tr>
            <th>
                Member Name
            </th>
            <th>
                Experience
            </th>
            <th>
                Designation
            </th>
            <th>
                Technical Expertise
            </th>
            <th>
                Date of Joining
            </th>
        </tr>
    </thead>
    <tbody id="memberData">
    </tbody>
</table>


Now in the resultant you can see the respecitve columns related values.

ASP.NET MVC 3 and jQuery templates - Part1

In .Net and jQuery, templates are nothing but specification of a format/structure in which the associated data need to be represented. Developers who worked with ASP.NET controls like GiridView, ListView, Repeater... would have felt the power of templates already. To achieve the power of templates in client side jQuery templates comes in a handy form.

It is easy to understand the core idea on the implementation of jQuery templates with the snippet in this post. To understand the complete details on jQuery template please visit here. Now I'll provide snippet which helps you to understand how to use the jQuery templates.

Snippet 1(General html snippet describing the core jQuery template implementation)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>jQuery template core implementation</title>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js"></script>
    <script id="memberDetailsTemplate" type="text/html">
            <tr>
                <td>
                    ${MemberName}
                </td>
                <td>
                    ${Designation}
                </td>
            </tr>

    </script>
    <script type="text/javascript">
        function applyTemplate() {
            var memberData = [
            { MemberName: "Member1", Designation: "Project Lead" },
            { MemberName: "Member2", Designation: "Tech Lead"}];

            $('#memberDetailsTemplate').tmpl(memberData).appendTo('#memberDetails');
        }

        $(document).ready(function () {
            applyTemplate();
        });
    </script>
</head>
<body>
    <table border="0" cellpadding="2" cellspacing="5">
        <thead>
            <tr style="font-weight:bold;">
                <td>
                    Member Name
                </td>
                <td>
                    Designation
                </td>
            </tr>
        </thead>
        <tbody id="memberDetails">
        </tbody>
    </table>
</body>
</html>


$('#memberDetailsTemplate').tmpl(memberData).appendTo('#memberDetails'); is the key statement that applies the template. Remaining are self explanatory. If the bound data is an array of object, then  the template will be repeated for each item in the array.

Three basic items required for applying jQuery template:
  1. template : Selector that specifies the template. $('#memberDetailsTemplate') in our case.
  2. data : Data to which the template need to be applied. memberData in our case.
  3. target : Selector that specifies the target element. ('#memberDetails') in our case.

.tmpl() is the method that help us achieving the template applied to the data. .appendTo() is one of the method that can be used in conjunction with the object(jQuery collection) returned from .tmpl().
So the statement specifies apply template $('#memberDetailsTemplate') over the data memberData and append the resultant to the ('#memberDetails').

Also, I've used the ${} template tag to read the value of the relevant property. Apart from this, there exists other template tags like {{if}}, {{else}}, {{each}}, {{html}}, {{tmpl}}, {{wrap}}. More details on those template tags can be got from http://api.jquery.com/category/plugins/templates/. We can also create named templates using .template() which you'll find helpful when using more number of templates frequently.

In this post, we just had an introduction for understanding the core idea behind jQuery templates. Part 2 focuses our topic of implementing the jQuery templates using ASP.NET MVC 3.

References
http://api.jquery.com/category/plugins/templates/
Creative Commons License
This work by Tito is licensed under a Creative Commons Attribution 3.0 Unported License.