Introduction
At Build 2014, Microsoft announced that WinJS was going open source and the first goal was to make it available to web sites, not just Windows and phone apps. On September 17th, the first official release of WinJS 3.0 was made available and brought with it cross browser compatibility. Setting it up to work on your site is no different than any other JavaScript library. But extending a Universal, Windows or Phone app to also include a web interface that includes WinJS is another thing entirely.
In this article, I will demonstrate how to create a web app with WinJS 3 and then reuse that code in a Universal app. I will then show how you can extend the capabilities of your site to include native WinRT features without using Cordova to create a hybrid application.
Background
I will be talking about a lot of different technologies in this article. The following are some good reference points if you come across something you have not seen before:
Setting Up the Web Project
For these examples, I am using Visual Studio Professional 2013 with Update 3 and have the Web Essentials extension installed.
Start by creating a blank solution called HelloWeb
in Visual Studio. This is found under Other Project Types > Visual Studio Solutions > Blank Solution.
For our web app, we are going to create a TypeScript project, but you could also create any other type of ASP.NET project. Right click on the solution name, select Add > New Project > TypeScript > HTML Application with TypeScript and name it HelloWeb.Web
.
Go ahead and delete the existing app.css, app.ts and index.html. We will be recreating these, one by one. The first thing we are going to do is add the NuGet packages for WinJS and its TypeScript definition. Right click on the web project and select Manage NuGet Packages. Enter winjs in the Search Online box and you should see results that include Windows Library for JavaScript (WinJS) and winjs.TypeScript.DefinitelyTyped
. Install both of those into the web project.
Your project should now include two new folders, Scripts and WinJS.
Creating the Default Page
Now we can start adding our code. First, let’s create the default JavaScript file, similar to what you would find in a Universal app. We will be creating a TypeScript file for this which will create a JavaScript file for us. If you are new to TypeScript, see the references section for more information. Add a new folder called js by right clicking on the project name and selecting Add > New Folder. Now add a file called default.ts to this folder by right clicking on it and selecting Add > TypeScript file. Add the following code to the default TypeScript file:
module HellowWeb {
"use strict";
var app = WinJS.Application;
if (location.protocol == "http:" || location.protocol == "https:") {
app.onready = () => {
WinJS.UI.processAll();
};
app.start();
}
}
When you save the file, it should generate a hidden default.js which you can see in the left pane.
Let’s go through this file line by line.
- The reference path is used by TypeScript for intellisense and is pointed to the winjs typing we downloaded with the NuGet package earlier.
- We are encapsulating everything in a TypeScript module, similar to a .NET namespace, to keep everything out of the global scope.
- Use strict helps you catch common coding issues with JavaScript and is a nice safety feature.
- The
app
variable is just creating a shortcut to WinJS.Application so we don’t have to type the entire thing out every time. I used this because you will often see this in the default Universal app templates. - The location protocol is used to detect that we are running in a browser. More on that in a moment.
app.onready()
signals that the page is ready for WinJS, similar to jQuery’s ready event. WinJS.UI.processAll()
finds all the WinJS controls on the page and sets them up. app.start()
fires off the WinJS lifecycle and gets things running.
I did not dig too deep into how WinJS works. For that, I recommend visiting the Try WinJS site listed in the references section.
Detecting Web, Phone or Windows Mode
When Universal apps were introduced, we got a new project type called a Shared Project. This is not a standard project type and does not compile into any type of self-contained library like Portable Class Libraries, it’s more of an improvement on linked files. The code in these projects would compile into both the Windows app and the Phone app. If you had code that you only want to run in one or the other, you would use compiler directives like this:
#if WINDOWS_APP
#endif
#if WINDOWS_PHONE_APP
#endif
Universal apps do not yet support adding a web project, so they will also not support any type of web based compiler directive. Instead, we can use the URL of the application to determine if we are running under a web site or not, which is exactly what we do in the previous code by looking for http or https. Windows and Phone apps will run under a ms-appx protocol.
Now we can add our default HTML page. Right click on the project and select Add > HTML page and name it default. We are doing this to be consistent with the Universal projects. You may also want to set this as your start page for debugging under Project Properties > Web > Start Action > Specific Page.
Add the following markup:
<html>
<head>
<meta charset="utf-8" />
<title>Hello Web</title>
<!--
<link href="/WinJS/css/ui-dark.css" rel="stylesheet" />
<script src="/WinJS/js/base.min.js"></script>
<script src="/WinJS/js/ui.min.js"></script>
<!--
<script src="/js/default.js"></script>
</head>
<body>
<h1>Hello Web!</h1>
</body>
</html>
There is really nothing special going on here, we are just setting up the references to WinJS and our own files.
If you build your project right now, you will get a bunch of errors relating to the typings we added for WinJS earlier. This is because the typing includes all the legacy versions (1.0, 2.0 and 2.1) of WinJS. Go into your Scripts/typings/winjs folder and delete all the files except winjs.d.ts.
Your project folder should now look like this:
Build your app and run it. You should get a simple web page that is styled to look like a Windows Modern app.
Congratulations! You now have a working TypeScript based web app running WinJS that you can continue to build out. Next, let’s reuse that code structure in a Windows and Phone app.
Setting Up the Universal App Project
Right click the solution name and select Add > New Project. Pick the Blank App (Universal App) template under JavaScript > Store Apps > Universal Apps and name it HelloWeb
.
Before we make any changes here, let’s talk about code re-use strategies.
Code Re-Use
As we discussed earlier, Universal Apps do not support web projects (yet). We still have two strategies that we can use to share our code: linked files and post build events.
Linked Files are easy to setup but have a big issue, Shared Projects do not support them. You will need to add the linked file to the same location in both the Windows and the Phone project.
Post build events can be used, such as xcopy, to copy the files to the Shared Project folder and then add them to the project. The downside here is that it is not completely obvious that they are copies, if you make changes to them, those changes will be lost on the next build.
There are certainly more options available depending on your source control and third party tools, but these two options are the easiest to setup. For this article, we will be using linked files to both the Windows and Phone projects, and save the Shared Project for WinRT specific functionality.
Let’s start by cleaning up the files we are not going to be using. Delete the css folder and the default.html file in the Windows
and WindowsPhone
projects. Next, right click on the Windows
project and select Add > Existing Item, navigated to the web project directory and select and select the default.html file. Before selecting Add, click the down arrow on the Add button and select Add as Link instead. Repeat this process with the Windows Phone project.
Let’s do the same thing with the TypeScript’s JavaScript file. Note that this file does not appear in the Web project’s list of files because it is hidden and created automatically by TypeScript. Create a js folder in the Windows project, add the linked default.js file from the web project and repeat for the Phone
project. Just a reminder, we are not putting these in the Shared Project because it does not support linked files.
The last step to prepare the Windows and Phone projects is to include the WinJS and TypeScript typing NuGet packages to each project the same way we did it for the Web project. Alternatively, you could also set these up as linked files, it’s just a matter of preference. Just don’t forget to remove the legacy typings from the winjs folder.
Your Windows and Phone projects should now look like this:
If you try to build the project right now, it will not work. There is a duplicate default.js file in the Shared Project. We’ll take care of that next.
Windows Specific Functionality
After all that, we are finally able to add in support for extending the web project with WinRT specific functionality. The path we are going to take is to reference both a web based JavaScript file and a Windows based JavaScript file from the default page. The web project will contain an empty Windows based JavaScript file and the Shared Project will contain a real JavaScript file. We will demo this by setting up the default.js file for the Windows and Phone projects which must respond to Windows based events, but this strategy can easily be reused for any JavaScript file you create.
First, rename the Shared Project’s js folder to jsrt. Now, it will not conflict with the default.js files in the Windows and Phone projects.
Next, add a jsrt folder to the web project and add a JavaScript file named default.js to this folder. Leave the file empty. The only reason this file exists is because we are using the same default.html for all our projects.
Now add a reference to this file in the default.html page after the existing default.js page reference.
<!--
<script src="/js/default.js"></script>
<script src="/jsrt/default.js"></script>
So if you remember, we added this line of code when we set up the /js/default.js file:
if (location.protocol == "http:" || location.protocol == "https:") {
This is one way you can include code that will only run in your web project. In our case, we fired up WinJS in this block of code. When it runs under Windows, it will ignore it. So we need to activate WinJS for Windows in a different manner, which we do in the /jsrt/default.js file. Since Windows specific code will only be included in the jsrt files, there is no need for a protocol checking statement.
We don’t need to touch the default.js file in the jsrt folder of the Shared Project, it’s good to go. We also have not setup TypeScript for this file. At the time of this article, TypeScript does not work with Shared Projects. You will need to live with just JavaScript or put all your TypeScript files in the Windows project and link them to the Phone project instead of using the Shared project. Hopefully, this limitation will be resolved soon.
(function () {
"use strict";
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState
!== activation.ApplicationExecutionState.terminated) {
} else {
}
args.setPromise(WinJS.UI.processAll());
}
};
app.oncheckpoint = function (args) {
};
app.start();
})();
Build the solution and run the web project to make sure everything is still working. Now set the Windows project as the startup project and do the same thing. You should see an identical application running as a Windows app. And now, do the same for Windows Phone.
We now have a code base with a lot of code reuse running across Windows, Phone and Web!
Note: If you try to do a Rebuild the entire solution and get an error about Payload files not existing, just build the web project first.
Some Final Cleanup
Everything is working, but let’s do a couple more things to make the development experience better.
First, remove the old references to the Windows Library from the References folder of the Windows and Phone projects. We won’t be using them.
Second, add a _references.js file to the Web Project’s Scripts folder by right clicking on the Scripts folder and selecting Add > _references.js Intellisense file. This will give you intellisense for your JavaScript libraries. It is setup for AutoSync, so any changes to your libraries will automatically be references here. We just need to setup the ones we already have. Edit this file to look like this:
/// <autosync enabled="true" />
/// <reference path="../WinJS/js/js/base.js" />
/// <reference path="../WinJS/js/ui.js" />
/// <reference path="../WinJS/js/winjs.js" />
/// <reference path="../WinJS/js/en-us/ui.strings.js" />
Repeat the same thing for the Windows and Phone projects.
By default, those project types will not look for this file. You can add them under Tools > Options > Text Editor > JavaScript > Intellisense > References. Change the Reference Group to Implicit (Windows 8.1) and add a reference to ~/Scripts/_references.js. Repeat the same change for Implicit (Windows Phone 8.1) as shown below.
Moving Forward
As you continue to build out your application, keep a few things in mind:
Work in the web project to create the base of your application. As you add more html, JavaScript, CSS, etc. be sure to also add them as linked files to the Windows and Phone projects.
Anything that is WinRT specific should be added to the Shared Project and empty matching files should be added to the Web project.
Remember this is native code, it is not a hybrid wrapper. You will be getting the full capabilities and performance of a native JavaScript application in Windows. If you want to extend your application even further to iOS and Android, you will need to use something like Cordova.
Limitations Review
Hopefully with the next big update to Visual Studio, we will see web projects become an integrated part of Universal projects, but for now there are some limitations which we worked around in this article.
- WinJS 3 libraries are not included in Universal apps. You need to add them through a NuGet package yourself. This also means they will be downloaded as part of your app, taking up slightly more disk space.
- Shared Projects do not support linked files.
- TypeScript does not support Shared Projects yet.
- Rebuilding the solution may cause some issue with linked files. If this happens, build the web project first.
Also keep in mind, many WinJS samples on the web will use a Microsoft CSS grid for layout of controls. This is fine in a Windows only app, but it is not cross-browser compatible.
History
- 09/27/2014 - First version posted