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!

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.

Radio Buttons in Knockout Simplegrid Using Custom Grid Template

This is a follow on from a previous post First Look At Knockout Paged Simplegrid which I suggest you read as we will be building on the code from that post.

So now we want to extend the SimpleGrid and apply an action to each row.  In the table below I can select Approve or Reject and click the button to perform some like update the database.

knockout simple grid with controls

The first thing that changes from previous code is the definition of the columns. Notice how an Action column is now defined, data element of the column is a function.  This function will be the bound to the button click event that is executed on each row.

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: "Action", rowText: {
                action: function (item) {
                    return function () {
                        alert(item.employeeid);
                        alert(item.selected());
                    }
                }
            }
        }
    ],
    pageSize: 5
});

There is a new item in the data called selected that is marked as observable. This item will be bound to the radio button group of each row. To simplify things I have also removed the startdate data item from the previous example as it was not being used in this example.

var gridData = [
    { employeeid: 1000, title: "Mr", forename: "Fred", surname: "Flinstone", selected: ko.observable() },
    { employeeid: 1001, title: "Mr", forename: "Barney", surname: "Rubble", selected: ko.observable() },
    { employeeid: 1002, title: "Ms", forename: "Belinda", surname: "Mason", selected: ko.observable() },
    { employeeid: 1003, title: "Mrs", forename: "Mary", surname: "Moore", selected: ko.observable() },
    { employeeid: 1004, title: "Mr", forename: "Harry", surname: "Hill", selected: ko.observable() },
    { employeeid: 1005, title: "Dr", forename: "Carl", surname: "Flowers", selected: ko.observable() },
    { employeeid: 1006, title: "Miss", forename: "Jenna", surname: "Pray", selected: ko.observable() },
    { employeeid: 1007, title: "Mr", forename: "John", surname: "Doe", selected: ko.observable() },
    { employeeid: 1008, title: "Mrs", forename: "Jane", surname: "Smith", selected: ko.observable() },
    { employeeid: 1009, title: "Mrs", forename: "Helen", surname: "Jones", selected: ko.observable() },
    { employeeid: 1010, title: "Miss", forename: "Amy", surname: "Simmonds", selected: ko.observable() },
    { employeeid: 1011, title: "Mr", forename: "William", surname: "Parker", selected: ko.observable() }
];

This is fine if you have hard coded data in your pages, but we all know that would not normally be the case, right? If you are calling some back end method (e.g. an ajax call) to retrieve the data then you will have to add the observable item to the data manually

$.getJSON('@Url.Content("~/knockoutjs/getallemployees")', function (data) {
    for (var i = 0; i < data.length; i++) {
        data[i].selected = "ko.observable()";
    }
    ko.applyBindings(new PagedGridModel(data));
});

The final difference is how the table is constructed, a customer grid template gives a lot of control over how the grid appears.  By linking a grid template to the simplegrid div element you can practically do anything with the table you want.

Here the columns in the model are first being iterated to display the column headers in the table thead.   A table row is built for each row in the model.   The action property (which is a function) is bound to the button click event.  The selected property is bound to the radio group.  The row item is passed as a parameter to the function and because the selected property is an observable it will be set to the selected radio value.

<div data-bind='simpleGrid: gridViewModel, simpleGridTemplate: "custom_grid_template"'></div>
<script type="text/html" id="custom_grid_template">
    <table>
        <colgroup>
            <col span="4">
            <col span="1" style="width: 240px;">
         </colgroup>
        <thead>
            <tr data-bind="foreach: columns">
                <th data-bind="text: headerText"></th>
            </tr>
        </thead>
        <tbody data-bind="foreach: itemsOnCurrentPage">
            <tr data-bind="foreach: $parent.columns">
                <!--ko if: typeof rowText == 'object' && typeof rowText.action == 'function'-->
                <td>
                    <input data-bind="checked: $parent.selected, attr: { name: 'radioGroup' + $parentContext.$index() }" value="1" type="radio" /> Approve  
                    <input data-bind="checked: $parent.selected, attr: { name: 'radioGroup' + $parentContext.$index() }" value="2" type="radio" /> Reject  
                    <button data-bind="click: rowText.action($parent)">action</button>
                </td>
                <!-- /ko -->

                <!--ko ifnot: headerText == 'status' || (typeof rowText == 'object' && typeof rowText.action == 'function')-->
                    <td data-bind="text: typeof rowText == 'function' ? rowText($parent) : $parent[rowText] "></td>
                <!--/ko-->
            </tr>
        </tbody>
    </table>
</script>

Go here to see a working demo on jsfiddle.

**Update 25th September 2014

In this post I have mentioned two ways the data can be assigned to the VieModel, hard coded and via an ajax call.  Because of the different ways the selected property is initialised as observable the value must be read differently. The only difference is the inclusion or exclusion of brackets.

When using the hard coded data method

alert(item.selected());

When making an ajax call to retrieve the data and manually

alert(item.selected);

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.