Showing posts with label razor. Show all posts
Showing posts with label razor. Show all posts

Friday, August 10, 2012

Master-detail with knockout in asp.net MVC 3

In this post we will be discussing creating a master detail relationship with Knockout.js in asp.net MVC 3. For this let’s take a simple view model like below-
    public class PersonViewModel
    {
        public int PersonID { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Sex { get; set; }
        public int Age { get; set; }
        public string Address { get; set; }
    }
How we will be starting the problem is that, we will be retrieving a list of person with id, and name only and present the data in a HTML table. And on click of the table row we will be showing detail of the person with the additional information.

If we are not aware of knockout we can here and download and study the it.

For this example we will be using the following controller method to retrieve some in memory data-
public ActionResult Index()
{
    List<PersonViewModel> data = new List<PersonViewModel>();
    for (int i = 0; i < 10; i++)
        data.Add(new PersonViewModel() {PersonID=i, FirstName = "First Name " + i.ToString(), LastName = "Last Name " + i.ToString() });
    return View(data);
}
Next step is to prepare UI for the problem. First we will take the reference of jQuery and knockout file like below-
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript"></script>
<script src="http://knockoutjs.com/js/knockout-2.1.0.js" type="text/javascript"></script>
And we will be using the following HTML for displaying the person list and the detail-
<table>
    <tr><th>First Name</th><th>Last Name</th></tr>
    <tbody >
    <tr id="PersonID" style="cursor:pointer" >@*repeat the row to display all the person*@
        <td >First name of the person</td>
        <td >Last name of the person</td>
    </tr>
    </tbody>
</table>
<p data-bind="with: personViewModel.selectedItem">
    <b>Name:</b> <span >First name of the person</span> &nbsp;<span >Last name of the person</span><br />
    <b>Age:</b> <span >Age of the person</span> <br />
    <b>Sex:</b> <span >Sex of the person</span> <br />
    <b>Address:</b> <span >Address of the person</span> <br />
</p>
As you can see that the controller method is returning List<PersonModelView> as the type of the strongly type view, so, we will have the following model directive in the view-
@model List<KnockoutMasterDetail.Models.PersonViewModel>
This will give generic list of Person data. But as per out requirement of knockout we need to convert this list to JavaScript array. We can do this by the following code-
var persons = new Array();
@foreach (var d in Model)
{
    <text>persons.push({PersonID:</text>@d.PersonID<text>, FirstName:"</text>@d.FirstName<text>", LastName:"</text>@d.LastName<text>"});</text>
}
The above code will be inside the script tag in the view. And it’s simply looping the person list and creating an array of person object like {PersonID:1, FirstName:”First Name 1”, LastName:”Last name 1” }. Now we have our person array ready. Next let’s create view model to be used with knockout.
function personModel() {
    var self = this;
    self.persons = ko.observableArray(persons);
    self.selectedItem = ko.observable();
    self.getDetail = function(item) { 
      //to be implemented  
    }
}
personViewModel = new personModel();
In the above JavaScript function, we are adding this line self.persons = ko.observableArray(persons);. What it means is that we are directing the knockout that the persons array is an observable. That mean if there is any change in the array then its corresponding UI will also be changed automatically. For this example, if we do not make the array as observable, it will work as we are not expecting any change in the array. So, we can simply have self.persons = persons;. But we need the next statement, as on clicking a row we need to change the detail. To activate the viewmodel to work with knockout, we need to register the viewmodel. We can do this by the following like-
ko.applyBindings(personViewModel);
Just this registration will not work. We need to make the necessary changes in the HTML.

Now self.persons is an array and we need to make changes in the HTML table to link this array to the HTML table. We can do this by following-
    <tbody data-bind="foreach: personViewModel.persons">
    <tr data-bind="click:personViewModel.getDetail, attr: {id: PersonID}" style="cursor:pointer" >
        <td data-bind="text: FirstName"></td>
        <td data-bind="text: LastName"></td>
    </tr>
    </tbody>
What we are doing here is adding data-bind attribute that is related to knockout. Whenever we will make any change in the array, knockout will use this attribute to make changes back to the UI. In the first line we are using foreach to loop through all the items of the array persons and putting the values of the array item to row. Next line we have click:personViewModel.getDetail, attr: {id: PersonID}. In first part of it we are associating click event of the row to a function in the viewmodel named getDetail. In the second part we are assigning id of the row to PersonID. And in the remaining two lines we are assigning first name and last name to two cells.

Next we need to modify the detail section and we need to associate this to observable selectedItem in the model and we can do this by altering the HTML as below-
<p data-bind="with: personViewModel.selectedItem">
    <b>Name:</b> <span data-bind="text: FirstName"></span> &nbsp;<span data-bind="text: LastName"></span><br />
    <b>Age:</b> <span data-bind="text: Age"></span> <br />
    <b>Sex:</b> <span data-bind="text: Sex"></span> <br />
    <b>Address:</b> <span data-bind="text: Address"></span> <br />
</p>
So, now whenever we will click the row it will call the function getDetail in the model. Currently the function is empty. Let’s first have a controller method that returns the detail of the person like below-
        public JsonResult GetDetail(int personID)
        {
            return Json(new PersonViewModel()
            {
                FirstName = "FirstName " + personID.ToString(),
                LastName = "LastName " + personID.ToString(),
                Sex = "Sex " + personID.ToString(),
                Age = personID,
                Address = "Address " + personID.ToString()
            });
        }
Here we are just building an in memory detail. And we can call the controller method like-
    self.getDetail = function(item) { 
        $.ajax({
            url:  '@Url.Action("GetDetail","Person")',
            data: { personID: item.PersonID },
            type: "POST",
            success: function (response) {
                self.selectedItem(response); 
            },
            error:function(x,y,z){debugger;}
        });
    }
Inside the success method we are getting the result of the ajax call and assigning the result to the selectedItem(self.selectedItem(response);). As it’s an observable, knockout will automatically update the corresponding HTML for detail. That’s all. You can download the source code from this link.

Sunday, June 3, 2012

Ajax data paging with dataTables.net jQuery plugin in asp.net MVC 3

In this post we will be discussing how to do ajax based server side data paging with dataTables.net jQuery plugin in asp.net MVC 3. To continue this lets check following post –

http://datatables.net/examples/data_sources/ajax.html

This is a basic way to access ajax data paging. In this sample all the data needed is retrieved and paging is done in browser memory. But we need more sophisticated approach. That is separate ajax call for each page index change. We can follow the following post for this-

http://datatables.net/examples/data_sources/server_side.html

This is a PHP based code. Let’s do in MVC. Before starting, let’s do some background study on the same.

First case: Let’s see the request on the IE developer tool network tab. If we visit request header and request value we can get data like below-

/examples/examples_support/server_processing.php?sEcho=1&iColumns=5&sColumns=&iDisplayStart=0&iDisplayLength=10&mDataProp_0=0&mDataProp_1=1&mDataProp_2=2&…………….. The bolded text query strings are the required ones. The additional query string can be used are we need but these are out of scope for the current sample.

Second case: Now let’s go to the Response Body. We can get the result like below-

{"sEcho": 1, "iTotalRecords": 57, "iTotalDisplayRecords": 57, "aaData": [ ["Gecko","Firefox 1.0","Win 98+ / OSX.2+","1.7","A"],["Gecko","Firefox 1.5","Win 98+ / OSX.2+","1.8","A"],["Gecko","Firefox 2.0","Win 98+ / OSX.2+","1.8","A"],["Gecko","Firefox 3.0","Win 2k+ / OSX.3+","1.9","A"],["Gecko","Camino 1.0","OSX.2+","1.8","A"],["Gecko","Camino 1.5","OSX.3+","1.8","A"],["Gecko","Netscape 7.2","Win 95+ / Mac OS 8.6-9.2","1.7","A"],["Gecko","Netscape Browser 8","Win 98SE+","1.7","A"],["Gecko","Netscape Navigator 9","Win 98+ / OSX.2+","1.8","A"],["Gecko","Mozilla 1.0","Win 95+ / OSX.1+","1","A"]] }

Here we can see that it returning the sEcho, iTotalRecords, iTotalDisplayRecords, aaData where first three values are integer type and last one is the array of data rows. We can visualize this in the image below-



Now if we think this in terms of MVC model binding things will get clear. We can use a model data class that except the required input parameters and return a JSON result of the desired format. Lets start with the source code-

ViewModel-
    public class TableParameter
    {
        public string sEcho { get; set; }
        public int iDisplayStart { get; set; }
        public int iDisplayLength { get; set; }
    }
In case of the parameter we are using only three here as explained in the first case. We can use other parameters as per our need.

View-
@{
    ViewBag.Title = "Pagination_With_Data_Table_issue";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
    Pagination_With_Data_Table_issue</h2>
    
<style type="text/css" title="currentStyle">
   @@import "http://datatables.net/release-datatables/media/css/demo_page.css";
   @@import "http://datatables.net/release-datatables/media/css/demo_table.css";
  </style>
<script type="text/javascript" language="javascript" src="http://datatables.net/release-datatables/media/js/jquery.js"></script>
<script type="text/javascript" language="javascript" src="http://datatables.net/release-datatables/media/js/jquery.dataTables.js"></script>
<script type="text/javascript" charset="utf-8">
    $(document).ready(function () {
        $('#example').dataTable({
            "bServerSide": true,
            "bProcessing": true,
            "sPaginationType": "full_numbers",
            "sAjaxSource": '@Url.Action("Pagination_With_Data_Table_issue_getData")'
        });
    });
</script>
<div id="dynamic">
<table cellpadding="0" cellspacing="0" border="0" class="display" id="example">
 <thead>
  <tr>
   <th width="20%">Rendering engine</th>
   <th width="25%">Browser</th>
   <th width="25%">Platform(s)</th>
   <th width="15%">Engine version</th>
   <th width="15%">CSS grade</th>
  </tr>
 </thead>
 <tbody>
 </tbody>
 <tfoot>
  <tr>
   <th>Rendering engine</th>
   <th>Browser</th>
   <th>Platform(s)</th>
   <th>Engine version</th>
   <th>CSS grade</th>
  </tr>
 </tfoot>
</table>
</div>
While initialization of the table plugin, we are using bServerSide and bProcessing to true, sPaginationType to full_number and finally sAjaxSource to controller action.

Controller action-
        public ActionResult Pagination_With_Data_Table_issue()
        {
            return View();
        }

        public ActionResult Pagination_With_Data_Table_issue_getData(TableParameter param)
        {
            var data = new[]{   
                new []{"Trident test","Internet Explorer 4.0","Win 95+","4","X"},
             new []{"Trident","Internet Explorer 5.0","Win 95+","5","C"},
             new []{"Trident","Internet Explorer 5.5","Win 95+","5.5","A"},
             new []{"Trident","Internet Explorer 6","Win 98+","6","A"},
             new []{"Trident","Internet Explorer 7","Win XP SP2+","7","A"},
             new []{"Trident","AOL browser (AOL desktop)","Win XP","6","A"},
             new []{"Gecko","Firefox 1.0","Win 98+ / OSX.2+","1.7","A"},
             new []{"Gecko","Firefox 1.5","Win 98+ / OSX.2+","1.8","A"},
             new []{"Gecko","Firefox 2.0","Win 98+ / OSX.2+","1.8","A"},
             .
                .
                .
                .
                .
            };

            var pagedData = data.Skip(param.iDisplayStart).Take(param.iDisplayLength);

            return Json(new
            {
                sEcho = param.sEcho,
                iTotalRecords = data.Count(),
                iTotalDisplayRecords = data.Count(),
                aaData = pagedData
            }, JsonRequestBehavior.AllowGet);
        }
In this case we are using the input parameter for filtering the correct data. Finally the return JSON matches the format explained in the second case. The array of data we have prepared form the following link-

http://datatables.net/examples/examples_support/json_source.txt

This explains everything. Let me know for any query. You can download the code here.

Thursday, May 24, 2012

Show/hide detail in a table using jQuery and MVC 3

This is another simple example where we will be showing some record in a table and there will be some plus/ minus image in each row and on click of the image we will be showing some detail of the record. And we will toggle the images and data. To do that lets have the following view models-
namespace MVCRazor.ViewModel
{
    public class TableRowItemViewlModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public TableRowItemDetailViewlModel Detail { get; set; }
    }
    public class TableRowItemDetailViewlModel
    {
        public string Address { get; set; }
        public string Sex { get; set; }
        public string Nationality { get; set; }
    }
}
The model is simple and self explanatory. And we are going to use the following view to display the tabular data and the controller to read the data. In the sample we have taken some in memory object for data access-
namespace MVCRazor.Controllers
{
    public class TableRowDetailController : Controller
    {
        List<TableRowItemViewlModel> data = new List<TableRowItemViewlModel>();
        public TableRowDetailController()
        {
            data.AddRange(new List<TableRowItemViewlModel>(){
            new TableRowItemViewlModel(){ Id =1,Name="Name 1", Email ="Email 1", Detail=new TableRowItemDetailViewlModel(){Address="Address 1", Nationality="Nationality 1", Sex="Sex 1"}},
            new TableRowItemViewlModel(){ Id =2,Name="Name 2", Email ="Email 2", Detail=new TableRowItemDetailViewlModel(){Address="Address 2", Nationality="Nationality 2", Sex="Sex 2"}},
            new TableRowItemViewlModel(){ Id =3,Name="Name 3", Email ="Email 3", Detail=new TableRowItemDetailViewlModel(){Address="Address 3", Nationality="Nationality 3", Sex="Sex 3"}},
            new TableRowItemViewlModel(){ Id =4,Name="Name 4", Email ="Email 4", Detail=new TableRowItemDetailViewlModel(){Address="Address 4", Nationality="Nationality 4", Sex="Sex 4"}},
            new TableRowItemViewlModel(){ Id =5,Name="Name 5", Email ="Email 5", Detail=new TableRowItemDetailViewlModel(){Address="Address 5", Nationality="Nationality 5", Sex="Sex 5"}},
            new TableRowItemViewlModel(){ Id =6,Name="Name 6", Email ="Email 6", Detail=new TableRowItemDetailViewlModel(){Address="Address 6", Nationality="Nationality 6", Sex="Sex 6"}},
            new TableRowItemViewlModel(){ Id =7,Name="Name 7", Email ="Email 7", Detail=new TableRowItemDetailViewlModel(){Address="Address 7", Nationality="Nationality 7", Sex="Sex 7"}},
            new TableRowItemViewlModel(){ Id =8,Name="Name 8", Email ="Email 8", Detail=new TableRowItemDetailViewlModel(){Address="Address 8", Nationality="Nationality 8", Sex="Sex 8"}},
            new TableRowItemViewlModel(){ Id =9,Name="Name 9", Email ="Email 9", Detail=new TableRowItemDetailViewlModel(){Address="Address 9", Nationality="Nationality 9", Sex="Sex 9"}},
        });
        }

        public ActionResult Index()
        {
            return View((from c in data
                         select new TableRowItemViewlModel() { Id = c.Id, Name = c.Name, Email = c.Email }).ToList());
        }
    }
}
@model List<MVCRazor.ViewModel.TableRowItemViewlModel>
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
<style>
.tbl {border:1px solid gray; }
.tbl td{padding:5px 10px 5px 10px; }
.tbl th{padding:5px 10px 5px 10px;background-color:Gray;color:White }
.pm{ cursor:pointer;}
.plus
{
    background:url('http://www.quimicasuiza.com/images/extras/plus-minus.gif') 0 -16px;
    display:block;
    width:16px;
    height:16px;
}
.minus
{
    background:url('http://www.quimicasuiza.com/images/extras/plus-minus.gif') 0 0;
    display:block;
    width:16px;
    height:16px;
}
.detail
{
    background-color:#d4d0d8;    
    padding:7px;
}
</style>
<table class="tbl" cellpadding="0" cellspacing="0">
<tr><th>&nbsp;</th><th>Id</th><th>Name</th><th>Email</th></tr>
@foreach (var row in Model)
{ 
    <tr><td><span class="pm plus"></span></td><td>@row.Id</td><td>@row.Name</td><td>@row.Email</td></tr>
}
</table>
The above controller and the view will display the data like below-
We are going to use the following script to retrieve data from the controller and show hide the details.
<script type="text/javascript">
    var colCount;
    $(document).ready(function () {
        colCount = $(".tbl tr:first").children().length;
        $("tr:odd").css("background-color", "#f0f3f4");
        $(".pm").click(function () {
            if ($(this).hasClass("plus")) {
                $(this).removeClass("plus").addClass("minus");
                if (!$(this).closest("tr").next().hasClass("detail")) {
                    getDetail($(this).closest("tr"));
                }
                else
                    $(this).closest("tr").next().show();
            }
            else {
                if ($(this).closest("tr").next().hasClass("detail"))
                    $(this).closest("tr").next().hide();
                $(this).removeClass("minus").addClass("plus");
            }
        });
    });
    function getDetail(row) {
        var rowNew = $("<tr class='detail'><td colspan=" + colCount + "></td></tr>");
        $.ajax({
            type: "get",
            timeout: 30000,
            data: "id=" + row.find("td:eq(1)").html(),
            url: '@Url.Action("GetDetail")',
            dataType: "json",
            contentType: "application/json; charset=utf-8",
            success: function (result) {
                rowNew.find("td").append("<b>Address </b>: " + result.Address);
                rowNew.find("td").append("<br /><b>Sex </b>: " + result.Sex);
                rowNew.find("td").append("<br /><b>Nationality </b>: " + result.Nationality);
                row.after(rowNew);
            },
            error: function (a, b, c) {
                debugger;
            }
        });
    }
</script>
In this we are showing plus/minus button as span with CSS class pm. On click of the span we are checking whether it has a class named plus . Based on that in line 8 and 18 we are toggling the classes plus/minus.

If the line has class called plus, in the line 9 we are checking whether the next row of the container row has a CSS class called detail. Suppose it has that class then we are simply showing that row(in line 13). Otherwise we are calling a function called getDetail in line 10 by passing the current row. In that function we are building a row in memory with same columnspan as of the table row. Then we are passing the id of row and doing a ajax call to the controller to get the data. In the success of the ajax call we are adding the newly created row next to the current row(in line 37).

On click of span, if the condition check in the line 7 does not have a class named plus, that means the detail is already loaded and open. We are simply hiding the detail in line(16-17).

The controller method for getting detail is as follows-
        public JsonResult GetDetail(int id)
        {
            return Json((from c in data
                         where c.Id == id
                         select c.Detail).First(), JsonRequestBehavior.AllowGet);
        }
In the image we can see things in action. If we click the button multiple time, the ajax call will happen one time only. And on subsequent click it just show and hide the data.

Monday, May 14, 2012

Conditional if in attribute in a HTML element using razor syntax.

This is little interesting. While on asp.net forum, I came across a requirement of conditional if in deciding css class of a HTML tag. I thought of writing this as a blog post.

Lets take a small section of the following code-
@for (int i = 0; i < 10; i++)
{
    if (i%2 == 0)
    {
    <div class="a">
        Test
    </div>
    }
    else
    {
    <div class="b">
        Test
    </div>
    }
}
 
In the above code segment we are simply checking the odd and even and setting the css class as "a" or "b". But the code is not optimal. We can reduce the lines of code by the following-
@for (int i = 0; i < 10; i++)
{
    <div class="@(i % 2 == 0? 'a': 'b')">
        Test
    </div>
}