Monday, March 26, 2012

While attending a Webcamp around HTML 5, the instructor pointed out the "Pin to Taskbar" feature that is available as from Internet Explorer 9. Pinning a site to the taskbar makes your website behave more like a desktop application, allowing users to select from fixed tasks and/or a dynamic jumplist, receive notifications, see thumbnail previews... all available from a prominent location on screen showing your website's logo.

My blog pinned to my Taskbar

You can find all information and guidance on the 'Build my Pinned Site' website. Obviously I had to try it out for myself so if you are on Internet Explorer 9 or higher, you should be able to pin my blog to your taskbar. The result of doing this should look like this at the moment:

My brand new logo always present on the desktop

The right-click menu containing:

A link to my homepage

A fixed 'Tasks' section showing a link to my LinkedIn profile

A dynamic jumplist with my 10 most recent posts

As long as you don't pin this blog to your taskbar you will see a teaser bar at the bottom of my page informing you that this site can take advantage of the 'Pin to taskbar' feature

Generating the script

Microsoft provides a quick way of generating the script that is needed to enable the feature. When you start their wizard you'll need to provide some basic information, a list of fixed tasks, upload a 64x64 icon file, indicate if you want a teaser bar and give the URL to your RSS or Atom feed to generate the dynamic jump list. This generates a single JavaScript file that is hosted on the 'BuildMyPinnedSite' data servers, along with the icon you uploaded. So all you need to do is insert a reference to this script file on your page and you are good to go.

Unfortunately when I first inserted that script in my blog there were some issues:

The teaser bar was inserted on a fixed position on the top of the page where it collided with the Google navigation bar.

The URLs in my dynamic jumplist pointed to the 'comments' pages instead of the posts themselves.

I linked to the JavaScript file from within my blogspot template file, meaning that I would lose it if I were to choose a different template.

Making it work on Blogspot

Blogspot allows inserting custom HTML and JavaScript via their 'HTML/JavaScript' gadget. You can find it when you put your blog in design mode, choose layout and click 'Add Gadget'. I choose a wide gadget at the bottom of my screen because the generated teaser bar is quite wide.

Since the script has to do everything by itself, the entire construction of the teaser bar is done via script. I don't need that kind of dynamic behavior on this blog so I extracted the teaser bar <div id="___ie9sitepinning__bar_container"> from JavaScript and converted it to HTML. I also don't want the teaser bar to decide its own position. It should display in the area that is reserved for the gadget as defined by the template and chosen by the user.

These are the changes I made to the inline style of the root div:

Changed the style so it gets positioned inside the gadget's area

from "position: absolute; top: 0; left: 20px;width: 95%;"

into "position: relative; top: 0; left: 0px;width: 100%;"

Add the "display:none;" style because I don't want this div to appear by default

Notice that the image on the highlighted line 20 has a class attribute set to "msPinSite". This class is recognized by Internet Explorer 9 and higher and enables the 'Drag this icon to your taskbar' functionality

The rest of the JavaScript needs to be encapsulated in a CDATA block and appended below the above div. Notice the two changes (highlighted) I had to make:

Line 87-99

The original code for generating the jumplist downloads the list of links per post and then just takes the first available link. This happens to be the link to the comments page of each blog. According to the information I found on the structure of an Atom feed (as used by blogspot) the perma-link to a post is called the 'alternate' link. So I execute a 'filter' function to the list of links to find the alternate link. If it can't find it, I take the first link just like the original code did

Line 171-172

Instead of dynamically generating the div here, I just find it back using its 'id' and I set the 'display' style to 'block'

Pinning my blog site seems a bit over the top but I can easily imagine business cases for this feature. Web applications are starting to behave more and more like desktop applications and this is one of the things that makes the end-user experience richer and provides the content owner with a means to grab the users' attention and get them to visit their site more frequently.

Thursday, March 15, 2012

This blog 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 first and second posts. The code snippets are just the incremental changes that are needed since the second post

The mobile viewmodel

In this post I'm adding a mobile component to the DeliveryTracker application I have been describing in my previous posts. I want to have an overview page per delivery, an overview page per customer and a detail view for the currently selected delivery. In order to do that, I'll create a new MobileDeliveriesViewModel in the DeliveriesViewModel.js file. This new class inherits from the existing DeliveriesViewModel class to inherit the existing features but adds some of its own:

self.currentDelivery is an observable (knockout.js) variable to keep track of the currently selected delivery which we need in order to navigate from the overview pages to the detail page

self.nav keeps the navigation history (nav.js) for our single page. It gives the impression to the user of navigating between different pages as usual. But it enables back button and allows creation of bookmarks within the single page application.

The name of the view will be stored in the view parameter. The default page will be the deliveries view.

A deliveryId paramater will be included in the querystring and intialized to null

On navigation we extract the deliveryId from the querystring and use it to filter the datasource

self.showsomething functions to invoke the navigation functionality. The showDelivery function receives the delivery object that was clicked so it can extract the deliveryId from it and attach it to the querystring so the delivery view can read it.

Note that the onNavigate function uses the findById method on the upshot datasource that doesn't exist in the original beta release of ASP.NET MVC 4. Add the following javascript code to the upshot.knockout.extensions.js file. The highlighted line below is already present in the file so don't add it again.

The mobile views

Now add a Index.mobile.cshtml to the \Views\Home folder. It's quite simple. When the DOM is ready a javascript function initializes the upshot library and initializes the knockout bindings to the MobileDeliveriesViewModel. The actual views are defined as partial views that are shown whenever their views are active.

Notice that the _DeliveryDetails view in Steven Sandersons example used the with: data-bind parameter. I couldn't get it to work so I encapsulated it within a parent div that checks the active view

In part 1 I recreate the first part of a demo by Steven Sanderson that shows a list of deliveries on screen, allows a user to mark an item as delivered and have the results immediately sent back to the server. In this second part the application will also show the deliveries grouped by customer and uses "Save All" and "Revert All" buttons so that the user can decide when to update the back-end.

Group by Customer

In order to group deliveries by customer, you'll need to a bit of javascript code that can go over the observable 'deliveries' array and group them together based on the referenced customer. This grouped data needs to be observable too. Steven has released the following bit of javascript called arrayUtils.js in order to achieve this:

If you would run the application now you will notice that knockout.js is keeping the values is the deliveries list and the customers list in sync.

Save, Revert, Exclude if delivered

Upshot can be configured to NOT synchronize the changes to the backend, meaning the user will have to click a "Save" button when he wants to submit. There is also the possibility to undo the accumulated changes or to put a filter on the datasource.
In order to enable above functionality, make the following changes to the DeliveriesViewModel.js

Set bufferChanges to true so that the upshot datasource will keep the changes in memory and not sync them with the back-end

Create a self.localDataSource that we can use for client-side operations like filtering the data

Create a self.excludeDelivered variable to keep track of the value of the checkbox saying the user wants to exclude already delivered items from the list

Create the self.saveAll and self.revertAll function to commit or revert the changes on the upshot datasource

Subscribe a function to the self.excludeDelivered observable that gets called everytime the value changes. Inside this function a new filter rule is created which is then applied to the local data source

To take advantage of the functionalities you can bind the view to the functions that have been put in place in the DeliveriesViewModel

Added two buttons of which knockout binds the click event to the appropriate functions in the DeliveriesViewModel

The "Exclude Delivered items" checkbox bound to the 'excludeDelivered' property in the viewmodel

The 'delivered' CSS class is bound to the existing 'IsDelivered' property which was foreseen on the view model. The 'updated' CSS class however is bound to the 'IsUpdated' property that comes built-in with the observable datasource

If you now mark items as delivered, they will get the 'delivered' AND 'updated' styles in both lists. These are nicely kept in sync by upshot. When the user clicks the "SaveAll" button, all changes will be submitted to the server and the 'updated' style will disappear. The "RevertAll" button undoes all the changes. Selecting "Exclude Delivered Items" will remove the delivered items from the shared datasource so they won't show up in either list.