I've seen this question come up twice lately, once via email and again on StackOverflow. The questions are something like this:

My app has strange behavior with its template bindings with WinJS. When I navigate to a page and then back, everything is fine, but if I navigate to the same page then the bindings get all jumbled and I see only the raw JSON.

I have an app written in JavaScript whose app bar commands navigate to different pages, including the page that's currently loaded. Navigating between different pages works fine, but navigating to the same page (essentially reloading it) loses event handlers on my buttons.

In short, strange things seem to happen when navigating (with WinJS.Navigation.navigate) to the same page that's already loaded. Here's what's happening.

Page navigation in WinJS is just a matter of DOM replacement. When you navigate using the navigator.js file that's part of the Visual Studio templates, the target “page” contents are loaded into the DOM and then the previous “page’s” contents are unloaded. You can see this in navigator.js in the _navigating method. It creates a new element for the page being loaded, renders that fragment therein, and then unloads the old page.

The ready method for the new page, however, is called before the old page is unloaded (this was a change in WinJS 2.0, as WinJS 1.0 unloaded the old page before calling ready). The upshot of this is that when you navigate to the same page that’s already loaded, page.html(A) is in the DOM when you load page.html(B).

As a result, anything you attempt to do in the DOM during the processed or ready methods, which includes adding event listeners or attaching templates, will find the elements in page.html(A) and not page.html(B). That is, when getElementById traverses the DOM it finds elements in (A) first and stops there; locating templates works in the same way, so any data bindings that include templates will get hooked up to the templates in (A).

But then, of course, as soon as you return from the ready method, page.html(A) gets unloaded, so all your hard work is thrown away!

Again, this is a behavior that appears when navigating to the same page that's already loaded, so the solution is to avoid navigating to the same page because it's just fraught with peril. In some cases you can disable the navigation controls in your UI so you can't navigate to the current page. However, that's not always easy to do, so here's a way to wrap your call to WinJS.Navigation.navigate with a check for whether you're already on the target page:

function navigateIfDifferent(target) {
    var page = document.getElementById("contenthost").winControl.pageControl;
    var fullTarget = "ms-appx://" + Windows.ApplicationModel.Package.current.id.name + target;
    if (fullTarget !== page.uri) {
        WinJS.Navigation.navigate(target);
    }
}

This assumes that the PageControlNavigator control that you're using is in a div called "contenthost" in default.html (which is what the VS template gives you). What I'm then doing is building the full in-package URI for the target page and comparing that to the uri of the current page control, which is also a full in-package URI. You could also strip off the ms-appx:// part from the current page URI and compare to the target URI. Either way–you can then replace calls to WinJS.Navigation.navigate with navigateIfDifferent.

On the flip side, there are likely some scenarios where you specifically want to reload or refresh the current page, so a self-navigation seems like a way to do this. It should be fairly obvious, though, that any page initialization you do in ready or processed can be moved into separate methods that you call elsewhere to refresh the page. Remember too that you can call WinJS.Binding.processAll at any time in your code to refresh the data binding–it's not specific to page loading or other initialization procedures.

I'd recommend this as the first choice, because it won't mess with everything else that's been set up in the DOM already, and should perform better than reloading everything. 

However, if you still want to navigate to the same page to reload, then you need to defer the processing you'd normally do in ready to a later time. You could use a call to setImmediate for this purpose (it should work–I haven't tried it directly), but a simpler means (thanks to Guillaume Leborgne) is to make sure you scope any element queries or selections using the page's root element that's passed as the element argument to the ready method. Using this as the starting point, you'll restrict the scope to the newly-loaded page rather than the whole document.

And as a final note, you can always go to the lower-level WinJS.UI.fragments API to do your own page-loading implementation.


When using WinJS page controls to implement pages and navigation in an app, it's important to understand that any and all CSS that is loaded on behalf of those pages is cumulative. Because page controls are just performing DOM replacement within the context of the app's root page, you get the benefit of preserving the global JavaScript context but at the same time get the sometimes-frustrating behavior of preserving all the CSS styles as well.

For example, let's say the app's root page is default.html and its global styles are in css/default.css. It then has several page controls defined in pages/page1 (page1.html. page1.js, page1.css), pages/page2 (page2.html. page2.js, page2.css), and pages/page1 (page3.html. page3.js, page3.css). Let's say that page1 is the 'home' page and is loaded into default.html at startup. This means that the styles in default.css and page1.css have been loaded.

Now the user navigates to page2. This causes page1.html to be dumped from the DOM, but its styles remain in the stylesheet. So when page2 is loaded, page2.css gets added to the overall stylesheet as well. This means that any styles in page2.css that have identical selectors to page1.css will overwrite the latter's. The same thing happens also when the user navigates to page3–the styles in page3.css get loaded and overwrite those that already exist.

The tricky part is what then happens if the user navigates back to page1. Because the apphost's rendering engine has already loaded page1.css into the stylesheet, page1.css won't be loaded again. This means that any styles that were overwritten by other pages' stylesheets will not be reset to those in page1.css–basically you get whichever ones were loaded most recently.

The same thing happens with .js files, by the way, as those are not reloaded if they've been loaded already. The lesson here is to not duplicate variable names between those files.

On that thought, there are two ways to get around the CSS issue. One is to just use a global stylesheet (i.e. default.css) and not do much in the individual page .css files unless you're certain it's local. The second way is to make sure that each page has a top-level div with a page-name class, e.g. <div class="page1">, which allows you to then scope all style rules in page1.css with the page name, e.g.

.page1 p {
    font-weight: bold;
}

This will prevent collision between all the page<n>.css files.

Another place where this can arise is if you try to use the ui-light.css and ui-dark.css WinJS stylesheets in different pages of the same app. Because there are only two files here, whichever one is used first will be overwritten by the second, with the effect that subsequent pages that refer to ui-light.css will still appear with the dark styles.

The solution to this particular problem comes from the fact that where the styles differ, those in ui-light.css are scoped with a CSS class win-ui-light and those in ui-dark.css are scoped with win-ui-dark. This means you can just refer to whichever stylesheet you use most often in your .html files, then add either win-ui-light or win-ui-dark to individual elements that you need to style differently. (Thanks to Scott Salam for this one.)

The other way of avoiding collisions is to specifically unload and reload CSS files by modifying <link> tags in the page header. You can either remove one <link> tag and add a different one, toggle the disabled attribute for a tag between true and false, or change the href attribute of an existing link. These methods are demonstrated for styling an iframe in the CSS styling and branding your app sample, which swaps out and enables/disables both WinJS and app-specific stylesheets. Another demonstration for switching between the WinJS stylesheets is in scenario 1 of the HTML NavBar control sample  (js/1-CreateNavBar.js):

function switchStyle() {
    var linkEl = document.querySelector('link');
    if (linkEl.getAttribute('href') === "//Microsoft.WinJS.2.0 /css/ui-light.css") {
        linkEl.setAttribute('href', "//Microsoft.WinJS.2.0 /css/ui-dark.css");
    } else {
        linkEl.setAttribute('href', "//Microsoft.WinJS.2.0 /css/ui-light.css"); 
    }
}

The downside of this approach is that every switch means reloading and reparsing the CSS files and a corresponding re-rendering of the page. This isn’t much of an issue during page navigation, but given the size of the WinJS files I recommend using it only for your own page-specific stylesheets and using the win-ui-light and win-ui-dark classes to toggle the WinJS styles.


At the recent Facebook/Windows 8 Hackathon hosted at Facebook headquarters, I was helping one group who was bringing a bunch of JavaScript code from a website into their project. They brought all this in with <script> tags at the top of default.html, where default.html contained just a <div data-win-control=”Application.PageControlNavigator” …> as some of the Visual Studio templates provide.

The PageControlNavigator was pointed to the app’s home page in its options, and that page control contained various <canvas> elements that the various JavaScript code attempted to initialize.

Almost everything worked. However, the code that attempted to obtain a canvas element and perform some initialization was failing. More specifically, document.getElementById calls with the canvas IDs were returning null, clearly indicating that those elements did not yet exist in the DOM.

The cause for this was that the code pulled at the top of default.html was all being executed before the PageControlNavigator was able to load the home page contents and attach it to the DOM, thus that page’s elements were not yet available. To correct this, we only needed to move the one bit of code from all those files that started the initialization process into the page control’s ready method, and all was well.

In short, when using page controls, the ready method is where you can be assured that the page’s contents have been loaded and are available for manipulation. The exact timing of how all those elements get into the DOM is different from the direct contents of default.html (and different from what you might know from your browser experience), and so initialization calls need to be in the ready method and not just executed automatically on loading default.html.