Workings of ASP.NET MVC SPA app

ASP.NET MVC 4 Single Page Application template depends on ASP.NET Web API Data WebHost Nuget package (https://nuget.org/packages/AspNetWebApi.Data) which provides two main things:  Html Metadata helper  and DataController.

Html Metadata helper defined in System.Web.Http.Data.Helpers assembly have signature as given below:

 1: public static IHtmlString Metadata<TDataController>(this HtmlHelper htmlHelper) where TDataController : DataController

The Metadata helper generates metadata that will be used by Upshot.js. It takes a type which should derive from DataController. The DataController is defined in System.Web.Http.Data assembly and derives from ApiController.

The call to Metadata helper is made in the javascript file as :

 1: upshot.metadata(@(Html.Metadata<MvcApplication7.Controllers.MvcApplication7Controller>()));

This generates the following content for the controller.

 1: upshot.metadata({

 2:     "TodoItem:#MvcApplication7.Models": {

 3:         "key": ["TodoItemId"],

 4:         "fields": {

 5:             "IsDone": {

 6:                 "type": "Boolean:#System"

 7:             },

 8:             "Title": {

 9:                 "type": "String:#System"

 10:             },

 11:             "TodoItemId": {

 12:                 "type": "Int32:#System"

 13:             }

 14:         },

 15:         "rules": {

 16:             "Title": {

 17:                 "required": true

 18:             }

 19:         },

 20:         "messages": {}

 21:     }

 22: });

TodoItem.cs

 1: namespace MvcApplication7.Models

 2: {

 3:

 4:     public class TodoItem

 5:     {

 6:         public int TodoItemId { get; set; }

 7:         [Required]

 8:         public string Title { get; set; }

 9:         public bool IsDone { get; set; }

 10:     }

 11: }

MvcApplication7Context

 1: namespace MvcApplication7.Models

 2: {

 3:     public class MvcApplication7Context : DbContext

 4:     {

 5:         public DbSet<TodoItem> TodoItems { get; set; }

 6:     }

 7: }

Using the code generated by SPA template, on creating a new TodoItem a POST request is made. Looking into the POST request we can see the format in which data is submitted.

 1: [{

 2:     "Id": "0",

 3:     "Operation": 1,

 4:     "Entity": {

 5:         "__type": "TodoItem:#MvcApplication7.Models",

 6:         "Title": "To blog on Uphot.js internals"

 7:     }

 8: }]

Update TodoItem request looks like this:

 1: [{

 2:     "Id": "0",

 3:     "Operation": 2,

 4:     "Entity": {

 5:         "__type": "TodoItem:#MvcApplication7.Models",

 6:         "IsDone": false,

 7:         "Title": "Try Knockout.js",

 8:         "TodoItemId": 1

 9:     },

 10:     "OriginalEntity": {

 11:         "__type": "TodoItem:#MvcApplication7.Models",

 12:         "IsDone": false,

 13:         "Title": "Test",

 14:         "TodoItemId": 1

 15:     }

 16: }]

Adding a new ViewModel

Disclaimer: The following code is a learning exercise and not the best or recommended way of developing single page application.

To add a new ViewModel I did following steps:

  1. Added server side data model class “Airport.cs”
  2. Updated context with it’s value
  3. Added Controller methods
  4. Added partial view
  5. Added JavaScript

Model Airport and Context class

 1: public class Airport

 2:     {

 3:         public int AirportId { get; set; }

 4:         [Required]

 5:         public String Code {get;set;}

 6:         public String Name { get; set; }

 7:         public String City { get; set; }

 8:     }

 1: public class MvcApplication7Context : DbContext

 2:    {

 3:         public DbSet<TodoItem> TodoItems { get; set; }

 4:

 5:        public DbSet<Airport> Airports { get; set; }

 6:    }

Controller Action Methods for new model

 1: public partial class MvcApplication7Controller : DbDataController<MvcApplication7.Models.MvcApplication7Context>

 2:     {

 3:         public IQueryable<MvcApplication7.Models.Airport> GetAirports()

 4:         {

 5:             return DbContext.Airports.OrderBy(t => t.AirportId);

 6:         }

 7:

 8:         public void InsertAirport(MvcApplication7.Models.Airport entity)

 9:         {

 10:             InsertEntity(entity);

 11:         }

 12:

 13:         public void UpdateAirport(MvcApplication7.Models.Airport entity)

 14:         {

 15:             UpdateEntity(entity);

 16:         }

 17:

 18:         public void DeleteAirport(MvcApplication7.Models.Airport entity)

 19:         {

 20:             DeleteEntity(entity);

 21:         }

 22:     }

Partial View for creating airport entry

 1: <h2> Add Airport</h2>

 2:

 3: <form data-bind="with: editingAirportEntry, submit: addNewAirport">

 4:     <div class="row" >

 5:             <div class="control-group">

 6:                 <label class="control-label">

 7:                     Name :</label>

 8:                 <div class="controls">

 9:                     <input name="name" type="text"  data-bind="value: Name"  />

 10:                 </div>

 11:             </div>

 12:         </div>

 13:         <div class="row">

 14:             <div class="control-group">

 15:                 <label class="control-label">

 16:                     Code :</label>

 17:                 <div class="controls">

 18:                     <input name="code" type="text"   data-bind="value: Code" />

 19:                 </div>

 20:             </div>

 21:         </div>

 22:         <div class="row">

 23:             <div class="control-group">

 24:                 <label class="control-label">

 25:                     City :</label>

 26:                 <div class="controls">

 27:                     <input name="name" type="text"   data-bind="value: City" />

 28:                 </div>

 29:             </div>

 30:         </div>

 31:

 32:         <div class="row">

 33:         <button type="submit" class="btn">Add</button>

 34:         </div>

 35: </form>

Note here  I have used with and submit knockout bindings.

JavaScript code for ViewModel and Navigation

Add code for mapping raw data to entity
 1: var airportEntityType = "Airport:#MvcApplication7.Models";

 2:

 3:     MyApp.AirportEntry = function (data) {

 4:         var self = this;

 5:         self.AirportId = ko.observable(data.AirportId);

 6:         self.Code = ko.observable(data.Code);

 7:         self.City = ko.observable(data.City);

 8:         self.Name = ko.observable(data.Name);

 9:         upshot.addEntityProperties(self, airportEntityType);

 10:     }

DataSource for Airport Entries
 1: var airportDataSourceOptions = {

 2:     providerParameters: { url: options.serviceUrl, operationName: "GetAirports" },

 3:     entityType: airportEntityType,

 4:     bufferChanges: true,

 5:     mapping: MyApp.AirportEntry

 6: };

 7:

 8: var airportEditorDataSource = new upshot.RemoteDataSource(airportDataSourceOptions);

Data property for airport entry being created or edited
 1: self.editingAirportEntry = airportEditorDataSource.getFirstEntity();

Submit event handler
 1: self.addNewAirport = function (formElement) {

 2:     airportEditorDataSource.commitChanges(function () {

 3:         self.showGrid();

 4:     });

 5: };

Navigation code to show Airport editor form
 1: self.createAirportEntry = function () {

 2:    self.nav.navigate({ edit: "new", entity: 'airport' })

 3: };

 4:

 5: self.nav = new NavHistory({

 6:    params: { edit: null, page: 1, pageSize: 10, entity: 'todo' },

 7:    onNavigate: function (navEntry, navInfo) {

 8:

 9:        if (navEntry.params.entity === 'airport') {

 10:            //reset airport editor data source

 11:            airportEditorDataSource.revertChanges();

 12:            airportEditorDataSource.reset();

 13:            if (navEntry.params.edit) {

 14:                if (navEntry.params.edit === 'new') {

 15:                     //for creating new airport entry

 16:                    airportEditorDataSource.getEntities().push(new MyApp.AirportEntry({}));

 17:                }else{

 18:                     //code for editing exiting entry

 19:                 }

 20:            }

 21:        } else {

 22:             //Todo Item navigation code

 23:        }

 24:    }

 25: }).initialize({ linkToUrl: true });

Binding to show Airport Form and button conditionally
 1: <div data-bind="visible: editingAirportEntry">

 2:     @Html.Partial("_AirportEditor")

 3: </div>

 4: <p><button data-bind="click: createAirportEntry, visible: !editingAirportEntry()">Create Airport Entry</button></p>

Conclusion: We have seen today basic steps to add new views to the Single Page Application.

Simple Search Form using knockout.js

In this post I continue to explore the knockout.js with ASP.NET MVC 4.

I wish to create a flight search form which looks like this:screenN1

My ViewModel looks like this:

var airport = function(city,code){

       this.city = city;

       this.code = code;

   };

   var airports =  ko.observableArray([

               new airport("New Delhi", "DEL"),

               new airport("Mumbai", "BOM"),

               new airport("Chennai", "MAA"),

               new airport("Kolkata", "CCU"),

               new airport("Bangalore", "BLR")]);


   var flightSearch = {


       isOneWay: ko.observable("oneWay"),


       airports: airports,


       originatingAirport: ko.observable(),

       destinationAirport: ko.observable(),

       departureDate: ko.observable(),

       returnDate: ko.observable()

   };

I have an airport object defined to take two values: city and code. Then there is an array of airports, which is build using ko.observableArray. Then there is flightSearch view model class with properties for selecting between return flights or one way flight booking, list of airports,  origin and destination airports, departure and optional return date.

I want my view logic to hide return date ui when one way flight is selected.

Here is my markup for the same, along with a summary of values selected.

 1:

 2: <div class="row">

 3:     <div class="span8 ">

 4:         <div class="row">

 5:             <div class="span2 control-group">

 6:                 <input type="radio" name="isOneWay" value="return" data-bind="checked: isOneWay" />

 7:                 Return

 8:             </div>

 9:             <div class="span2 control-group">

 10:                 <input type="radio" name="isOneWay" value="oneWay" data-bind="checked: isOneWay"    />

 11:                 Oneway

 12:             </div>

 13:         </div>

 14:         <div class="row">

 15:             <div class="control-group">

 16:                 <label class="control-label">

 17:                     Leaving From :</label>

 18:                 <div class="controls">

 19:                     <select data-bind="options: airports,optionsText: 'city', optionsValue: 'code', value: originatingAirport, optionsCaption: 'Choose...'">

 20:                     </select>

 21:                 </div>

 22:             </div>

 23:         </div>

 24:         <div class="row">

 25:             <div class="control-group">

 26:                 <label class="control-label">

 27:                     Going To :</label>

 28:                 <div class="controls">

 29:                     <select data-bind="options: airports,optionsText: 'city', optionsValue: 'code',value: destinationAirport, optionsCaption: 'Choose...'">

 30:                     </select>

 31:                 </div>

 32:             </div>

 33:         </div>

 34:         <div class="row">

 35:             <div class="control-group">

 36:                 <label class="control-label">

 37:                     Departing :</label>

 38:                 <div class="controls">

 39:                     <input name="departureDate" type="text" data-bind="value : departureDate" />

 40:                 </div>

 41:             </div>

 42:         </div>

 43:         <div class="row">

 44:             <div class="control-group" data-bind="visible: isOneWay() === 'return'" style="display:none">

 45:                 <label class="control-label">

 46:                     Returning :</label>

 47:                 <div class="controls">

 48:                     <input name="returnDate" type="text" data-bind=" value: returnDate" />

 49:                 </div>

 50:             </div>

 51:         </div>

 52:     </div>

 53:     <div class="span3">

 54:         <h3>

 55:             Search Summary:</h3>

 56:         <div data-bind="if: isOneWay() === 'return'">

 57:             Return flights

 58:         </div>

 59:         <div data-bind="ifnot: isOneWay() === 'return'">

 60:             One way flight

 61:         </div>

 62:         <div>

 63:             Starting at: <span data-bind="text: originatingAirport"></span>

 64:         </div>

 65:         <div>

 66:             Going to: <span data-bind="text: destinationAirport"></span>

 67:         </div>

 68:     </div>

 69: </div>

knockout.js binding usages

Value binding

All input and select controls are bound using value binding.

Options, OptionsText, OptionsValue and OptionsCaption bindings

For Origin and Destination Airports, I have used these bindings. OptionsText and OptionsValue is used to bind the options to JavaScript objects.

The code at line no. 19 specifies that ‘city’ property of the object should be used as text and ‘code’ property of the object should be used a value for the options.

if and ifnot binding

In line number 44 if binding was used to control the visibility of div containing the return date input controls. Similarly in line number 59, ifnot  binding is used to control div’s visibility.

text binding

In line number 63 and 66, text binding is used to show the values of selected airports.

Upshot.js

This post is part of series of post on Single Page Application using ASP.NET MVC 4. We will explore the upshot, nav and history JavaScript libraries in this post.

What is Upshot.js?

Upshot is a new JavaScript library coming out of Microsoft. It’s based on previously previewed JavaScript library RIA/JS.  It is available through NuGet http://nuget.org/packages/upshot. As it is relatively undocumented, expect some of what is written below to change.

Upshot is for data access and related functionalities like paging, sorting, change tracking. Upshot.js is designed to work with DataController and oData (only data reading).

Objects provided by Upshot

  • EntitySource :  a wrapper of array of Entities which are observable. provides method to bind/unbind events
  • EntityView : inherits from EntitySource and provides change tracking on it
  • DataSource : inherits from EntityView and add paging, sorting and filterting support on it.
  • RemoteDataSource 
  • LocalDataSource
  • DataProvider : a wrapper over jQuery ajax to get and submit entity values
  • ODataDataProvider
  • riaDataProvider
  • EntitySet : inherits from EntitySource and provides change tracking on EntityType. This object does the heavy lifting of managing entities
  • DataContext
  • AssociatedEntitiesView

TODO: Update more about these objects

Usage of Upshot in ASP.NET MVC4 SPA

On creating a Single Page Application controller, we got code generated for the View and ViewModel. In the ViewModel code we can some usage of Upshot.

Creating RemoteDataSource

var gridDataSource = new upshot.RemoteDataSource(dataSourceOptions);

var editorDataSource = new upshot.RemoteDataSource(dataSourceOptions);

The RemoteDataSource are created with dataSourceOptions. DataSource options contains property values for dataProvider parameters ( url and oprationName), entityType , bufferChanges, mapping

Fetching data using DataSource

self.todoItems = gridDataSource.getEntities();

self.editingTodoItem = editorDataSource.getFirstEntity();

In the above code getEntities() method is used to get all TodoItem entities, and getFirstEntity() to get first TodoItem entity.

Another way to load data is to call refresh.

// Load and begin editing an existing todoItem instance

editorDataSource.setFilter({ property: "TodoItemId", value: Number(navEntry.params.edit) }).refresh();

// Not editing, so load the requested page of data to display in the grid

gridDataSource.refresh();

Saving data using DataSource

editorDataSource.commitChanges(function () {

                self.successMessage("Saved TodoItem").errorMessage("");

                self.showGrid();

            });

Deleting data using DataSource

editorDataSource.deleteEntity(todoItem);

Knockout.js

This post is part of series of post on Single Page Application using ASP.NET MVC 4. We will explore the knockout JavaScript library in this post.

What is Knockout.js?

Knockout.js is a JavaScript library for building HTML UI using Model-View-ViewModel (MVVM) pattern. The library is MIT licensed and source code is hosted on GitHub at https://github.com/SteveSanderson/knockout and project web hosted at http://knockoutjs.com/

Salient features of this library are:

  • Declarative bindings. A way to bind UIs to underlying data model using declarative bindings like data-bind=”text: firstName”
  • Dependency tracking. The build in dependency tracking updates the right UI when the underlying data changes by doing dependency check.
  • Templating

What is MVVM?

Model-View-ViewModel is a design pattern for building user interfaces.

  • Model: an application’s persisted data.
  • View: an application’s visible, interactive UI component.
  • ViewModel: In MVVM ViewModel is the an abstract layer to separate View and Model. ViewModel is a code based representation of data and operations on a UI.

Using Knockout.js

ViewModel in Knockout.js is a JavaScript object like:

var contact = {

    firstName : 'John',

    lastName  : 'Clark',

    email     : 'john.clark@company.com'

}

To bind this to UI, data-bind attribute are used like:

Contact's first name is  <span data-bind="text: firstName"> </span> 

 

For binding view to viewmodel, call Knockout.js activateBindings method as given here:

ko.activateBindings(contact);

To automatically update UI when the data model changes, we need to use Knockout’s observable as given below:

var contact = {

    firstName   : ko.observable('John'),

    lastName    : ko.observable('Clark'),

    email       : ko.observable('john.clark@company.com')

};

Built-in bindings:

  1. text : use text binding to display text value
  2. html : use html binding to display HTML
  3. css : use css binding to add or remove CSS classes
  4. style : use style binding to add or remove style values to the DOM node
  5. attr : use attr binding to
  6. visible : use visible binding to control the DOM element’s visibility
  7. foreach : use  foreach to loop around array and create markup on them
  8. if : use if to control a section of markup’s visibility
  9. ifnot : similar to if binding but works if the conditional is false
  10. with : use with in templates for creating new binding context
  11. click : use click binding to add an event handler to the click event to elements like a, input and buttons
  12. event : use event  binding to add an event handler to specified events like mouseover, mouseout, keypress, keydown etc. on the DOM element
  13. submit : use submit binding to add an event handler to the DOM node which would be called when the element value is being submitted like in case of form submit
  14. enable : use enable  binding to enable the DOM element based on the specified boolean expression. This is typically used for DOM elements like input, select, textarea.
  15. disable : use disable binding to disable the DOM element based on the specified boolean expression
  16. value : use value binding to  link the DOM element’s value with a property in view model. Typically used with input, select, textarea
  17. hasfocus : use hasfocus  binding to link a DOM element’s focus state with a property in view model
  18. checked : use checekd  binding with checkbox or radio button to link it with property on view model
  19. options  : use options binding to control the options  of a select control (<select>) 
  20. selectedOptions : use selectedOptions binding to control the elements selection in a multi-select list. Use this with <select> element and options binding
  21. uniqueName :
  22. template : use template  binding to render a templates output to the associated DOM element

In addition to the built-in bindings, knockout.js supports creating custom bindings.

ASP.NET MVC4 and Single Page Application

A new Nuget package is provided by Microsoft for developing Single Page Application.

What is Single Page Application? Traditionally, the website/web applications have been using multiple pages to provide the intended functionalities. For e.g. an e-commerce web application might have Catalog, Cart and Payment pages.  This model of using multiple pages results in common problems of page reloads and UI flickering on navigation between pages. To address these problems, a single web page can be enhanced using AJAX and DOM manipulation to provide a similar user experience as in desktop applications.

How to decide between developing typical web applications vs Single Page applications?

Some points to consider are:

  1. Minimize page reloads and flickering on page navigation.
  2. To provide look and feel as well as user experience similar to desktop applications.

To read more about Single Page Application read the MSDN article Single-page Interface and AJAX Patterns.

Let see how to get started with ASP.NET MVC 4 SPA template.

Single Page Application template is not provided through MVC 4 beta installer but through Nuget package. See details on NuGet pacakge at http://nuget.org/packages/SinglePageApplication.

To install the template, I have opened a VS2010 solution (yuk, you need to open a solution before installing the package, otherwise Package Manger console throws “Install-Package : The current environment doesn’t have a solution open.”) and run command Install-Package SinglePageApplication

On creating a new ASP.NET MVC 4 web application project, you will get the following dialog to select the template to be used.

image

 

Major differences as compared to typical ASP.NET MVC project.

  1. New Javascript libraries to support SPA. knockout-2.0.0.js, upshot.js, nav.js, native.history.js and modernizer development version from Microsoft.
  2. The controller should derive from DataController which derives from ApiController.

After creating the project, to add functionalities you have to do following steps:

1. Add a model class

Here is the TodoItem model class.

using System;

using System.Collections.Generic;

using System.ComponentModel.DataAnnotations;

using System.Linq;

 

namespace MvcApplication7.Models

{   

 

    public class TodoItem

    {

        public int TodoItemId { get; set; }

        [Required]

        public string Title { get; set; }

        public bool IsDone { get; set; }

    }

}

2. Add controller by invoking add controller action on the Controllers folder. It opens a dialog, select in Template “Single Page Application with read/write actions and views, using Entity Framework”, select the Model class and select the context class or to create new context class select the option “<New data context…>”.

image

 

On adding controller, Visual Studio will generate code for Controller, Views, and JavaScript for this model.

The generated controller class looks like this:

using System.Linq;

using System.Web.Http;

using System.Web.Http.Data.EntityFramework;

 

namespace MvcApplication7.Controllers

{

    public partial class MvcApplication7Controller : DbDataController<MvcApplication7.Models.MvcApplication7Context>

    {

        public IQueryable<MvcApplication7.Models.TodoItem> GetTodoItems() {

            return DbContext.TodoItems.OrderBy(t => t.TodoItemId);

        }

 

        public void InsertTodoItem(MvcApplication7.Models.TodoItem entity) {

            InsertEntity(entity);

        }

 

        public void UpdateTodoItem(MvcApplication7.Models.TodoItem entity) {

            UpdateEntity(entity);

        }

 

        public void DeleteTodoItem(MvcApplication7.Models.TodoItem entity) {

            DeleteEntity(entity);

        }

    }

}

Under Views, Tasks folder is added with Index.cshtml, _Editor.cshtml, _Grid.cshtml and _Paging.cshtml views in it.

Index.cshtml

@{

    ViewBag.Title = "TodoItems";

    Layout = "~/Views/Shared/_SpaLayout.cshtml";

}

 

<div data-bind="visible: editingTodoItem">

    @Html.Partial("_Editor")

</div>

 

<div data-bind="visible: !editingTodoItem()">

    @Html.Partial("_Grid")

</div>

 

<div class="message-info message-success" data-bind="flash: { text: successMessage, duration: 5000 }"></div>

<div class="message-info message-error" data-bind="flash: { text: errorMessage, duration: 20000 }"></div>

 

 

<script type="text/javascript" src="@Url.Content("~/Scripts/TodoItemsViewModel.js")"></script>

 

<script type="text/javascript">

    $(function () {

        upshot.metadata(@(Html.Metadata<MvcApplication7.Controllers.MvcApplication7Controller>()));

 

        var viewModel = new MyApp.TodoItemsViewModel({

            serviceUrl: "@Url.Content("~/api/MvcApplication7")"

        });

        ko.applyBindings(viewModel);

    });

</script>

_Editor.cshtml

<h2>Edit TodoItem</h2>

 

<form data-bind="with: editingTodoItem, validate: validationConfig">

    <p data-bind="visible: TodoItemId">

        TodoItemId:

        <b data-bind="text: TodoItemId"></b>

    </p>

    <p>

        Title:

        <input name="Title" data-bind="value: Title, autovalidate: true" />

        <span class="error" data-bind="text: Title.ValidationError"></span>

    </p>

    <p>

        IsDone:

        <input name="IsDone" type="checkbox" data-bind="checked: IsDone, autovalidate: true" />

        <span class="error" data-bind="text: IsDone.ValidationError"></span>

    </p>

    <p>

        <button type="submit" data-bind="enable: CanSave">Save</button>

        <a href="#" data-bind="visible: TodoItemId, click: $parent.deleteTodoItem">Delete</a>

        <a href="#" data-bind="click: $parent.showGrid">Back to grid</a>

        <a href="#" data-bind="visible: IsUpdated, click: $parent.revertEdit">Undo changes</a>

    </p>

</form>

 

_Grid.cshtml

<h2>TodoItems (<span data-bind="text: paging.totalItems"></span>)</h2>

 

<div data-bind="visible: paging.totalItems() > 0">

    @Html.Partial("_Paging")

 

    <table>

        <thead>

            <tr>

                <th>Title</th>

                <th>IsDone</th>

            </tr>

        </thead>

        <tbody data-bind="foreach: todoItems">

            <tr data-bind="click: $parent.editTodoItem" style="cursor: pointer">

                <td data-bind="text: Title"></td>

                <td data-bind="text: IsDone"></td>

            </tr>

        </tbody>

    </table>

</div>

 

<p><button data-bind="click: createTodoItem">Create TodoItem</button></p>

 

_Paging.cshtml

<p data-bind="with: paging">

    Page <b data-bind="text: pageIndex"></b> 

    of <b data-bind="text: totalPages"></b>

    | Show <select data-bind="options: [5, 10, 20, 50], value: pageSize"></select> per page

 

    <a href="#" data-bind="click: moveFirst, visible: canMovePrev">&laquo; First</a>

    <a href="#" data-bind="click: movePrev, visible: canMovePrev">&laquo; Prev</a>

    <a href="#" data-bind="click: moveNext, visible: canMoveNext">Next &raquo;</a>

    <a href="#" data-bind="click: moveLast, visible: canMoveNext">Last &raquo;</a>

</p>

Under Scripts folder, TodoItemsViewModel.js is added.

TodoItemsViewModel.js

/// <reference path="_references.js" />

 

(function (window, undefined) {

    // Define the "MyApp" namespace

    var MyApp = window.MyApp = window.MyApp || {};

 

    // TodoItem class

    var entityType = "TodoItem:#MvcApplication7.Models";

    MyApp.TodoItem = function (data) {

        var self = this;

 

        // Underlying data

        self.TodoItemId = ko.observable(data.TodoItemId);

        self.Title = ko.observable(data.Title);

        self.IsDone = ko.observable(data.IsDone);

        upshot.addEntityProperties(self, entityType);

    }

 

    // TodoItemsViewModel class

    MyApp.TodoItemsViewModel = function (options) {

        var self = this;

 

        // Private properties

        var dataSourceOptions = {

            providerParameters: { url: options.serviceUrl, operationName: "GetTodoItems" },

            entityType: entityType,

            bufferChanges: true,

            mapping: MyApp.TodoItem

        };

        var gridDataSource = new upshot.RemoteDataSource(dataSourceOptions);

        var editorDataSource = new upshot.RemoteDataSource(dataSourceOptions);

 

        // Data

        self.todoItems = gridDataSource.getEntities();

        self.editingTodoItem = editorDataSource.getFirstEntity();

        self.successMessage = ko.observable().extend({ notify: "always" });

        self.errorMessage = ko.observable().extend({ notify: "always" });

        self.paging = new upshot.PagingModel(gridDataSource, {

            onPageChange: function (pageIndex, pageSize) {

                self.nav.navigate({ page: pageIndex, pageSize: pageSize });

            }

        });

        self.validationConfig = $.extend({

            resetFormOnChange: self.editingTodoItem,

            submitHandler: function () { self.saveEdit() }

        }, editorDataSource.getEntityValidationRules());

 

        // Client-side navigation

        self.nav = new NavHistory({

            params: { edit: null, page: 1, pageSize: 10 },

            onNavigate: function (navEntry, navInfo) {

                self.paging.moveTo(navEntry.params.page, navEntry.params.pageSize);

 

                // Wipe out any old data so that it is not displayed in the UI while new data is being loaded 

                editorDataSource.revertChanges();

                editorDataSource.reset();

 

                if (navEntry.params.edit) {

                    

                    if (navEntry.params.edit == "new") {

                        // Create and begin editing a new todoItem instance

                        editorDataSource.getEntities().push(new MyApp.TodoItem({}));

                    } else {

                        // Load and begin editing an existing todoItem instance

                        editorDataSource.setFilter({ property: "TodoItemId", value: Number(navEntry.params.edit) }).refresh();

                    }

                } else {

                    // Not editing, so load the requested page of data to display in the grid

                    gridDataSource.refresh();

                }

            }

        }).initialize({ linkToUrl: true });

 

        // Public operations

        self.saveEdit = function () {

            editorDataSource.commitChanges(function () {

                self.successMessage("Saved TodoItem").errorMessage("");

                self.showGrid();

            });

        }

        self.revertEdit = function () { editorDataSource.revertChanges() }

        self.editTodoItem = function (todoItem) { self.nav.navigate({ edit: todoItem.TodoItemId() }) }

        self.showGrid = function () { self.nav.navigate({ edit: null }) }

        self.createTodoItem = function () { self.nav.navigate({ edit: "new" }) }

        self.deleteTodoItem = function (todoItem) {

            editorDataSource.deleteEntity(todoItem);

            editorDataSource.commitChanges(function () {

                self.successMessage("Deleted TodoItem").errorMessage("");

                self.showGrid();

            });

        };

 

        // Error handling

        var handleServerError = function (httpStatus, message) {

            if (httpStatus === 200) {

                // Application domain error (e.g., validation error)

                self.errorMessage(message).successMessage("");

            } else {

                // System error (e.g., unhandled exception)

                self.errorMessage("Server error: HTTP status code: " + httpStatus + ", message: " + message).successMessage("");

            }

        }

        

        gridDataSource.bind({ refreshError: handleServerError, commitError: handleServerError });

        editorDataSource.bind({ refreshError: handleServerError, commitError: handleServerError });

    }

})(window);

 

jQuery Selectors

jQuery provides selectors based on CSS 1-3 and it’s custom selectors.

CSS selectors for common tasks:

  • Class selector: For class myDiv the selector is $(‘.myDiv’)
  • ID selector : For selecting a div with ID myPanel the selector will be $(‘#myPanel’)
  • Tag name: to select all paragraph nodes use $(‘p’)
  • Attribute selectors like
    • contains prefix [name|=”value”]
    • contains [name*=”value”]
    • contains word[name~=”value”]
    • ends with[name$=”value”]
    • equals[name=”value”],
    • not equal[name!=”value”],
    • starts with[name^=”value”],
    • has attribute [“name”] selector.
  • Parent child selector like “parent > child” for example to select all child li elements under div with id myPanel use $(‘div#myPanel > li’)
  • Custom selectors: jQuery have a number of custom selectors like animated, button, checked, contains(), disabled, empty, enabled, eq(), even, file, first-child, first, focus, gt(), has(), header, , hidden, image, input, last-child, last, lt(), not(), nth-child(), odd, only-child, parent, password, radio, reset, selected, submit, text, visible which can be used like $(‘div span:last-child’) to select all last span child of divs.
  • Descendant selector (“ancestor descedant”)  selects all the elements that are descendants of a given ancestor
  • Next Adjacent selector (“prev + next”) Selects all next elements matching “next” that are immediately preceded by a sibling “prev”
  • Next siblings selector (“prev ~ siblings”) Selects all sibling elements that follow after the “prev” element, have the same parent, and match the filtering “siblings” selector.

Mobile Enabled Razor View Engine

Today I was trying to add jQuery Mobile to my project. To start with I found Scott Hanselman’s blog entry

A Better ASP.NET MVC Mobile Device Capabilities ViewEngine . I used the code by deriving the RazorViewEngine. My class looks like this:

 

WebEnabledViewEngine
  1. public class WebEnabledViewEngine : RazorViewEngine
  2.     {
  3.         public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
  4.         {
  5.             ViewEngineResult result = null;
  6.             var request = controllerContext.HttpContext.Request;
  7.  
  8.             // Avoid unnecessary checks if this device isn't suspected to be a mobile device
  9.             if (request.Browser.IsMobileDevice)
  10.             {
  11.                 result = base.FindView(controllerContext, "Mobile/" + viewName, masterName, useCache);
  12.             }
  13.  
  14.             //Fall back to desktop view if no other view has been selected
  15.             if (result == null || result.View == null)
  16.             {
  17.                 result = base.FindView(controllerContext, viewName, masterName, useCache);
  18.             }
  19.  
  20.             return result;
  21.         }
  22.     }

 

Apparently Scott has marked his blog entry as broken. The issue I was facing was that my jQuery Mobile site  was running good but the desktop version failed to load script files. So I end up adding following routing entry and now both version work’s good.

 

Global.asax.cs
  1. routes.IgnoreRoute("{*alljs}", new { alljs = @".*\.js(/.*)?" });