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.

ASP.NET Conditional Required Field Validation on Client and Server Side

If you have a user input form on an ASP.NET site a RequiredFieldValidator will ensure the user enters a value in the associated field before the form can be submitted to the server.
On more complex forms one field may only be required depending on the value or selection of another field. A RequiredFieldValidator cannot be used as the associated field will always expect user input irrespective of any other field values. A CustomValidator can be used to customise the validation on either the client side, server side or both.

First we need an input form. If the Other radio button is selected a value must be entered in the textbox.
conditional-validation
Here is the full html for the above form. The radio button group consists of individual radio buttons but a RadioButtonList would work too.

<form id="form1" runat="server">
<div>
 <asp:Label runat="server">What is the operating system of your phone?</asp:Label>
 <div>
 <asp:RadioButton ID="AndroidRadioButton" ClientIDMode="Static"
 runat="server" ValidationGroup="PHONEGROUP" GroupName="PhoneOSGroup" />
 <asp:Label ID="Label1" runat="server">Android</asp:Label>
 </div>
 <div>
 <asp:RadioButton ID="IOSRadioButton" ClientIDMode="Static"
 runat="server" ValidationGroup="PHONEGROUP" GroupName="PhoneOSGroup" />
 <asp:Label ID="Label2" runat="server">IOS</asp:Label></div>
 <div>
 <asp:RadioButton ID="BlackberryRadioButton" ClientIDMode="Static"
 runat="server" ValidationGroup="PHONEGROUP" GroupName="PhoneOSGroup" />
 <asp:Label ID="Label3" runat="server">Blackberry</asp:Label>
 </div>
 <div>
 <asp:RadioButton ID="OtherOSRadioButton" ClientIDMode="Static"
 runat="server" ValidationGroup="PHONEGROUP" GroupName="PhoneOSGroup" />
 <asp:Label ID="Label4" runat="server">Other</asp:Label><br />
 <asp:TextBox runat="server" ID="OtherOSTextBox" ClientIDMode="Static"></asp:TextBox>
 <asp:CustomValidator ID="OtherOSValidator" ClientIDMode="Static"
 runat="server" Display="Dynamic" Text="Specify Phone OS"
 ClientValidationFunction="PhoneOSValidation"
 OnServerValidate="PhoneOSValidation" ValidationGroup="PHONEGROUP">
 </asp:CustomValidator>
 </div>
 <asp:Button ID="GoButton" runat="server" Text="Button" ValidationGroup="PHONEGROUP"/>
</div>
</form>

On the custom validator the ClientValidationFunction attribute specifies which javascript method to call to validate the field.

<script type="text/javascript">
    function PhoneOSValidation(Source, args) {
        args.IsValid = true;
        var otherOSRadio = document.getElementById("OtherOSRadioButton");
        var otherOS = document.getElementById("OtherOSTextBox");
        if (otherOSRadio.checked) {
            args.IsValid = ( otherOS.value != "")
        }
    }
</script>

The OnServerValidate attribute specifies which server side code behing method to call to validate the user input.

protected void PhoneOSValidation(object source, ServerValidateEventArgs args)
{
    args.IsValid = true;
    if (OtherOSRadioButton.Checked)
    {
        args.IsValid = (OtherOSTextBox.Text.Length >= 1);
    }
}

If the client side PhoneOSValidation javascript method returns false the form will be invalid and will not be submitted.
If client side validation passes the server side PhoneOSValidation method in the code behind will be called when the page is submitted to the server. Because of the server validation you do not really need the client validation but it certainly doesn’t hurt.

In the button click event checking the Page IsValid property will be true if the form validation passes.

protected void GoButton_Click(object sender, EventArgs e)
{
    if (Page.IsValid)
    {
 
    }
}

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.

File Upload in an ASP.NET MVC Application

Using the upload control in an ASP.NET MVC 4 application is quite straight forward, though there are a couple of gotchas that can catch you out.

In the example below an image can be selected and uploaded to the Uploads folder on the site. The image is then displayed in the page on post back. At this point there is no validation on the type of files being uploaded so anything file other than a valid image file will cause the application to error. Validation will be discussed at a later time.

This is how my solution looks in Visual Studio
FileUpload-Solution Explorer

1. View Model (FileUploadViewModel.cs)
The view model has 2 properties. The UploadFilePath property will hold the path of the file to be uploaded. The Url property will hold the relative Url for displaying the image on the page.

using System.ComponentModel.DataAnnotations;
using System.Web;

namespace FileUpload.Models
{
    public class FileUploadViewModel
    {
        [Display(Name="File To Upload")]
        [Required]
        public HttpPostedFileBase UploadFilePath { get; set; }

        public string Url { get; set; }
    }
}

2. The Controller (HomeController.cs)
When a file is selected and submitted the HttpPost Index method checks the model is valid, i.e. a file has been chosen. If a file has been chosen the file is saved in the Uploads folder and the relative Url is populated in the Url property.

using FileUpload.Models;
using System.IO;
using System.Web.Mvc;

namespace FileUpload.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View(new FileUploadViewModel());
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Index(FileUploadViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }

            var fileName = Path.GetFileName(model.UploadFilePath.FileName);
            var uploadPath = Path.Combine(Server.MapPath("~/uploads/"), fileName);
            model.UploadFilePath.SaveAs(uploadPath);

            model.Url = Path.Combine("/uploads/", fileName);

            return View(model);
        }
    }
}

3. The View (Index.cshtml)
The view displays a ‘file’ type of input control.
One of the things that can catch you out is the form encryption type. Note how it is set to “multipart/form-data”, without this it will not work because your model will be invalid.

@model FileUpload.Models.FileUploadViewModel
@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

@using (Html.BeginForm("index", "home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{  
     @Html.AntiForgeryToken()
     @Html.ValidationSummary()
    <fieldset>
        <legend>                
                <strong>Files</strong>
        </legend>
        
        <p>
            @Html.LabelFor(m => m.UploadFilePath)<br />
            <input type="file" id="UploadFilePath" name="UploadFilePath"/>
            <br />
            <input type="submit" value="Upload File"/>
        </p>
        <img src="@Model.Url" alt="Uploaded Image"/>
    </fieldset>
}

If no file is selected when the form is submitted the standard Required Field validation message will display ‘The FieldDisplayName is required’ which in this case is exactly ‘The File To Upload field is required’. Which is the same message displayed when the form encryption type is not set.

This is what is displayed when the application is initially running
File Upload Application Running
…and when an image file is uploaded
FileUpload-Uploaded
How Can This Be Enhanced Further?
We could

  • Add validation to limit file types
  • Have the upload locations configurable, different file types could be saved in different locations