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

Simple single page web application using knockoutjs

Here I am going to demonstrate a simple (emphasis on simple) single-page application, using knockoutjs. For more information on knockoutjs visit their official website.

This demo site I will be going through is for requesting conference tickets and is written as an ASP.Net MVC (Razor) application. The layout page contains the script references to jquerybootstrap and knockoutjs and contains the template header, menu and footer sections. I will not be going through the implementation of these libraries or layout page in detail.

The ticket request site has three views
1. Introduction (intro)
single page intro view
2. Ticket Request Form (requestform)
single page request view
3. Ticket Request Confirmation (confirmation)
single page confirmation view

There are limitations

  • The request form does not contain any validation.
  • The request does not actually get sent anywhere.

First of we’ll look at the script.

<script type="text/javascript" language="javascript">
    var model = {
        view: ko.observable("intro"),
        request: {
            name: ko.observable(""),
            email: ko.observable(""),
            phone: ko.observable(""),
            quantity: ko.observable(0)
        },
    }
    var showForm = function () {
        model.view("requestform");
    }
    var sendRequest = function () {
        model.view("confirmation")
    }

    var showIntro = function () {
        model.view("intro")
    }

    $(document).ready(function () {
        ko.applyBindings();
    })
</script>

The viewmodel has two properties, the view property is an observable string containing the the name of the current view being displayed.  The request property is an object that hold the details of the request and contains the following observable properties nameemailphone and quantity.

There are three methods named showIntro, showForm and sendRequest. These methods are called when
clicking the navigation buttons at the bottom of the views. Each method will set the observable view property of the viewmodel to the respective view name.
The model is bound to the page using the ko.applyBindings() call when the page has finished loading.

Now let’s look at the html code.

All the views are wrapped in a single form element. Each view is contained in a separate DIV element.  On each view notice how the visibility of the outer DIV is controlled by a condition comparing the view name with the view property of the viewmodel.
The introduction holds instructions for completing the request.  This view will only be visible when the viewmodel view property is set to ‘intro’.

<div data-bind="visible: model.view() == 'intro'">
    <div class="panel panel-success">
        <div class="panel-heading"><h4>Conference Tickets</h4></div>
        <div class="panel-body">
            <div class="text-justify">
                <p>
                    This is a demonstration of a simple single-page application using Knockoutjs.
                </p>
                <strong>There are three sections</strong>
                <ul class="list-unstyled">
                    <li><span class="glyphicon glyphicon-info-sign"></span> Introduction (This page)</li>
                    <li><span class="glyphicon glyphicon-tasks"></span> Request Form</li>
                    <li><span class="glyphicon glyphicon-ok"></span> Thank you/Confirmation</li>
                </ul>

                <strong>Notes:</strong>
                <ul class="list-unstyled">
                    <li><span class="glyphicon glyphicon-asterisk"></span>There is no validation on form fields or on form submission.</li>
                    <li><span class="glyphicon glyphicon-asterisk"></span>This is a coding example only, the request is not actually sent anywhere.</li>
                    <li><span class="glyphicon glyphicon-asterisk"></span>No personal information is collected or sent.</li>
                </ul>
            </div>
            <div class="text-center">
                <button class="btn btn-success" data-bind="click: showForm">Next</button>
            </div>
        </div>
    </div>
</div>

The request form gathers information relating to the conference ticket request.  This view will only be visible when the viewmodel view property is set to ‘requestform’.

<div data-bind="visible: model.view() == 'requestform'">
    <div class="panel panel-success">
        <div class="panel-heading"><h4>Conference Tickets</h4></div>
        <div class="panel-body">
            <div class="col-xs-4">
                <div class="form-group">
                    <label>Full name:</label>
                    <input type="text" class="form-control" data-bind="value: model.request.name" />
                </div>
                <div class="form-group">
                    <label>Contact email:</label>
                    <input class="form-control" data-bind="value: model.request.email" required />
                </div>
                <div class="form-group">
                    <label>Contact phone:</label>
                    <input class="form-control" data-bind="value: model.request.phone" />
                </div>

                <div class="form-group">
                    <label>How many will attend?</label>
                    <select class="form-control" data-bind="value: model.request.quantity">
                        <option value="0">Not Attending</option>
                        <option value="1">1</option>
                        <option value="2">2</option>
                        <option value="3">3</option>
                        <option value="4">4</option>
                    </select>
                </div>
            </div>

            <div class="col-xs-12 text-center">
                <button class="btn btn-success" data-bind="click: showIntro"> Back </button>
                <button class="btn btn-success" data-bind="click: sendRequest"> Submit Request </button>
            </div>
        </div>
    </div>
</div>

Finally, the confirmation view displays a summary of the request. This view will only be visible when the viewmodel view property is set to ‘confirmation’.

<div data-bind="visible: model.view() == 'confirmation'">
    <h1>
        <span data-bind="visible: model.request.quantity() == '0'">
            Oh no! <span data-bind="text: model.request.name()"></span>, I'm gutted you're not coming.
        </span>
        <span data-bind="visible: model.request.quantity() != '0'">
            Yay! <span data-bind="text: model.request.name()"></span> is coming.
        </span>
    </h1>
    <div class="lead">
        <strong>Here are the details submitted</strong>

        Contact phone: <span data-bind="text: model.request.email()"></span>

        Contact email: <span data-bind="text: model.request.phone()"></span>

        No of tickets: <span data-bind="text: model.request.quantity()"></span>

    </div>
    <div class="text-center">
        <button class="btn btn-success" data-bind="click: showForm">Back</button>
    </div>
</div>

As I often say, this is not the only way but it is effective. There can be as many views as are required.