Playing with WinJS as a cross-platform UI layer?

Just a bit of an experiment for me really but since the WinJS library got open-sourced (go here to take a look at that) I’ve been thinking a bit about how it opens up the WinJS library to be used in different places.

I’ve been wanted to experiment with that for a while and so I dusted off the old code that I’ve got for doing flickR searches (from this post) and I thought I’d try and make use of it in a few places as below;

1 – Playing at Home – WinJS inside of a Windows 8.1 Native App

This is what WinJS was created for in the first instance and so it’s not surprising that I can use WinJS as a UI inside of a Windows 8.1 app. I knocked up a very basic, rough-and-ready UI as per the screenshot below;

image

and if I make this a little more portrait than landscape then it sort-of, kind-of responds;

image

This small bit of functionality is just built with some sketchy JS, HTML and CSS files. Specifically, I have a UI defined in HTML as;

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>flickr search</title>

    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>

    <!-- App167 references -->
    <link href="/css/default.css" rel="stylesheet" />
    <script src="/js/default.js"></script>
    <script src="js/flickrSearchLibrary.js"></script>
</head>
<body>

    <div id="myTemplate" data-win-control="WinJS.Binding.Template">
        <div class="itemContainer">
            <img class="itemImage" data-win-bind="src: imageUrl WinJS.Binding.oneTime" />
            <div class="win-type-large itemTitle win-type-ellipsis" data-win-bind="textContent: title WinJS.Binding.oneTime">
            </div>
        </div>
    </div>

    <h1 id="title">flickR Search</h1>
    <div id="listView"
         data-win-control="WinJS.UI.ListView"
         data-win-options="{itemTemplate:select('#myTemplate')}">
    </div>
    <div id="searchBox"
         data-win-control="WinJS.UI.SearchBox">

    </div>

</body>
</html>

and then that default.js file that lives behind this is;

(function ()
{
  "use strict";

  var app = WinJS.Application;

  app.onactivated = function (args)
  {
    var promise = WinJS.UI.processAll();

    promise.then(
      function ()
      {
        // Grab the control
        var searchBox = document.getElementById('searchBox').winControl;
        searchBox.addEventListener('querysubmitted', onQuerySubmitted);
        searchBox.addEventListener('suggestionsrequested', onSuggestionsRequested);
      });

    args.setPromise(promise);
  };

  function onSuggestionsRequested(e)
  {
    var promise = FlickrSearchLibrary.FlickrSearcher.getHotTagsAsync(e.detail.queryText);

    promise.done(
      function (results)
      {
        e.detail.searchSuggestionCollection.appendQuerySuggestions(results);
      }
    );
    e.detail.setPromise(promise);
  }

  function onQuerySubmitted(e)
  {
    var queryText = e.detail.queryText;

    var promise = FlickrSearchLibrary.FlickrSearcher.searchAsync(queryText);

    promise.done(
      function (results)
      {
        var bindingList = new WinJS.Binding.List(results);
        var listControl = document.getElementById('listView').winControl;
        listControl.itemDataSource = bindingList.dataSource;
      }
    );
  }
  app.start();

})();



and I’d point out that this is entirely WinJS. There’s no native Windows.* calls in this code although, naturally, I could be making those native calls into Windows functionality.

There’s then a little piece of JavaScript which does the searching of flickR for me;

(function ()
{
    "use strict";

    var apiKey = 'API KEY';
    var serviceUri = "https://api.flickr.com/services/rest/?method=";
    var baseUri = serviceUri + "flickr.photos.search&format=json&nojsoncallback=1&";
    var hotlistUri = serviceUri + "flickr.tags.getHotList&format=json&nojsoncallback=1&api_key=" + apiKey;
    var MAX_HOTTAGS = 5;

    var flickrUrl = WinJS.Class.define(
        function(searchTerm, pageNo, perPage, contentType)
        {
            this.searchTerm = searchTerm;
            this.pageNo = pageNo || this.pageNo;
            this.perPage = perPage || this.perPage;
            this.contentType = contentType || this.contentType;
        },
        {
            searchTerm: 'Windows',
            pageNo : 1,
            perPage : 50,
            contentType : 1,
            toString : function()
            {
                return(
                    baseUri + 
                    "api_key=" + apiKey + "&" +
		    "safe_search=1" + "&" +
                    "text=" + this.searchTerm + "&" +
                    "page=" + this.pageNo + "&" + 
                    "per_page=" + this.perPage + "&" + 
                    "content_type=" + this.contentType);
            }
        }
    );

    var flickrSearcherAsync = function(searchTerm)
    {
        var searchUrl = new flickrUrl(searchTerm);
        var promise = WinJS.xhr(
        {
            url : searchUrl.toString()
        });
        promise = promise.then(
            function(results)
            {
                var deserialized = JSON.parse(results.responseText);

                deserialized.photos.photo.forEach(
                    function (photo)
                    {
                        photo.imageUrl =
                            "https://farm" + photo.farm +
                            ".static.flickr.com/" + photo.server +
                            "/" + photo.id +
                            "_" + photo.secret + "_m.jpg";
                    }
                );

                return (deserialized.photos.photo);
            }
        );
        return(promise);
    }

    function flickrHotTagsAsync(userText)
    {
        var promise = WinJS.xhr({ url: hotlistUri });

        promise = promise.then(
            function (xhr)
            {
                var results = xhr.responseText;
                var deserialized = JSON.parse(results);
                var returnedValues = [];
                var regEx = new RegExp(".*" + userText + ".*");

                deserialized.hottags.tag.forEach(
                    function (tag)
                    {
                        if (regEx.test(tag._content))
                        {
                            returnedValues.push(tag._content);
                        }
                    }
                );
                return (returnedValues.slice(0, MAX_HOTTAGS));
            }
        );

        return (promise);
    }

    WinJS.Namespace.define("FlickrSearchLibrary",
        {
            FlickrSearcher : 
            { 
                searchAsync: flickrSearcherAsync,
                getHotTagsAsync : flickrHotTagsAsync
            }
        }
    );
})();




There’s then a pretty horrible piece of CSS included in default.css which just sets up the styles for the item templates and the headings and so on, I’ll not include it here because it’s short but also really, really ugly Smile

And that’s pretty much all there is.

2 – Experimenting – WinJS inside of a Windows 8.1 Hybrid App Written in C#

To try and get a feel of what it’s like to move this basic code out of its environment and into another one, I thought I’d re-home it inside of a Windows 8.1 app written in .NET. That is – make it into a hybrid app.

I made a very basic XAML UI;

<Page x:Class="App176.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App176"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <WebView x:Name="webView" />
  </Grid>
</Page>

with the key part being, of course, the webView and then I went off to the web trying to find the right version of the WinJS libraries to download rather than just copying the JavaScript out of my previous Windows 8.1 project.

I went on a journey which had me visiting;

  1. The WinJS Dev Center
  2. GitHub

and following the “Build WinJS” instructions on that page to clone the git repository and run a bunch of grunt files in order to end up with the CSS, JS and font files.

I dropped those into a folder in my C# solution called WebContent along with the HTML/CSS/JS files that I made in Step 1 above;

image

and so now I have all my HTML/CSS/JS content (including WinJS) living in that WebContent folder.

I needed to hack the HTML file such that references such as;

image

became a little more aligned with my new structure;

image

and then I just need to tell the WebView control to load up that content. As far as I know, the WebView control won’t just load content using a ms-appx:/// URL to load from the application’s package.

It does support ms-appdata:/// and it supports regular web URLs but I don’t think it’s prepared to load app package content directly so I had to write a resolver to get it to do that which is no great shakes (I have a snippet for one inside of my Visual Studio as I’ve shown it in demos many times).

That means that I end up with some “code behind” my MainPage.xaml file that looks like this;

using System;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.Web;

namespace App176
{
  // stole this code from MSDN.
  public sealed class StreamUriWinRTResolver : IUriToStreamResolver
  {
    public IAsyncOperation<IInputStream> UriToStreamAsync(Uri uri)
    {
      if (uri == null)
      {
        throw new Exception();
      }
      string path = uri.AbsolutePath;

      return GetContent(path).AsAsyncOperation();
    }
    async Task<IInputStream> GetContent(string path)
    {
        Uri localUri = new Uri("ms-appx:///WebContent" + path);
        StorageFile f = await StorageFile.GetFileFromApplicationUriAsync(localUri);
        IRandomAccessStream stream = await f.OpenAsync(FileAccessMode.Read);
        return stream.GetInputStreamAt(0);
    }
  }
  public sealed partial class MainPage : Page
  {
    public MainPage()
    {
      this.InitializeComponent();
      this.Loaded += OnLoaded;
    }

    void OnLoaded(object sender, RoutedEventArgs e)
    {
      var uri = this.webView.BuildLocalStreamUri("Foo", "default.html");

      this.webView.NavigateToLocalStreamUri(uri,
        new StreamUriWinRTResolver());
    }
  }
}



and that’s pretty much all I need to create this slightly odd hybrid Windows 8.1 application where I’m running some C# and XAML and using it to host HTML/CSS/JS with a WinJS UI. I changed my XAML a little bit just to make it more obvious;

  <Grid Background="Red">
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <WebView Grid.Row="0" x:Name="webView" />
    <TextBlock Grid.Row="1" Text="HTML hosting provided by XAML and .NET"
               HorizontalAlignment="Left" />
  </Grid>

and there it is;

image

Step 3 – WinJS Inside of a Web Page

WinJS is a bunch of web technology so I’d “kind of” expect that it’d feel quite happy inside of a web browser. With that in mind, I figured that I’d make a new website inside of Visual Studio running off my local machine using the exact same bits that I’d just used in Step 2 above;

image

and then I published this up to Azure and that’s here running from Azure (I took it down afterwards as the JS had my embedded API key for flickR in it Smile);

image

and in the “modern” browser, it looks an awful lot like where I started off in building a native Windows 8.1 app;

image

a couple of things I had to check here – I’m not sure why but the SearchBox control doesn’t seem to be firing its “SuggestionsRequested” event when running in the browser although it does seem to fire its “QuerySubmitted” event. The other thing was that I was maybe 75% confident that this would work at all when I first tried it in a browser due to doing cross-origin calls but IE’s F12 tools showed that this is working out ok;

image

There is a bit of a layout problem lurking for me inside of that UI though which will become more visible when I get to Android but is hidden because I’ve used IE to view the page above. If I’d used Chrome, I’d see different results…

Step 4 – WinJS Inside of an Android App ( written, ahem, in .NET! )

I wanted to see how this would work out when running inside of an app on another platform. The easiest place for me to try it out is on Android and the easiest way for me to try it out is via Xamarin. Naturally, most Android developers wouldn’t be taking that approach but if I want to try something out quickly then C# is going to work best for me.

So…what’s it like to host this WinJS UI code inside of a hybrid app for Android written in .NET? Winking smile

I thought I’d give it a whirl and see whether I could get it up and running. I wasn’t entirely sure how I build a “WebView” app in Xamarin so I was very relieved to see that the Xamarin guys have a template for it!

image

which effectively gives me this bit of UI;

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:minWidth="25px"
    android:minHeight="25px">
    <WebView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:id="@+id/webView" />
</LinearLayout>

and a bit of activity code which grabs hold of that WebView control and loads up some HTML into it. As it happens, that code in the template goes way beyond what I was hoping for in that it makes use Razor from ASP.NET which took me a good few minutes to digest but I largely discarded that code and went with a much simpler setup. I dropped the HTML bits into the Android assets folder (not 100% sure that this is the right thing to do);

image

and then hacked the code that the template gave me a little (mostly to remove things that I didn’t need);

  public class MainActivity : Activity
  {
    protected override void OnCreate(Bundle bundle)
    {
      base.OnCreate(bundle);

      // Set our view from the "main" layout resource
      SetContentView(Resource.Layout.Main);

      var webView = FindViewById<WebView>(Resource.Id.webView);
      webView.Settings.JavaScriptEnabled = true;

      webView.LoadUrl("file:///android_asset/WebContent/default.html");
    }
  }

and that actually works fine. The only problem that I hit (as you’ll spot below) is that my layout wasn’t any good;

image

that looks better than it really is, if I scroll things down a little;

image

then the search box has clearly moved to live below the results list – this is the issue I flagged at Step 2 above which hits me because I’d chosen to use CSS Grid layouts and those aren’t implemented anywhere outside of IE (http://caniuse.com/css-grid).

This wasn’t WinJS doing stuff wrong – it’s just my basic layout of the simple page that I made so I needed to just change that layout to use a different mechanism (I went with FlexBox which is implemented in other browsers) and then I have the same code running in 4 places;

Windows 8.1 Native;

imageimage

Windows 8.1 Hybrid;

imageimage

Web Browser (Chrome);

imageimage

and Android (via a Hybrid application written in .NET);

image

As I said at the start – mostly “just for fun” but I’d been toying with the idea for a little while and wanted to try it out here as WinJS has become something that can be more than just a library for building Windows/Phone UI – it can go anywhere.

Azure Mobile Services–Custom APIs and URI Paths

In the previous post where I was taking a look at Custom URIs in Azure Mobile Services I’d got to the point where I was defining server-side handlers in JavaScript for specific HTTP verbs.

For instance, I might have a service at http://whatever.azure-mobile.net/myService ( I don’t have this service by the way Smile) and I might want to accept actions like;

  • GET /myService
  • GET /myService/1
  • POST /myService
  • PATCH /myService/2
  • DELETE /myService/3

In my previous post I didn’t know how to do this – that is, I didn’t know how I could enable Mobile Services such that I could add something like a resource identifier as the final part of the path and so I ended up using a scheme something like;

  • GET /myService?id=1
  • DELETE /myService?id=2

and so on.

Since that last post, I’ve been educated and I learned a little bit more by asking the Mobile Services team. I suspect that my ignorance largely comes from not having a real understanding of node.js and also of the express.js library that seems to underpin Mobile Services. Without that understanding of the surrounding context I feel I’m poking around in the dark a little when it comes to figuring out how to tackle things that aren’t immediately obvious.

Since asking the Mobile Services team, I’ve realised that the answer was already “out there” in the form of this blog post which covers similar ground to mine but comes from a more trusted source so you should definitely take a look at that and the section called “Routing” is the piece that I was missing (along with this link to the routing feature from express.js).

Even with that already “out there”, I wanted to play with this myself so I set up a service called whatever.azure-mobile.net and added a custom API to it called myService.

When you do that via the Azure management portal, some nice bit of code somewhere throws in a little template example for you which looks like this;

exports.post = function(request, response) {
    // Use "request.service" to access features of your mobile service, e.g.:
    //   var tables = request.service.tables;
    //   var push = request.service.push;

    response.send(200, "Hello World");
};

and what I hadn’t appreciated previously was that this is in some ways a shorthand way of using the register function from express.js as in a longer-hand version might be;

exports.register = 
    function(api)
    {
        console.log("Hello");
        api.get('*', 
            function(request, response) 
            {    
                response.send(200, "Hello World");
            });
    };

Now, when I first set about trying to use that I crafted a request in Fiddler;

image

and I got a 404 until I realised that I needed to append the trailing slash  – I’m unsure of exactly how that works;

image

but this does now mean that I can pick up additional paths from the URI and do something with them and there’s even some pattern matching built-in for me which is nice. For instance;

exports.register = 
    function(app)
    {
        app.get('/:id',
            function(request, response)
            {
                response.send(200, "Getting record id " + request.params.id);
            });
            
        app.get('*', 
            function(request, response) 
            {    
                response.send(200, "Getting a bunch of data");
            });
            
        app.delete('/:id',
            function(request,response)
            {
                console.log("Deleting record " + request.params.id);
                response.send(204);  
            });     
    };

and that’s the sort of thing that I was looking for in the original post – from trying this out empirically, it does seem to be important to add the more specific routes prior to the less specific routes. That seemed to make a difference to handling /myService/ versus /myService/1.

Windows 8–Updated flickR Search Demo in HTML/JS

In a lot of the Windows 8 sessions that I’ve given around the UK I’ve been building out a simple demo app that searches on flickR for some photos. Over time I’ve come to build that app usually with some combination of the following;

  • Build out the basics of the app in .NET
  • Rebuild the same thing in C++ and, usually, I re-use the .NET component that I use to do flickR searching in order to show that we can make custom WinRT components and build them into hybrid apps
  • Rebuild the same thing in JavaScript and, usually, I re-use the .NET component again to show that works in JavaScript and I then remove the .NET component and replace it with a pure JavaScript implementation of flickR searching

and, based on audience and time, I sometimes take either the .NET example or the JavaScript example ‘forward’ and sketch out areas like;

  1. Tiles and Toasts
  2. Search
  3. Share
  4. Settings
  5. Screens/Views
  6. Process Lifecycle Management
  7. Storage – Local Storage
  8. Storage – SkyDrive Storage
  9. Windows Azure Mobile Services
  10. Background Tasks

A number of people who came along to the sessions said that they’d like to see this demo recorded along with all the various assets and code-snippets and so on such that they could walk through it in their own time and across both XAML/.NET and HTML/JS.

In this post, I’ll provide the HTML/JavaScript implementation. The .NET version is already written up in this post.

This is just a demo and was cooked up to show people things rather than to provide a great user experience or provide guidance around how to structure your code or something like that – I generally bang code into one big file rather than try to be elegant or leap off into fancy patterns or similar.

I’d recommend downloading the original video files from Vimeo and running them at about 1.5x the play speed as you’ll get through them a little quicker than if you’re streaming them and playing them at their original play speed.

Assets/Snippets

At each step I dropped whatever assets I’ve used into this SkyDrive folder – snippets, pictures, code files, etc;

http://sdrv.ms/UkaSV8

so that everything that I’ve used is available to you subject to you taking it entirely as it is and understanding that it’s from a demo and I can’t offer you a support service on its use.

Here are the individual videos if you want to consume that way;

Step 0 – Creating the Projects

Step 1 – Searching & Displaying Results

Step 2 – Adding Tile/Toast Notifications

Step 3 – Adding Search

Step 4 – Adding Share (Source)

Step 5 – Adding Settings

Step 6 – Screens/Views

Step 7 – App State

Step 8 – Saving a Photo

Step 9 – Saving to SkyDrive

Step 10 – Azure Mobile Services

Step 11 – Background Tasks

Conclusion

Enjoy – this demo is very definitely getting due for retirement but I’ve had good mileage out of using different pieces of it and it taught me a lot along the way  Smile