... that is the question.
(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.

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!!!!

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?

... 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!