How to update partial view using jquery ajax Part 2

This follows on from a previous post, Make sure you have read How to update partial view using jquery ajax before continuing or some of this may not make sense.

In the previous post the partial view was updated when a selection was made from a drop down list. When the main view first loaded there was no code to populate the partial view. The purpose here is to change the original code to allow the partial view to be loaded when the main view is initially loaded.

ViewModels
Create a new view model for the main index view. The CatalogueViewModel contains a DateTime and a list of products. This is the list which will be used to initially populate the partial view.

public class CatalogueViewModel
{
    public DateTime CatalogueDate { get; set; }
    // any other required properties
    public List<Product> Products { get; set; }
}

Main Index View
The main view has changed slightly, As a CatalogueViewModel model is now passed to the view the strongly-typed model declaration needs to change.

@model MVCUpdatePartial.Models.CatalogueViewModel

Also the date displayed is now taken from the view model.

Catalogue Date:
@Model.CatalogueDate.ToLongDateString()    
@Model.CatalogueDate.ToLongTimeString()

The select list for selecting the quantity of products to display now has an additional All option. This is to accommodate the fact that when the view loads initially the quantity is not specified and all products will initially be loaded.

<select id="PageSize">
    <option value="">All</option>
    <option value="1">1</option>
    <option value="2">2</option>
    <option value="3">3</option>
    <option value="4">4</option>
    <option value="5">5</option>
    <option value="6">6</option>
    <option value="7">7</option>
    <option value="8">8</option>
    <option value="9">9</option>
    <option value="10">10</option>
</select>

The div element where the partial view is loaded now has a command to explicitly render the partial view. This will render the view and display a list of products when the main view is initially loaded.

<div id="SelectedProducts">
    @{Html.RenderPartial("_ProductList", Model.Products);}
</div>

Controller
The Index (GET) Action method now passes a CatalogueViewModel to the Index view. Initially the products list is populated with all products (OK for this scenario as there are not that many).
Also the GetProducts (POST) Action method handles the situation where a null is passed in the quantity parameter. This will happen when the All option is selected from the select list.

public ActionResult Index()
{
    var catalogueVM = new CatalogueViewModel
    {
        CatalogueDate = DateTime.Now,
        Products = products
    };
    return View(catalogueVM);
}

[HttpPost]
public ActionResult GetProducts(int? quantity)
{
    var selectedProducts = products.Take(quantity ?? products.Count);
    return PartialView("_ProductList", selectedProducts);
}

Done!

How to update partial view using jquery ajax

I wanted to give a quick but clear example how to use jQuery to update a partial view, an example which could be easy to replicate.

We need a model
The model consists of product class with three properties; a product code, name and price.

public class Product
{
    [Required]
    public string Code { get; set; }
    [Required]
    public string Name { get; set; }
    [Required]
    [DataType(DataType.Currency)]
    public decimal UnitPrice { get; set; }
}

And a Controller
For simplicity there is no specific data layer or repository, the controller contains a global array of products. When a http post is made to the GetProducts ActionResult a parameter is passed specifying how many products to return.

public class HomeController : Controller
{
    private List<Product> products = new List<Product>() ;

    public HomeController()
    {
        products.Add(new Product { Code = "P001", Name = "Hair Dryer", UnitPrice = 10.50M });
        products.Add(new Product { Code = "P002", Name = "Carpet Cleaner", UnitPrice = 3.60M });
        products.Add(new Product { Code = "P003", Name = "Hair Remover", UnitPrice = 12.10M });
        products.Add(new Product { Code = "P004", Name = "Vacuum Cleaner", UnitPrice = 80.99M });
        products.Add(new Product { Code = "P005", Name = "Table", UnitPrice = 7.69M });
        products.Add(new Product { Code = "P006", Name = "Duvet Cover", UnitPrice = 12.89M });
        products.Add(new Product { Code = "P007", Name = "Towel", UnitPrice = 3.50M });
        products.Add(new Product { Code = "P008", Name = "Electric Oven", UnitPrice = 125.99M });
        products.Add(new Product { Code = "P009", Name = "Hair Straighteners", UnitPrice = 8.90M });
        products.Add(new Product { Code = "P010", Name = "Belt", UnitPrice = 3.50M });
    }

    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public ActionResult GetProducts(int quantity)
    {
        var selectedProducts = products.Take(quantity);
        return PartialView("_ProductList", selectedProducts);
    }
}

The Main View
mvcpartialviewupdate-main-view
Selecting from the dropdown list causes a change event the initiates the post to the server. If the post is successful the partial view is updated.
The date and time is displayed for comparison with the date and time on the partial view, they will be different.

<h2>Product Display</h2>
<div class="col-sm-6">
    @DateTime.Now.ToLongDateString()
    @DateTime.Now.ToLongTimeString()
<div class="panel panel-default">
<div class="panel-heading">
            Products</div>
<div class="panel-body">
<div class="form-group form-group-sm">
                <label class="control-label" for="PageSize">
                    How many products should be displayed?
                </label>
<div>
                    <select id="PageSize">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
</select></div>
</div>
<div id="SelectedProducts"></div>
</div>
</div>
</div>

jQuery AJAX
When a selection is made from the dropdown list the change event is triggered and a POST made to the server.

@section scripts{
    <script type="text/javascript">

        $(document).ready(function () {

            $("#PageSize").on("change", function () {
                var val = $('#PageSize').val();
                $.ajax({
                    url: "/Home/GetProducts",
                    type: "POST",
                    data: { quantity: val }
                })
                .done(function (partialView) {
                    $("#SelectedProducts").html(partialView);
                });
            });
        });
    </script>
}

And the Partial View (_ProductList.cshtml)
The partial view displays a list of products in a table. The POST date and time is displayed for comparison purposes.

@model IEnumerable<MVCUpdatePartial.Models.Product>
<div class="editor-label">
    Selected Products</div>
<table class="table">
    @foreach(var product in Model)
    {
<tr>
<td>@product.Code</td>
<td>@product.Name</td>
<td>@product.UnitPrice</td>
</tr>
}</table>
Updated: @DateTime.Now.ToLongDateString()
@DateTime.Now.ToLongTimeString()

After the POST
And how the view looks after the POST and the partial view is updated.
mvcpartialviewupdate-main-view-after-post

First look at Knockout Paged SimpleGrid

I have recently been looking at the knockoutjs library and what it can be used for. I started off writing a post on knockout grids. It was getting a little long so I have decided to break it up into a number of posts so they will be shorter and hopefully clearer.

In this first part we are going to take a look at a paged SimpleGird similar to this Paged Grid sample on the KO site.

The current knockout library is at version 3.0 and can be found here or via an easy to install nuget package.  The paged grid sample uses an additional library called knockout.simpleGrid.3.0.js available here.  It is not necessary, but I have local copies of the files and configured a bundle

bundles.Add(new ScriptBundle("~/bundles/knockoutjs").Include(
    "~/scripts/knockout-{version}.js",
    "~/scripts/knockout.simpleGrid.3.0.js"));

Paged Grid
The amount of code needed on the page to create a grid with paging abilities is not much as the code below demonstrates. Obviously the data would not usually be hard coded in the page, data would usually be retrieved from a database.

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<div data-bind='simpleGrid: gridViewModel'></div>

@section Scripts {
    @Scripts.Render("~/bundles/knockoutjs")

    <script type="text/javascript">

        var PagedGridModel = function (items) {

            this.items = ko.observableArray(items);

            this.gridViewModel = new ko.simpleGrid.viewModel({
                data: this.items,
                columns: [
                    { headerText: "Person ID", rowText: "employeeid" },
                    { headerText: "Title", rowText: "title" },
                    { headerText: "Forename", rowText: "forename" },
                    { headerText: "Surname", rowText: "surname" },
                    { headerText: "Start Date", rowText: "startdate" }
                ],
                pageSize: 3
            });
        }

        var gridData = [
            { employeeid: 1000, title: "Mr", forename: "Fred", surname: "Flinstone", startdate: "12 May 1953" },
            { employeeid: 1001, title: "Mr", forename: "Barney", surname: "Rubble", startdate: "20 Jun 1978" },
            { employeeid: 1002, title: "Ms", forename: "Belinda", surname: "Mason", startdate: "11 Nov 2004" },
            { employeeid: 1003, title: "Mrs", forename: "Mary", surname: "Moore", startdate: "5 Aug 1999" },
            { employeeid: 1004, title: "Mr", forename: "Harry", surname: "Hill", startdate: "1 Mar 1995" },
            { employeeid: 1005, title: "Dr", forename: "Carl", surname: "Flowers", startdate: "11 Sep 1988" },
            { employeeid: 1006, title: "Miss", forename: "Jenna", surname: "Pray", startdate: "5 Dec 2010" }
        ];
        ko.applyBindings(new PagedGridModel(gridData));
    </script>
}

The page size is set to 3, although it could be set to anything as appropriate. With a size of 3 I could keep the demo data to a minimum.

Because the grid is dynamically created you will not see the html markup of the table when viewing the page source, right-click and View source in IE or View page source in Chrome. Though if you inspect the page via the Developer Tool of you browser you can see how knockout has constructed the table.

<div data-bind="simpleGrid: gridViewModel">
    <table class="ko-grid" cellspacing="0">
        <thead>
            <tr data-bind="foreach: columns">
                <th data-bind="    text: headerText">Person ID</th>
                <th data-bind="    text: headerText">Title</th>
                <th data-bind="    text: headerText">Forename</th>
                <th data-bind="    text: headerText">Surname</th>
                <th data-bind="    text: headerText">Start Date</th>
            </tr>
        </thead>
        <tbody data-bind="    foreach: itemsOnCurrentPage">
            <tr data-bind="    foreach: $parent.columns">
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">1000</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">Mr</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">Fred</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">Flinstone</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">12 May 1953</td>
            </tr>
            <tr data-bind="    foreach: $parent.columns">
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">1001</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">Mr</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">Barney</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">Rubble</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">20 Jun 1978</td>
            </tr>
            <tr data-bind="    foreach: $parent.columns">
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">1002</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">Ms</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">Belinda</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">Mason</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">11 Nov 2004</td>
            </tr>
            <tr data-bind="    foreach: $parent.columns">
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">1003</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">Mrs</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">Mary</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">Moore</td>
                <td data-bind="    text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText]">5 Aug 1999</td>
            </tr>
        </tbody>
    </table>
    <div class="ko-grid-pageLinks"><span>Page:</span>
        <!-- ko foreach: ko.utils.range(0, maxPageIndex) -->
        <a class="selected" href="#" data-bind="    text: $data + 1, click: function () { $root.currentPageIndex($data) }, css: { selected: $data == $root.currentPageIndex() }">1</a>                                                       <a href="#" data-bind="    text: $data + 1, click: function () { $root.currentPageIndex($data) }, css: { selected: $data == $root.currentPageIndex() }">2</a>                                                       <a href="#" data-bind="    text: $data + 1, click: function () { $root.currentPageIndex($data) }, css: { selected: $data == $root.currentPageIndex() }">3</a>
        <!-- /ko -->
    </div>
</div>

The bottom div with a class of ko-grid-pageLinks contains the pager.
We will see code like this again when looking at custom grid templates later.

Using Configuration to Control When to Show Debug Javascript Alerts

It can sometimes be frustrating constantly changing alert messages when debugging javascript. One way around this is to read the debug setting from the web.config file.

<compilation debug="true" targetFramework="4.0" />

Create a variable in the javascript and initialise with the debug value.

var isDebug = ("@HttpContext.Current.IsDebuggingEnabled".toLowerCase() == 'true');

In the code at various places check the isDebug variable and display debug alerts as required, or some other action if needed.

if(isDebug)
{
    alert( // your message here );
}

When deployed to a production environment, presuming the debug setting is not there the alerts will not be displayed.

MVC Page Refresh After Ajax Call

So the page needs to refresh after an ajax call…..

In the controller action build the redirect url and include any route parameters that are needed. The Url is returned in the Json object to the calling javascript along with any other values e.g. the result of a database update.

[HttpPost]
public ActionResult Cancel( //parameters here )
{
    var redirectUrl = new UrlHelper(Request.RequestContext).Action("Index", "user", new { } );

    try
    {
        // perform some action
    }
    catch (Exception)
    {
        return Json(new { Url = redirectUrl, status = "Error" });
    }
                
    return Json(new { Url = redirectUrl, status ="OK" });
}

In the Ajax call check the returned data from the action method. If the action was successful redirect, if not display a message or something else if you need.

var UserModel = {
    // initialise model fields
};

$.ajax({
    url: "@Url.Content("~/user/cancel")",
    data: JSON.stringify(UserModel),
    type: 'POST',
    contentType: 'application/json; charset=utf-8',
    success: function (data) {
        if (data.status === 'OK') {
            window.location.href = data.Url
        }
        else {
            alert("The status cannot be updated at this time");
        }
    }
});

Thanks for reading.