Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Playing with WinJS as a cross-platform UI layer?

Blogs

Mike Taulty's Blog

Elsewhere

Archives

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.


Posted Fri, Jul 11 2014 1:12 PM by mtaulty

Comments

cross platform native mobile development wrote re: Playing with WinJS as a cross-platform UI layer?
on Fri, Aug 8 2014 7:20 AM

WinJS open source>..............................Well , great . But unfortunately many apps do not use WinJS at all........Your concept is good for UI player, I will surely try it. Well, nice write up.

Danny wrote re: Playing with WinJS as a cross-platform UI layer?
on Fri, Sep 5 2014 12:37 PM

I use other tecnique. I use WebViewBrush.Create a relatngce under webview or in sobstitution. Some like:and a functionpublic void ThisBrush()        {            WebViewBrush b = new WebViewBrush();            b.SourceName = "ViewHtml";            b.Redraw();            ViewHtml.Visibility = Windows.UI.Xaml.Visibility.Collapsed;            ViewHtmlBrush.Fill = b;        }        public void ThisDeBrush()        {            ViewHtml.Visibility = Windows.UI.Xaml.Visibility.Visible;            ViewHtmlBrush.Fill = new SolidColorBrush(Windows.UI.Colors.Transparent);        }What do you thing?is possible to add a superimposed text