Tuesday, April 3, 2012

This blogpost is part of a series about building Single Page Applications using ASP.NET MVC 4 beta. When writing this post I assumed you have read the previous ones. The code snippets are just the incremental changes that are needed since the third. post I'd advise to just download the completed source code directly

This post was more or less triggered by this comment on my first post in this series. James wanted to avoid creating GET functions for each single case. I already replied that he could take advantage of the OData features of WebApi but I think a dedicated post is needed since this is a topic where upshot is showing its limitations.

Updating to the latest packages

First thing I did -starting from the code from my previous post- was to upgrade all packages to the latest version. I had to rename the existing upshot.knockout.extensions.js file before updating, since I had modified it and the package manager would skip it instead of updating it. Since I'm working with C#, I used this command in the package manager console

Install-Package SinglePageApplication.CSharp

This will upgrade some dependencies and add new files such as T4 templates to the project. You will need to update some of the links to javascript files because for example knockout-2.0.0.js is upgraded and renamed to simply knockout.js. I also created a namespace 'deliveryTracker' and put my DeliveriesViewModel in it.

After the package update the UpshotContext HtmlHelper function is now available. So in my desktop and mobile views I can now replace the Javascript call to upshot.metadata with following code:

This will generate javascript that calls upshot.metadata and also sets up a remote datasource called 'DeliveriesForToday. This means I can now remove the code that creates a new RemoteDataSource in the constructor of my DeliveryViewModel and get it from upshot.dataSources.DeliveriesForToday

Note how you can sort and filter on a property of an associated entity by using a forward slash (...property: "Customer/Name"...) because this is supported by OData. The data also got filtered so the browser only received those deliveries where the IsDelivered property was set to false. Unfortunately upshot does not expose the full power of OData to the client application.

Sorting and Filtering the local data

Once data is loaded into the browser it can still be manipulated. In order to see the effect of a manipulation while data is being edited, I decided to set the 'allowRefreshWithEdits: true' property on the local datasource. This lets me sort or group the data when edits are still pending (not uploaded back to the server).

In order to demonstrate sorting and grouping on the local data, I thought it would be fun to include the geolocation capabilities of the current browsers. If a user allows the application to read his current location, the application will sort the data by 'distance from user'.

I added latitude and longitude data to the my server-side Customer domain entity. This entity and the Delivery entity are both mapped to their javascript counterparts, like so:

It's important to note that upshot will map the Delivery object automatically but not the Customer object even when specifying it in the ClientMapping function on the UpshotContext. So whenever upshot tries to map the Delivery object, I create an observable Customer object myself. See the highlighted lines above

self.position is a knockout (ko) observable that holds the current location.

CalculateDistanceFromMe is triggered when self.position or self.deliveries gets updated

calculateDistance is a function I found on HTML5 Rocks and wrapped in the 'geolocationUtils' namespace. It is called for each customer when the data gets refreshed

The calculated distance is stored in the client-side observable customer.DistanceFromMe property

Sorting on the localDataSource passes a sorting function to the setSort method on the dataSource.

Sorting on the deliveriesForCustomer passes a sorting function to the sort method off the knockout observable array (wrapper around javascript arrays)

Conclusions

Sorting is a tricky subject when mixing technologies like Microsoft is doing in this beta release of Single Page Applications. Different objects allow different features depending on which part of the abstraction leaks through

The remoteDataSource object is leaking OData conventions so when sorting or filtering you need to comply with the OData Orderby and Filter uri-conventions. For example you can easily sort on the Customer/Name property of an entityset of Delivery objects.

The localDataSource object however has its own implementation of sorting, filtering and grouping. Besides passing a sorting function, it also allows passing in a property name. So you can sort on the CustomerId of an entityset (of Delivery objects) but you cannot sort on Customer/Name

The GetEntities method on a localDataSource returns a Knockout observableArray. This object exposes a wrapper around the built-in javascript arrays and exposes the same functionality which again is different from the remote or local datasources.

What annoys me is that knockout and javascript both have a syntax for navigating associated entities, being the dot operator. If a remoteDataSource allows to sort by "Customer/Name", at least the localDataSource should expose the same interface. Also when binding data into the UI, you can easily do the following:

So navigating the associated entities using the dot operator works fine when data-binding but not when sorting, filtering, grouping, ...

As the Single Page Application are still in an early phase I suppose this is to be expected. In my opinion it's still way too soon to use upshot in its current form in any serious development.