I've often said that as a developer, I get this suspicious feeing that I'm mostly solving the same problem over and over again. We innovate, create new and wonderful technology, and then piece together a new solution for an old problem that has just morphed into a different kind of problem but is really the same problem underneath. At this point, some developer alarm should go off in your head screaming "Burke! If you fix the root of the problem, then it WILL go away!" And you are right - it will. The reason why we can't fix the root, is that the root is what keeps changing.

Shifting Foundations

Join me on a trip down memory lane for just a moment. Remember the old days of web development? You know - FrontPage, Geocities, the <blink> tag, Limp Bizkit. Times were much simpler then. Websites were composed of static pages which required duplication of content. In other words, if you had a menu bar running along the top of your page, you would put the menu bar on every page. This allowed you to set the "selected" menu item by simply adjusting its class. This way the right menu item is highlighted based on the URL. Assume we have the following markup for a simple application that uses a Kendo UI Menu. The active menu item is indicated by the k-state-highlight class.

index.html

<ul id="menu">
  <li class="k-state-highlight"><a href="index.html">Home</a></li>
  <li><a href="about.html">About</a></li>
  <li><a href="pictures.html">Pictures</a></li>
  <li><a href="guestbook.html">Sign The Guestbook</a></li>
</ul>

<script>

  (function() {

    $('#menu').kendoMenu();

  }());

</script>

Notice that I've set the k-state-highlight class on the "Home" menu item. Doing this statically is tedious, but easy.

View Example

While that works and is entirely functional for your Geocities masterpiece, that code is not maintainable. I had a hard time maintaining it just while I was putting that sample together. My self esteem also completely bottomed out. For these reasons, as our skills progressed, we turned to server-side frameworks to address this "common components" problem.

PHP And Other TLA's

As we learned more about development and began to realize that FrontPage wasn't really "development" as much as it was "blunt force trauma", we went about learning server-side languages which allowed us to be better structure our code with less repetition. We could extract this menu out into its own PHP file which would have the smarts to update the class using PHP.

index.php

<?php

  $currentPath = basename(__FILE__);

  include("nav.php");

?>

<h1>Hi! I'm The Home Page</h1>

<script>

  (function() {

    $('#menu').kendoMenu();

  }());

</script>
<?php

  function echoClass($path) {
    if ($path == $GLOBALS["currentPath"]) {
      echo "class='k-state-highlight'";
    }
  }

?>

<ul id="menu">
  <li <?=echoClass('index.php')?>><a href="index.php">Home</a></li>
  <li <?=echoClass('about.php')?><a href="about.php">About</a></li>
  <li <?=echoClass('pictures.php')?><a href="pictures.php">Pictures</a></li>
  <li <?=echoClass('guestbook.php')?><a href="guestbook.php">Sign The Guestbook</a></li>
</ul>

Now you could just drop that nav.php include on any page and provided you included that distasteful $currentPath variable, the right link in the nav would get the right class. No jQuery required. Of course, I would argue that "no jQuery" is not a reason to be excited. We could trim all of this down and have a jQuery version that looks a lot cleaner.

index.php

<?php

  $currentPath = basename(__FILE__);

  include("nav.php");

?>

<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://cdn.kendostatic.com/2013.3.1119/js/kendo.all.min.js"></script>

<script>

  (function() {

    $('#menu').find('a[href="<?php echo $currentPath ?>"]').parent().addClass('k-state-highlight');

    $('#menu').kendoMenu();

  }());

</script>

This allows the nav.php file to go back to being much simpler.

<ul id="menu">
  <li><a href="index.php">Home</a></li>
  <li><a href="about.php">About</a></li>
  <li><a href="pictures.php">Pictures</a></li>
  <li><a href="guestbook.php">Sign The Guestbook</a></li>
</ul>

At this point we're just shifting the responsibility between the main page and the nav component. Now this is just straight PHP and anything goes. As we felt more comfortable with server-side languages, we noticed that some frameworks were taking this concept to a whole other level.

Layouts And Master Pages

I first encountered MasterPages while working with ASP.NET Webforms. Now mind you, I'm not a good Webforms developer. I'm not even a mediocre one, but I liked the concept of MasterPages: Have a main file which loads in content files. This is sort of the inverse of what we are doing above with PHP. I was pleased to see the concept get even better in ASP.NET MVC with Layouts.

We could refactor the PHP code to an ASP.NET MVC Layout. We can then refactor the menu into an HTML Helper which handles state for us. If you are using the Kendo UI ASP.NET MVC Wrappers, this is done for you automatically.

layout.cshtml

<!DOCTYPE html>
<html>
  <head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <link href="@Url.Content("~/Content/kendo/2013.3.1324/kendo.common-bootstrap.min.css")" rel="stylesheet" type="text/css" />
      <link href="@Url.Content("~/Content/kendo/2013.3.1324/kendo.bootstrap.min.css")" rel="stylesheet" type="text/css" />
      <script src="@Url.Content("~/Scripts/kendo/2013.3.1324/jquery.min.js")"></script>
      <script src="@Url.Content("~/Scripts/kendo/2013.3.1324/kendo.all.min.js")"></script>
      <script src="@Url.Content("~/Scripts/kendo/2013.3.1324/kendo.aspnetmvc.min.js")"></script>
      <script src="@Url.Content("~/Scripts/kendo.modernizr.custom.js")"></script>
    </head>
  <body>

        <!-- The Kendo UI Menu wrapper takes care of highlighting the correct menu item
        @(Html.Kendo().Menu().Name("menu").Items(items => {
            items.Add().Text("Home").Url("/");
            items.Add().Text("About").Url("/Home/About");
            items.Add().Text("Pictures").Url("/Home/Pictures");
            items.Add().Text("Sign The Guestbook").Url("/Home/Guestbook");
        }))

        @RenderBody()

  </body>
</html>

That behavior can be turned off by setting HighlightPath to false.

This approach makes far more sense to me, and it's dryer from a developer standpoint in that we aren't having to include the nav in the page over and over again. Our frameworks and tools are getting pretty smart at this point. Why should we have to worry about all of the messy little details like "which menu item is highlighted". We have bigger problems to solve; like linking together 12 SQL Servers and trying to somehow turn years of technical debt into a consumable dataset.

At this point we figure that we have laid this pesky menu issue to rest, and our tools are taking care of the messy details. And just as we shift our focus onto more important things, the foundation suddenly shifts again and we turn our attention to welcoming in the next generation of rich web clients.

Single Page Apps And The Same Old Problem

Now that all logic has moved (mostly) onto the server, it's time to move it back to the client! This is why we love our jobs so much right? Just when you think you've got it all down, everything changes.

When it comes to SPAs, we're still dealing with URLs. We're just processing our UI mostly on the client now instead of making trips back to the server. On the surface, the problem of maintaining application state appears to get easier. In a very simple SPA scenario, it's tempting to attach an event handler to the menu so that it updates the item to the correct CSS class when the menu is clicked. That's where the event is occurring after all, so that seems to be the root of the issue.

View Demo

In this case, we wire a simple function up to the click event of any li in the menu. It's done in the layout init method so we don't attach the event listener before the element is actually added to the page.

var layout = new kendo.Layout('#layout-template', {
  init: function () {

    // SO TEMPTING!
    $('#menu').on('click', 'li', function (e) {

      // blanket removal of the class from all li elements
      $('#menu > li').removeClass('k-state-highlight');

      // add it back on the clicked element
      $(e.currentTarget).addClass('k-state-highlight');
    });

  }
});

What's tricky here is that the menu click is not actually the root issue. The root issue is the URL. If a user comes directly to the Home page and clicks on the menu bar alone to navigate, this works fine. If they happen to come directly to the details page (or any other page save the index), the "Home" tab will be active and the state is broken.

The correct way to handle this is to wire up to the Kendo UI Router change event. This ensures that the navbar will say in sync with the page no matter how the user navigates through the application.

var router = new kendo.Router({
  init: function () {
    layout.render('#app');
  },
  change: function (e) {

    // blanket removal of active class
    menu.find('li').removeClass('k-state-highlight');

    // find the element by its url and make that one active
    menu.find('li > a[href="#'+ e.url + '"]').parent().addClass('k-state-highlight');
  }
});

View Demo

That's better! Now we have a consistent navbar that is tied to the true state of the application.

However

Single Page Applications rarely contain all of the code in the same spot. Usually we use something like RequireJS or some sort of module system to extract code into reusable pieces. This adds a level of complexity to the problem that we just solved.

Consider the strategy of extracting the layout and the different views into their own JavaScript files that you would then load in with some module loader or script concatenation tool. If you use RequireJS, you can additionally extract all of the HTML templates into their own files that are loaded by their corresponding JavaScript file. To see this in action, you can either run the Kendo UI Yeoman generator and have it build you a SPA, or you can install the new Kendo UI SPA Project Template for ASP.NET MVC. Each has the same project structure.

The layout file and views are all their own modules. They all return the Kendo UI View object except the layout which returns a Kendo UI Layout object (which subclasses from a standard view). All that to say that the main application JavaScript file knows NOTHING about the layout file and vice versa. We could still mutate the navbar from the application router, but that defeats the purpose of decoupling; the Layout should be responsible for updating its components, not the Router.

Here is where the wrench gets thrown into the system. At this point, you could expose a method on the Layout module that the Router could call, passing in the URL. Something like this...

// DUMMY ENCAPSULATION FOR DEMONSTRATION
var layout = (function() {
  var layout = new kendo.Layout('#layout-template', {
    init: function () {

      // cache a reference to the menu item
      menu = $('#menu');
    }
  });

  var updateMenu = function (url) {

    // blanket removal of active class
    menu.find('li').removeClass('k-state-highlight');

    // find the element by its url and make that one active
    menu.find('li > a[href="#'+ e.url + '"]').parent().addClass('k-state-highlight');
  }

  return {
    layout: layout,
    updateMenu: updateMenu
  };
}());

var router = new kendo.Router({
  init: function () {

    // WAT
    layout.layout.render('#app');
  },
  change: function (e) {
    layout.updateMenu(e.url);
  }
});

View Demo

This is what I call "API Mutation". The Layout is no longer a Layout object so you have to get into the business of trying to appropriately name things. In the above case, I failed miserably resorting to layout.layout to get the actual Layout object. Naming is a game you do not want to play because you will lose. Worse than that is now my Layout API looks different than any other file that's returning just a View object. So if we don't want to play the Name Game, what other options do we have?

Pass The Message On The Lefthand Side

One of my all-time favorite patterns in JavaScript is Publish/Subscribe, often abbreviated by the hipsters to just PubSub. It's my all-time favorite because it's just so darn simple and the simplest solution is usually the right one. I didn't say that, Ockham did.

PubSub just means that instead of calling the layout updateMenu method directly from the router, we will instead just publish a message saying that the router change event has occurred, and pass the event data along with the message. Any modules then listening for that message will receive the event arguments and execute a function. This means that we do NOT have to expose the updateMenu function to the world and thusly can keep our public API on the layout module nice and simple.

I usually use Peter Higgin's PubSub library because it's simple and I might have mentioned that I like simple. Using that plugin, we can re-write our layout module to simply 'subscribe' to an event, and then publish that event from the router.

// DUMMY ENCAPSULATION FOR DEMONSTRATION
var layout = (function() {
  var layout = new kendo.Layout('#layout-template', {
    init: function () {
      // cache a reference to the menu item
      menu = $('#menu');
    }
  });

  var updateMenu = function (url) {

    // blanket removal of active class
    menu.find('li').removeClass('k-state-highlight');

    // find the element by its url and make that one active
    menu.find('li > a[href="#'+ url + '"]').parent().addClass('k-state-highlight');
  }

  // subscribe the /router/change event
  $.subscribe('/router/change', function (url) {
    updateMenu(url);
    });

  return layout;

}());

var router = new kendo.Router({
  init: function () {
    layout.render('#app');
  },
  change: function (e) {

    // publish the /router/change event and pass the url
    $.publish('/router/change', [e.url]);
  }
});

View Demo

As a bonus, if any other component needs to know about the router change event, we can just subscribe to the event. Otherwise we would have to expose their method publicly and call it from the router change event.

SSDD

But that's OK. The worst thing that I can possibly imagine is running out of problems to solve. I also like the newer client-side design solutions. Since JavaScript is a very simple and dynamic language, these problems get easier and easier to fix. You can download a copy of Kendo UI if you haven't already to start building SPA's. If you're working mostly on the server, you should checkout Telerik's UI for ASP.NET MVC, PHP or JSP.

In the name of "New and constantly changing technology", I wrote this entire blog post in the new Atom editor from GitHub. I have one invite left an I'll send it to whomever tweets this post first with the hashtag #ssdd.


Burke Holland is the Director of Developer Relations at Telerik
About the Author

Burke Holland

Burke Holland is a web developer living in Nashville, TN and the Director of Developer Relations at Telerik. He enjoys working with and meeting developers who are building mobile apps with jQuery / HTML5 and loves to hack on social API's. Burke works for Telerik as a Developer Advocate focusing on Kendo UI.

Related Posts

Comments

Comments are disabled in preview mode.