Motown: Easy JavaScript Apps for Metro (Part 1)
The Web has arrived. Microsoft has made a bold move with Windows 8 that makes Web technology (HTML/CSS/JS) a first class citizen for building professional and commercial grade applications. Up until now, even with the advent of HTML 5 and incredibly fast JavaScript engines, Web applications have always seemed to have limitations compared to native development stacks for creating fully integrated and cutting edge user experiences. These days are over with the arrival of Windows 8 as Microsoft blends the Chakra JS engine and the hardware accelerated rendering / DOM environment of IE 10 with the new WinRT OS-level abstractions.
The WinJS libraries and bindings to WinRT provide a solid foundation for building applications but I feel that a significant gap remains. MS rightly, and I presume intentionally, left most of this gap open for the development community to fill. I created the Motown JavaScript library for Metro-style applications (HTML/CSS/JS) to fill the gap.
Motown empowers you and your team to:
- Create apps that are easy to maintain and extend
- Be more productive, freeing you to focus on the details of your app
- Make small and simple apps easily and larger more complex apps a possibility
Motown accomplishes this by:
- Eliminating glue code and boilerplate
- Providing architectural structure and modularity, eliminating spaghetti-code
- Making application architecture implicit in the configuration
- Providing clear, thoughtful and flexible APIs
In this post I will briefly introduce you to Motown by walking you through the creation of a small application that illustrates how Motown addresses one of the largest weaknesses of what Microsoft delivers out-of-the-box with WinJS. This weakness lies in how WinJS implements page navigation with regards to what it attempts to do in addition to what it does not.
Single-Page Style Navigation
Microsoft recommends that developers use a “single-page” style of navigation instead of a “multi-page” style in their applications. The idea is to prevent the browser from navigating to a different page, as if you had entered a new URL into the location field of a regular web browser. Doing this causes the browser to unload the current document, including all the images, stylesheets and JavaScript that are part of it. In a web application, or in this case a Metro-style application in JavaScript, this is obviously problematic because the unloading of the document effectively unloads your entire app. You might be able to reestablish/reinitialize your application in the new page that you navigate to but to do so would be a waste of time and system resources.
To assist developers in implementing “single-page” style navigation Microsoft provides a control they call “PageControlNavigator” in projects you create with the “Navigation Application” project tempate. The control assumes your “pages” use Microsoft’s WinJS.UI.Pages API. The former provides integration with the WinJS.Navigation API so that calls to “navigate(), back()” etc. use the functions defined in the latter to dynamically load content from other HTML files into the main document. As you navigate to different pages the PageControlNavigator swaps in the new page for the old page.
Using the Fragment Loader
Under the hood, the calls PageControlNavigator makes to the WinJS.UI.Pages API are using the WinJS fragment loader (WinJS.UI.Fragments). The fragment loader is a clever piece of code that allows you to load the content of an arbitrary HTML file into an element in the existing document. When you call WinJS.UI.Fragments.render() the loader goes out and intelligently parses the specified HTML file and first takes everything between then <body> tags and appends it to the “target” element you passed in. Then it takes all of the stylesheet and script references it encounters during parsing and adds them to the existing document. This functionality is the key to implementing the “single-page” navigation style.
The advantages to this approach are that it allows you build your application in a modular way. You can lazily load your application a page at a time (which improves performance and memory consumption), and as mentioned before, you do not have to completely unload/load your app as you do in the “multi-page” style.
Problems with the WinJS.UI.Pages API and PageControlNavigator
The WinJS.UI.Pages API and PageControlNavigator, both new as of the Consumer Preview release of Windows 8, take some good steps towards reducing the complexity of implementing “single-page” style navigation in your application. However, I think there are some flaws in this approach and Motown presents you with a better solution.
The first problem lies in the semantics of specifying the Page control’s URI with WinJS.UI.Pages.define(uri). This feels awkward and redundant. Why do you need to specify the URI of the HTML file the Page control is supposed to work with if that HTML file is already including the script defining the Page control? If the intent is to relate the Page control to the particular HTML file with a URI then that information is already implicit due to the inclusion.
To be fair this implementation does allow you to move the inclusion of the Page control’s script to another file and still maintain the relationship. However, if you do move the inclusion of the script defining the Page control for the URI to a different file (e.g., the default.html or main page) you would sacrifice modularity and the performance advantages of lazily loading the script in the HTML file that it “belongs to”.
Secondly, using WinJS.UI.Pages.define(uri) strongly couples your application’s logic to its presentation. In other words, with an MVC architecture in mind, the controller (the Page control) is strongly coupled to the view (HTML file) because of this hard-coded specification of the URI.
How would you reuse any of the logic defined in the Page control with a different view without introducing additional complexity? Ideally, you should be able to to use an arbitrary view with a particular controller and vice-versa, as long as the references the controller expects to find in the view are available using various querying functions (getElementById, queryAll, etc). If you want to share controller logic with this approach you will at least need to use “WinJS.UI.Pages.define(uri)” for each view you want to reuse the controller logic with and then come up with a way to pass the rest of the controller implementation into the second parameter of “WinJS.UI.Pages.define()”.
Finally, PageControlNavigator as implemented does not allow your pages to maintain their state in between navigations. Every time you navigate to a page, whether you have previously loaded the page or not, the PageControlNavigator implementation loads the page from scratch every time.
Think about a common situation in an application where you navigate to a “details” page from a “master” page. Should you really have to reload the whole master page from scratch each time you finish reviewing details and want to return to the master page? In order to give the user a seamless navigation experience where the master page is exactly the same as it was when he or she left you end up having to write code to save your page’s state and then “reset” it once the page is loaded to its initial state. This may be a good technique for situations where you want to be conservative with pages that consume a lot of memory, but it should be the exception and not the norm. Typical pages are most likely quite simple and do not significantly impact memory consumption. The other problem with this approach is that it invites memory leaks. Loading and then throwing away the page contents on every navigation has a tendency to produce a lot of DOM turnover. Developers will need to be careful with referencing DOM objects from closures and other be aware of other structural subtleties. In many ways this issue is a smaller version of the problem that PageControlNavigator and WinJS.UI.Pages is trying to solve in the first place, which is to keep you from having to reinitialize your application’s state across page navigations.
Using Motown to Avoid Pitfalls and Accelerate Application Development
Motown addresses all of these problems, keeps all of the benefits and goes even further with additional bells and whistles to help you create “Fast & Fluid™” navigations between pages without requiring redundant configuration or introducing coupling into your application. Let’s take a look at a basic Motown application and see how all of this is realized.
If you want to follow along with a real project download or clone the most recent version of Motown from my GitHub repository and install the Visual Studio template plugin before creating a new project. Follow these steps to do so:
- Download or clone Motown using the links above and unzip if you download a release archive.
- Copy the “MotownAppTemplate” folder from the root Motown directory into %HOMEPATH%\Documents\Visual Studio 2012\Templates\ProjectTemplates\JavaScript.
- Open Visual Studio and create a “New Project”.
- In the “New Project” dialog select: “Templates -> JavaScript” and then “Motown App” from the list on the right.
- Enter a name for your project and click “OK”.
When you create a new Motown application with the VS template you begin with a project that looks like this on disk:
+ Project Root |--+ views | |-- home.html |--+ controllers |--+ models |--+ images |--+ css | |--motown.css | |--home.css |--+ js | |-- motown.js | |-- application.js |-- default.html
Following typical Visual Studio project conventions for Metro-style JavaScript apps, the new application is configured to load the “default.html” file as the “Start Page”. However this page does not really have any content of its own and you will rarely need to edit it. You use it to bootstrap your application and it becomes the place in the DOM into which Motown loads your pages and displays them during navigation. In short, this is your “single page” in your “single-page” style navigation app.
The default.html file loads the motown.css stylesheet and the motown.js script first. Then it loads the “application.js” file, which is the place where you configure your application. Open this file and you will find the following:
'use strict';
MT.configApp({
name: 'Application Name',
namespace: /* Your Namespace Her e */'',
pages: [
'home'
]
});
Calling ‘MT.configApp()’ is all the code you need to start your application and load it’s home page. By default, Motown loads the page named “home”, and as you can see in the configuration, it is the only page we are currently defining in the ‘pages’ configuration property array. You will also notice that we have a view in the “views” folder named “home.html”. When you navigate to the “home” page Motown will automatically locate the view for the page in the “views” folder based on the naming convention: views/<pagename>.html
Go ahead and build/run the app. You will see the contents of the “home” page after your app launches.
Adding a New Page with Motown
Create an arbitrary HTML file with the name ‘page1.html’ and save it in the “views” folder of your project. Then, add the page to the “pages” configuration property of your app config accordingly:
'use strict';
MT.configApp({
name: 'Application Name',
namespace: /* Your Namespace Here */'',
pages: [
'home',
'page1'
]
});
Now build and run your app again. As before, and by default, Motown navigates to the “home” page at launch. This time, with your application running, open the JavaScript Console window in Visual Studio so to simulate some user navigation. Type or copy/paste the following into the console and type: WinJS.Navigation.navigate(‘page1′) <Enter>
You should see the content defined by the ‘page1′ HTML you saved in the ‘views’ folder. Now go back to the JavaScript Console and type: WinJS.Navigation.back(); You should see the ‘home’ page again. If you enter WinJS.Navigation.forward(); you will see ‘page1′ again, and so forth.
Please notice that when going back and forth between the “home” and “page1″ pages Motown is not loading a new copy of the page each time (like the PageControlNavigator class from Microsoft does). The pages maintain their state. You can verify this by using the DOM Explorer in Visual Studio to edit/add/remove something from one of the pages while your application is running and then issuing a sequence of “back”/”forward” navigations from the command line. The DOM for the page is exactly the same when you return.
Also notice that you did not need to add any controller logic or any other code to the view (page1.html) that would introduce any coupling.
NOTE: In a future post we will see how Motown preserves this benefit even when you add complex controller logic to your page.
You just added a new page to your application with almost no code, except for one String in the configuration. Now let’s examine how to perform one-time initialization for the new ‘page1′ page.
One-Time Initialization for Pages
The WinJS.UI.Pages API provides on optional method you can implement called “ready()” for the purpose of one-time page initialization. Motown uses a similar approach, providing you with an optional method called “viewReady”.
Let’s add some initialization to our ‘page1′ page (albeit trivial) to illustrate the use of this method. Our implementation will simply change the background color of ‘page1′ to green. Update your application.js file accordingly:
'use strict';
MT.configApp({
name: 'Application Name',
namespace: /* Your Namespace Here */'',
pages: [
'home', {
name: 'page1',
config: {
viewReady: function () {
this.viewEl.style.backgroundColor = 'green';
}
}
}]
});
Run the application again. Navigate to ‘page1′ and you will see the updated page with a green background. Please note that Motown calls WinJS.UI.processAll() appropriately when it loads the view for your page, thus activating any controls you have defined in the view. You never need to call WinJS.UI.processAll() directly.
Animating Navigation Transitions
With initialization in place, let’s add something to our application using Motown that the PageControlNavigator control does not allow us to do. To keep things looking nice and in harmony with the Metro aesthetic we probably want to add some animated transitions to our pages as the user navigates between them. To do so, we’ll need to update application.js again.
Motown allows you to provide as little or as much configuration as necessary for your pages. Now that we require more functionality in our ‘page1′ implementation, a little more configuration is required over the simple String (the page’s name) we had before. Update your application.js accordingly:
'use strict';
MT.configApp({
name: 'Application Name',
namespace: /* Your Namespace Here */'',
pages: ['home', {
name: 'page1',
config: {
viewReady: function () {
this.viewEl.style.backgroundColor = 'green';
},
beforeNavigateIn: function () {
return WinJS.UI.Animation.enterPage(this.viewEl);
},
beforeNavigateOut: function () {
return WinJS.UI.Animation.exitPage(this.viewEl);
}
}
}]
});
Run the application again and navigate to ‘page1′ page and back to ‘home’. You will notice navigating to and from the page results in a “slide-in” and “fade-out” animation, respectively. Take notice of how the beforeNavigateIn/beforeNavigateOut methods both return WinJS.Promise references. This allows Motown to chain together animations/transitions when going from one page to the next in a serial fashion. In other words: If the page you are navigating from returns a promise in its “beforeNavigateOut” implementation, you are guaranteed this promise will complete before Motown invokes the “beforeNavigateIn” method of the page you are navigating to.
If you do not need animations (or any other asynchronous code you might have) to run serially during navigation, simply remove the return statement to keep from returning a promise. Motown will then call “beforeNavigateIn” immediately after “beforeNavigateOut” returns. Experiment with this by adding another page and implementing beforeNavigationIn/Out methods that return promises and then navigate between the new page and ‘page1′.
Additional Navigation Lifecycle Methods
Although not used in our code thus far, Motown also provides “afterNavigateIn/afterNavigateOut”. These can be handy for doing things to the view of your page or its state after its view becomes visible or after its view is hidden, respectively.
With “beforeNavigateIn/beforeNavigateOut” the inverse effect is in play. The call to “beforeNavigateOut” occurs (and completes, if a promise is returned) before the view for the current page is hidden and “beforeNavigateIn” runs right before Motown shows the view for the new page.
The use cases for these navigation lifecycle methods are many and go beyond just implementing transitions. You will find yourself implementing these often for anything but the most trivial applications.
Motown Nuts & Bolts
By now you may be wondering what “viewEl” and “this” are in the lifecycle methods you implemented. Motown is an MVC-like library (MVP actually, if you adhere to the classical definitions of the MVC and MVP patterns) and as such, to implement each major unit of modularity (a Page), Motown employs the MVC design pattern.
We have focused mostly thus far on the “view” in MVC. The view in a Motown Page is the declarative HTML that defines the user interface. When Motown loads a view for a page it re-parents everything between the <body> tags of the view’s HTML to a new Element, then Motown adds this element to the existing DOM tree. This new element is the “viewEl” property. It is a reference to the page’s view in the DOM. The “this” reference in the code refers to the “C” in MVC, the controller. We will take an in depth look at the controller of a Motown page in the next post in this series.
Download Motown
Until then, download Motown from my Github repo and use the Visual Studio project template to try it out today. You may also view Motown API documentation here.
Happy Hacking.
