... that is the question.

Dr. Hamlet?
(Yes, that is Dr. Who (David Tennant) playing Hamlet! Dr. Hamlet? #WINNING)

Whether 'tis nobler in the browser to suffer the slings and arrows of re-rendering, or to take arms against a sea of troubles, and by opposing end them: to partial-render to rewrite the DOM no more than need ...

Routing and rendering

Routers are powerful tools in Single Page Applications, and Kendo UI's router provides an API to wrap up this power. But the API hides a lot of potential complexity and performance issues if you're not careful. Calling the navigate method without regard for the current context of the application and whether or not that context is changing can result in unwanted re-rendering of the entire application. This can reduce the responsiveness of the app, waste time re-drawing content that isn't changing, and cause a lot of flickering on the screen - and none of this is desireable from a user's perspective.

Sidebar: Kendo UI isn't the first or last framework to build a router that has this potential drawback. Every router in every Single Page App framework has this problem. I've written about this same subject in Backbone.js, previously, and I know others have talked about it with other frameworks, as well. So, while I'm talking about Kendo UI's router and API here, the principle of what I'm saying applies to most JavaScript routers.

A good rule of thumb is to only force navigation when the context of the application is changing. Otherwise, pass true to the second parameter of the the router.navigate method.

Wait, what? Why?

Consider the Kitteh Gallereh, once again (you didn't think I would post about the SPA without teh kittehs, did you?!)

This simple application is read-only right now, but it would be easy to add additional features and functionality. You could create the ability to add new images to the gallery, have an administrative section of the site where you can manage users and permissions, and have categorized views of images, for example.

A feature like an add form or categorization would be fairly easy to implement. You would not need to switch layouts to do this. You could just replace the viewer with the add/edit form, and provide the ability to filter the list view by a category. Simple filters and forms like this would not need to take you out of the "image gallery" context. You would stay in the same page layout, and in the same mode of working with the images.

But if you wanted to create an administrative section of the image gallery web application, swapping out the image view might not be enough. You don't necessarily want to have the list of images on the screen while you are managing users and permissions. You might want a very different layout for the screen, so that you can have more powerful features that are easier to understand and use. You have a very different context for the application at this point. Instead of being in the "image gallery" context or application feature set, you would be in the "administration" context or feature set.

It's this distinction - are we still in the same context within the application, or have we moved? - that determines whether or not you should allow router.navigate to run your route handler or not.

SHOW ME TEH CODEZ!

Ok, enough abstract babbling. What does this really mean in terms of the code we write? Let's start with some route handlers. One for the image gallery, one for the add form in the gallery, and one for an administrative page.

router.route("/images", function(){
  var layout = new kendo.Layout("image-gallery-layout-template");
  $("body").show(layout.render());

  // show the kitteh view 
  layout.showIn("#main", new kendo.View("kitty-view-template"));

  layout.showIn("#image-list", new kendo.View("kitty-list-template"));
  // ... etc
});

router.route("/images/new", function(){
  var layout = new kendo.Layout("image-gallery-layout-template");
  $("body").show(layout.render());

  // show the add kitteh form
  layout.showIn("#main", new kendo.View("add-kitteh-form-template"));

  layout.showIn("#image-list", new kendo.View("kitty-list-template"));
  // ... etc
});

router.route("/admin", function(){
  var layout = new kendo.Layout("admin-layout-template");
  $("body").show(layout.render());

  layout.showIn("#menu", new kendo.View("admin-menu-template"));
  layout.showIn("#search", new kendo.View("admin-search-template"));
  layout.showIn("#main", new kendo.View("user-admin-template"));
  // ... etc
});

Any time you hit the #/images or #/admin routes in your browser's url, you'll end up on the correct "page" of the app - either showing the image gallery or the administrative screens.

Both the handlers render a layout and populate the document body with the layout. The both render a set of views that are needed for the layout, and populate the layout with those views.

If a browser clicks a link or uses a bookmark to get to these routes everything is good. The page renders what is needed and the router fills in the details. But what happens when we're already using the application, and we want to switch over to the "add image" or "admin" features that I've mentioned? This is where it gets a little more tricky, and the rule of thumb that I mentioned previously comes in to play.

Sidebar: For simplicity in this example, I've hard coded the route handlers with all the things to show for the page. Please note that you shouldn't do this from inside of your route handlers, though. Build separate objects and methods that the router calls.

don't hard code the things!

When to run the route handler

Let's say we're looking at the Image Gallery at this point. The #/images route has fired and the image list with a beautiful kitty is showing on the screen. And now you want to get to the administrative section of the app. Conveniently enough, there is an HTML link in the app:

<a href="#/admin">Admin</a>

You click on this link... and the router fires the route handler for /admin like it is supposed to.

Job done. Go home. Party.

Since the application is changing contexts - since we are moving from the Image Gallery in to the Admin, completely replacing everything on the screen with something new - you can let the route handler do it's thing. Now you could extract all of the code from the /admin route handler in to an object and/or function, have the link click handled in JavaScript code, and call this same function. That would work, too. But I don't see any problems with letting the router do it's thing at this point.

When NOT to run the route handler

Now let's go back to the Image Gallery. Once again, the #/images route has fired and the image list has been displayed with a beautiful kitty. It's time to move on again, only this time, you want to show the "add" form for the kitty gallery. And oh, look - yet another link exists in the application:

<a href="#/images/new">ADDZ UH KITTEH!!!!</a>

You click the link, the route fires and ...

MY EYES!!!!

I CAN'T UNSEE THAT!

Why did the screen flicker, go blank, and redraw EVERYTHING?! It just redrew the same list on the screen and it only swapped out the kitteh view for the add form view. I thought this was a single page application where that kind of thing didn't happen!

Well, this is happening because it's what we told the router to do. When letting the router run the URL we have defined (#/images/new) it had navigate to this view. This means that the router has to re-draw the ENTIRE screen, from the overall layout of the application down to the ListView and the 'add' form.

Clearly this is not what you want. Since the application context is not changing - we're still looking at the kitteh pics after all - we don't need to wipe out the entire screen and re-draw it from the ground up. All we need to do is swap out the kitteh view with the add form. So in this case, it is worth adding a click handler to the link, and having that only show what is needed.

FIX IT! FIX IT! FIX IT! FIX IT!

Admitting you have a problem is the first step to recovery. The second is adding a data-bind attribute with a click handler, for the link.

<a href="#/images/new" data-bind="click: addKitteh">ADDZ UH KITTEH!!!!</a>

Now you can handle the click in the observable (view-model) for the layout or view that is handling this

kittehGallereh = kendo.observable({

  // ...

  addKitteh: function(e){
    // don't let the browser handle the click
    e.preventDefault();

    layout.showIn("#main", new kendo.View("add-kitteh-form-template"));

    // UPDATE THE ROUTE without firing the router
    router.navigate("/images/new", false);
  }
});

Now when you click the link, the layout will only update the #main element with the add form, instead of redrawing the entire screen.

The call to router.navigate will still update the browser's hash fragment, too. But by passing false as the second parameter, the router will not fire the route handler. It will only update the route in the URL. This allows you to still have your routable page, and still enable the browser's forward and back buttons correctly. But all without having to re-draw the entire screen.

Of course there's another problem here... the code to show the add form has been duplicated between the router and the observable object. And we all know that code duplication is the devil, right?

momma said code duplication is da devil

... but that's another problem for another time. For the moment, at least you've solved the problem of not redrawing the entire screen just because you wanted to replace one very small part of it.

It's never that easy

The example that I've shown here makes it pretty easy, right? You just handle a click event when the app is maintaining it's context, and allow the router to redraw when it's changing. Unfortunately it's never quite that simple... at least, not very often.

In the real world, with complex application layouts and sub-applications that may involve other parts of the system, it gets to be very hairy very quickly. But with the knowledge of the black-and-white solution like I've shown here, you can begin to explore the gray area that is reality. Try out different ideas and experiement a little. See what works and what doesn't work. But most importantly, look at why it does or does not work, and under what circumstances it would or would not.

Choose your own adventure

If you'd like to learn how to build the Kitteh Gallereh from the ground up, there's a series of blog posts that I've written, and a series of screencasts as well. Check out the getting started blog post for links to all of the resources that you'll need.

And if you're interested in seeing what Kendo UI can do for you and your projects Head over to the download page, and start working with Kendo UI today!


About the Author

Derick Bailey

About the Author
Derick Bailey is a Developer Advocate for Kendo UI, a developer, speaker, trainer, screen-caster and much more. He's been slinging code since the late 80’s and doing it professionally since the mid 90's. These days, Derick spends his time primarily writing javascript with back-end languages of all types, including Ruby, NodeJS, .NET and more. Derick blogs atDerickBailey.LosTechies.com, produces screencasts atWatchMeCode.net, tweets as @derickbailey and provides support and assistance for JavaScript, BackboneJS,MarionetteJS and much more around the web.

Related Posts

Comments

Comments are disabled in preview mode.