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.


4 Comments

  1. Guillaume Leborgne
    Posted June 19, 2014 at 11:47 pm | Permalink

    Hi Kraig,

    Instead of setImmediate, the best thing to do to avoid this kind of problems is to scope your DOM selector. Instead of “document.querySelector’ use ‘page.element.querySelector’. You could have the same kind of troubles when you navigate very fast. Say you navigate to a page and immediately click on back. If you have promises (like ajax request) you may have troubles if you use document.querySelector to build your page. I wrote an article that goes into details about that : http://mcnextpost.com/2014/05/27/best-practices-for-winjs-applications/

  2. kraigb
    Posted June 20, 2014 at 8:48 am | Permalink

    Thanks–I knew there was something I hadn’t thought of! I’ve edited the post to reflect your suggestion (crediting you too).

  3. occorom
    Posted July 22, 2014 at 4:52 am | Permalink

    How about navigating to an intermediate page, why doesn’t this resolve this problem:
    for exemple, I am on page A, I quickly navigate to page B and at it’s :ready method I navigate to page A
    Wouldn’t the elements of page A already be unloaded before the second navigation occurs?

  4. kraigb
    Posted July 25, 2014 at 8:23 pm | Permalink

    The implementation of WinJS is such that the new “page” contents are loaded into the DOM and ready called before the previous page’s elements are unloaded. So you’d have the same problem with different pages that use the same element IDs. Either way, Guillaume’s suggestion is the best approach.

One Trackback

  1. […] Issues when navigating to the current page in WinJS (Kraig Brockschmidt) […]

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>