Tag Archives: knockout

Part 6: Custom Components – Tabs

In Part 4 we have seen how to use History.JS along with Crossroads.js to achieve proper client side routing and maintain browser state for proper back button navigation. However, SPAs often have complex page states for example a page with tabbed data or grid data with a particular filter.

One of the ways we can make a particular Tab of data ‘bookmark-able’ is by including the selected tab information as a part of the route. Thus, the particular tab becomes a part of the URL and can be navigated to directly or pushed to the browser history and retrieved on back button.

As you may have noted, our SPA has a name now – SilkThread and a site of it’s own http://silkthread.pw. Today we’ll create a Tab-Item component that will working with SilkThread’s routing mechanism.

Important: When I started this series KO Components was still in Beta, since then version 3.2.0 has gone live and is now available on Nuget also.This article onwards, I am using the release version from Nuget.

Before diving into custom components

We have been using ‘’magic strings” for representing routes, component names and so on. Multi-use components are used more than once, and complex views might be composed of multiple components. Relying on magic strings can be error prone and ‘difficult to maintain’. Why? Well imagine that you have created a component called drop-down in the drop-down folder. Now when you access it elsewhere you specify a magic string ‘drop-down’. But does the next person looking into your code know what it signifies? Is it the component name or the folder name or file name or just a random key? However if you accessed the string via a literal app.components.dropDown.name it will be amply clear that that string is for. So before our framework gets any more complex, let’s take a stab at cleaning up our magic strings.

The Component Registration

We add a new file called app.js in the /app folder. Mind you this is a first stab and we are likely to refactor later. Next we add the following code to it

app = {
components: {
greeter: {
name: ‘greeter’,
template: ‘App/components/greeter/greeting’
}
},
pages: {
home: {
name: ‘home’,
template: ‘App/pages/home/home’
},
settings: {
name: ‘settings’,
template: ‘App/pages/settings/settings’
}
}
}

As we can see this is a simple JS object that splits pages and components into different types and registers each sub object represents a component with two properties name and template to start off with.

Now we update our startup.js to use the appropriate component name and template names.

define([‘jquery’, ‘knockout’, ‘./router’, ‘app’, ‘bootstrap’, ‘knockout-projections’], function ($, ko, router) {
ko.components.register(
app.components.greeter.name, { require: app.components.greeter.template });

    ko.components.register(app.pages.home.name, { require: app.pages.home.template });
ko.components.register(app.pages.settings.name, { require: app.pages.settings.template });
ko.applyBindings({ route: router.currentRoute });
});

Going forward, we’ll register our components via the app.components object.

Registering with Require

We have to tell Require there is a global app object now, so we update the require.config.js and add the app/app route the list.

var require = {
baseUrl: “/”,
paths: {


“app”: “app/app”
    },

}

Getting a little JS help – Including Underscore.js

The Underscore.js library is a very handy toolset and has some nifty helper functions. I’ll explain the ones we use as I use them. To install it, just use the package-management console

PM> install-package underscore.js

The library is by default installed under the Scripts folder. I’ve moved it to the ‘Scripts\underscore’ subfolder.

image

Like the app.js earlier we’ll register Underscore in the require.config.js as well.

var require = {
baseUrl: “/”,
paths: {

“app”: “app/app”,
“underscore”: “Scripts/underscore/underscore”
},

}

With these two modifications to existing code set, we’ll start with the actual component.

Creating a Tab Component

A tab component has two parts, the tabbed header and the panel showing the tab’s content. If we consider the Tab as a menu item it could be a different page altogether. But at any given point a tab control shows only one panel, which is logically related to the selected tab.

To start off with we’ll create a component – tabbed-navigation. The tabbed-navigation component will be the top-level container that will be responsible for showing the tab headers and the selected tab’s container panel. The container panel will be a placeholder div that will be replaced by the component that needs to be shown for the selected tab. We can configure each tab to show a different component.

The tab-navigation HTML template

The HTML template in the app\components\tab-navigation\tab-navigation.html has the following markup:

<ul id=”tabHeader” class=”nav nav-tabs” role=”tablist”>
<!– ko foreach: tabs –>
<li data-bind=”css : {active : isSelected }”>
<a data-bind=”text : text, attr : { href: url }”></a>
</li>
<!– /ko –>
</ul>

<div id=”tabPanel”
data-bind=”component: {name: selected().component, params: {name: selected().text }}”></div>

It has a simple layout using <ul> and the boot-strap styling classes nav and nav-tabs.

Inside the <ul> we loop through an array of tabs items which is going to be encapsulated in the tab-navigation.js view model.

In the loop we add a <li> that has the style active if the viewModel tab’s isSelected property is set to true. This sets the active style from Bootstrap.

In the <li> we have an anchor with two data-bindings. First is the text that’s going to be shown, next the url that’s bound to the href attribute.

Finally we have the <div> tabPanel. This is bound to a component which is initialized with the name and params properties. The name is bound to the selected tab’s component property (so our viewModel has to be able to return a selected tab). In params the only parameter we are currently sending is the name property, which is again picked up from the text property of the selected ViewModel.

The tab-navigation View Model and DataSource

From the above template we can probably guess of a data structure like the following

var ds = {
tabs: [
{
text: ‘selected 0’,
url: ‘/settings/tab0’,
isSelected : true
},
{
text: ‘selected 1’,
url: ‘/settings/tab1’,
isSelected: false
},
{
text: ‘selected 2’,
url: ‘/settings/tab2’,
isSelected: false
}],
selected : function() {
// Return the tab that has isSelected = true;
}
}

To accommodate this structure we use two JS modules – tabsNavigationConfig.js and tabitemConfig.js

tabitemConfig.js

tabitemConfig = function (text, url, selected, component) {
self = this;
self.text = text;
self.url = url;
self.isSelected = selected;
self.component = component;
return self;
}

tabsNavigationConfig.js

tabsNavigationConfig = function (tabitems, selectedIndex) {
self = this;
self.tabs = [];
for (var i = 0; i < tabitems.length ; i++) {
self.tabs.push(tabitems[i]);
if (i == selectedIndex) {
self.tabs[i].isSelected = true;
}
}
return self;
}

The tabsNavigationConfig object has a constructor function that has the list of tabitemConfig objects and the index of which of the tabs is selected.

If you are still wondering as to why we are having a separate datasource for the component, it is simple. We want our component to be re-usable at multiple places, in the same page if required. Hence if we can decouple the data-source from the ViewModel we can configure any number of tab-navigation components with different data-sources. The ViewModel would only be concerned with the rendering and event handling limited to changing of the tabs.

The tab-navigation ViewModel

With the data source ready, we’ll setup the viewModel of the tab-navigation component. I have tagged each relevant line of code with an Index. You can click on them to see more details.

define([“knockout”,
        “underscore”,                                          // #1
“text!./tabbed-navigation.html”],
function (ko, _, tabbedNavigationTemplate) {                   // #2
var isInitialized = false;                                 // #3
var tabsCache = [];
var selectedIndexCache = 0;
function tabbedNavigationViewModel(params) {               // #4
self = this;
if (!isInitialized) {                                  // #5
isInitialized = true;
tabsCache = params.tabConfig().tabs;
}
self.tabs = ko.observableArray(tabsCache);             // #6
self.selectedIndex = ko.observable(selectedIndexCache);// #7
self.selected = ko.pureComputed(function () {          // #8
return self.tabs()[self.selectedIndex()];
}, this);
if (params.route) {                                    // #9
selectByRoute(params.route());
}
function selectByRoute(route) {                       // #10
var newTab = _.find(self.tabs(), function (tab) {   // #11
return tab.url == route();
});
if (newTab) {
self.tabs()[self.selectedIndex()].isSelected = false; //#12
var index = self.tabs().indexOf(newTab);      // #13
select(index);                                // #14
}
return item;                                      // #15
};
function select(index)                                // #16
{
selectedIndexCache = index;
self.tabs()[index].isSelected = true;
self.selectedIndex(index);
}
  return self;
};
return {
viewModel: tabbedNavigationViewModel,
template: tabbedNavigationTemplate };
});

#1: We start off by declaring that we refer to Underscore.js.
#2: Underscore is assigned to the variable _ Winking smile.

#3: We create three cache variables that are a part of the module rather than the viewModel constructor function. These are the list of tabs, the selected index and a flag indicating whether we have some cached information or not. When navigating to a tab we will get routed via the settings route. This will reinitialize the viewModel, so if we have the values cached we can use them instead of initializing the tabbed control everytime.

#4: The viewModel constructor function tabbedNavigationViewModel which takes in an initialization parameter, and may have a tabsConfig property. The tabsConfig has an array of tabs and the index of the selctedTab.

#5: We check if the module is being initialized the first time around. If so, we set the incoming tabs collection into the cache and mark the module as initialized.

#6: We initialize the tabs with the list in tabs cache.

#7: We set the default selectedIndex to 0.

#8: Pure Computed Observables: We have a new type of Observable introduced in KO 3.2.0 called the pureComputed. We know KO had computed observables that automatically updated themselves when their constituent observables changed. However, they suffered from one drawback. Computed observables were always triggered as soon as the constituent observables changed, even if the computed observable was not bound to a view element. This was a performance hit. To resolve this we now have pureComputed observables that get re-calculated only if they have subscriptions, i.e. someone is waiting to update itself based on changes in the pureComputed.

Here we have declared the selected property as a pureComputed and it returns the selected Tab. The selection changes whenever the selectedIndex changes. So anytime the selectedIndex changes the selected property returns the newly selected tab. As we will see in a bit, this renders a new container.

#9: We check if this viewModel is being created as a result of navigation (all cases except of first time page load). If it is due to navigation the route parameter is populated and we use it to select the particular sub-tab indicated by the route property.

#10: The selectByRoute function, called everytime the route changes.

#11: We use the Underscore library to find tab in the tabs collection. The syntax of _.find expects the array of items to look into and a comparison function to call for each element in the array. It returns the first element it finds.

#12: Once we’ve found the element we use the current selected index to set the isSelected flag to false for the current tab.

#13: We retrieve the index of the new selcted tab

#14: Next we call the select method.

#15: The select method sets the isSelected property of the new tab and updates the selectedIndexCache value for future use.

Initializing the Tab Component

Now that we’ve seen the structure of data we need and how that data is used lets see how we can use them. The Tab component is used inside the Settings Page component. So someone has to initialize it in the Settings page.

Tab Component markup in Settings.html

The markup to use the Tab Component is simple and unremarkable:

<div>
<h1>Settings</h1>
</div>
<tabbed-navigation params=”tabConfig : tabbedNavigation(), route: route()”>
</tabbed-navigation>

There are two items being passed in the params property, tabConfig and route. The tabConfig property has the initialization parameters of how many tabs, their text, their URL and so forth. Refer to the tabNavigation viewModel section above.

The binding parameters imply that the Component View Model that’s backing this HTML has to provide he tabbedNavigation and route properties.

Tab Component viewModel code

Before now, the settings component was HTML only. We introduce the backing JS for this component. The code for it is as follows:

define(
[
“knockout”,
“text!./settings.html”,
“underscore”,
“../../components/models/tabbed-navigation/tabsNavigationConfig”,
“../../components/models/tabbed-navigation/tabitemConfig”
],
function (ko, settingsTemplate, _) {
var isInitialized = false;
var tabsNavigationInstance = null;
function settingsViewModel(params) {
var self = this;
self.tabbedNavigation = new ko.observable();
if (!isInitialized) {
isInitialized = true;
tabsNavigationInstance = init(params);
}
self.tabbedNavigation(tabsNavigationInstance);
self.route = new ko.observable();
if (params.tab) {
self.route(params.request_);
}
return self;
};
function init(params) {
var newTabs = [];
for (var i = 0; i < 5; i++) {
var key = ‘tab’ + i;
newTabs.push(new tabitemConfig(
“Settings ” + i,
“/settings/” + key,
key == params.tab,
‘greeter’));
}
tabsNavigationInstance = new tabsNavigationConfig(newTabs, 0);
return tabsNavigationInstance;
}
return { viewModel: settingsViewModel, template: settingsTemplate };
});

The crux of the component view model is the init method that’s called from the constructor function first time around. It need not be called init though. This function initializes an array and pushes in 5 instances of the tabitemConfig that we defined earlier. Since this is a demo we are simply doing a loop. For a real life scenario we would instantiate each instance with a relevant name and more importantly route to a component that should be visible when the tab is selected. In our case we are setting each route to point to the previously created ‘greeter’ component. So for every tab selection we’ll show a greeter component and if you refer back to the original markup at the top you’ll see we pass the selected tabs’ text property to the greeter as a parameter. So the greeter shows name of the tab selected.

Demo time

If we run the application and navigate to the Settings page we’ll see the following

– First tab ‘Settings 0’ is selected
– Greeter is saying Hello to ‘Settings 0’

image

– Clicking on Settings 1 tab, ‘navigates to that tab.Notice the route changes to settings/tab1. This corresponds to the route we setup when initializing the tabitemConfig object.

image

Now we can click on the Browser back button to navigate back to Settings 0 tab, and go forward again to come back to Settings 1 tab. When you navigate you’ll notice that the timestamp of Greeter component keeps changing this is because the greeter component doesn’t cache the time stamp so everytime the constructor function is called, the time gets updated.

Code for this version is available on the Part6 branch of the repository https://github.com/sumitkm/BuildingSpaUsingKO/tree/Part6

Conclusion

In conclusion we saw how we could create a ‘tab-component’ using KO’s component model of development. Next we’ll see how we can use different components in different tabs and how to call server to get/put data.

Advertisements
Tagged , , , ,

Part 5: SPAs, Accessibility and the Server side

Part 1: Dipping your feet into KnockoutJS Components
Part 2: Knockout Components – Separating Templates from View Model
Part 3: KO Components – Routes and Hashes
Part 4: SPA with KO Components-PushState and History JS

Recap

In the previous 4 parts we have seen how to use Knockout Components to make a sample Single Page Application, with different type of client routing libraries.

Towards the end of the third article I had highlighted how SPAs hinder Accessibility and Search-ability of a site’s content primarily because:

a. Search engines don’t ‘click’ on URLs to load content, they simply follow URLs they find and request the server for content. Whatever the server returns is indexed as content of that URL.

b. Accessibility tools work on similar logic to Search engines and cannot ‘click’ on URLs to load content.

Towards both these ends we took a small first step by moving from a hash based client side router to a pushState based one that falls back gracefully for non-push state browsers. This gave us URLs that looked like /settings instead of #settings. The search engine or accessibility tool now knows it has to navigate to a new URL to get the content.

At the end of the last article I has shown how the server would return a 404 or some similar server-side error because when we click on http://buildingspausingko.azurewebsites.net/settings there is no page at that URL. Remember our entire site is being served from index.html. How to we fix this?

Enter server side routing fallbacks

Before we start let’s get the Part 4 code from Github.

Using _Layout and adding an Index.cshtml for the home page

Before anything, let’s start using the server in the first place.

Our Application was an Empty ASP.NET application to start with. So to get started we’ll first add ASP.NET MVC to it. We can do this by installing the package Microsoft.AspNet.Mvc

PM> install-package Microsoft.AspNet.Mvc

Now let’s add Controller/HomeController and a Views/Home/Index.cshtml files.

We update the Layout such that it contains the top navigation bar and then calls the RenderBody function to render the rest of the page.

<!DOCTYPE html>
<html xmlns=”
http://www.w3.org/1999/xhtml”>
<head>
<title>Dipping your feet into KnockoutJS Components</title>
<link href=”/Content/bootstrap.css” rel=”stylesheet” />
<link href=”/Content/bootstrap-theme.css” rel=”stylesheet” />
</head>
<body>
<div class=”navbar navbar-inverse navbar-fixed-top”>
<div class=”container”>
<div class=”navbar-header”>
<button type=”button” class=”navbar-toggle” data-toggle=”collapse” data-target=”.navbar-collapse”>
<span class=”icon-bar”></span>
<span class=”icon-bar”></span>
<span class=”icon-bar”></span>
</button>
<a class=”navbar-brand” href=”/”>KO Components</a>
<ul class=”nav navbar-nav”>
<li>
<a href=”/”>Home</a>
</li>
<li>
<a href=”settings”>Settings</a>
</li>
</ul>
</div>
</div>
</div>
<div id=”page1″ class=”container” style=”padding-top:50px”>
@RenderBody()
</div>
<footer class=”navbar navbar-fixed-bottom”>
<div class=”container-fluid”>
<p> &copy; 2014 – Still Learning</p>
</div>
</footer>
<script src=”App/boot/require.config.js”></script>
<script data-main=”App/boot/startup” src=”Scripts/require/require.js”></script>
</body>
</html>

We put in a simple header in the index.cshtml file showing that it is coming from the server. However not that the div containing the Index content is also the container for the dynamic component which will be loaded by Require JS.

@{
Layout = “~/Views/Shared/_Layout.cshtml”;
}
<div id=”page1″ class=”container” style=”padding-top:50px;” data-bind=”component: { name: route().page, params: route }”>
<div class=”container” style=”background-color: red”>
<h1>Index from Server</h1>
</div>
</div>

Finally, let’s rename the Index.html to Index2.html so that it’s not picked up as the default by IIS.

If you run the application now you’ll see the ‘Index from Server’ text flashes briefly before being replaced by the greetings.

This simply implies that there was a page that the server rendered and returned, once the page was loaded the client side routing kicked in and replaced the container.

This was the default page.

In the browser change URL by adding settings you’ll get an error as follows:

image

This is because there is an invalid redirection in my web.config. Open the web.config and delete the highlighted section

image

Now if you type in the URL /settings on the browser you’ll get a proper 404

image

You must be wondering how come when I click on Settings link from Home page I get a proper page, but when I type in the URL I get a 404? Well, that’s because typing in a URL sends the request to the server directly. In our case, it sends to ASP.NET MVC. MVC framework uses the current route info and tries to map it to a ‘SettingsController’ with a default ‘Index’ action method. However it doesn’t find the SettingsController and throws a 404.

This is what will happen to Search Engines and Screen readers as well. Even though they will see a URL, trying to browse to it will get them a 404. Search engines will down-rate your site and screen readers will give a poor experience to people using them.

So how do we solve this? Let’s explore two ways today.

Sending the same page back for all URLs from Server

This is a hackish technique to make sure your users can navigate to the correct page from bookmarked URLs. This doesn’t really help in Search engine optimization or screen readers. But atleast no one gets a 404 when a valid URL is accessed directly from the browser.

For this, all we have to do is, manipulate the MVC router to send all requests to HomeController’s Index action method.

Once the Index page is returned the client side router will kick in and do the ‘right thing’.

In the App_Start\RouteConfig.cs update the RegisterRoutes method as follows:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”);
routes.MapRoute(
name: “Default”,
url: “{*pathinfo}”,
defaults: new { Controller=”Home”, action = “Index”, id = UrlParameter.Optional }
);
}

As you can see above, we have changed the url parameter to be a wildcard {*pathinfo}, and set the default for this wildcard path to Home Controllers’ Index action method.

If you run the application now, and navigate to the Settings page by typing in the URL you’ll see that there is no more 404 error. BUT, the client side routing seems to be taking us back to the Index page. Why?

image

Removing hard coded default path in router.js

Well, in our ‘excitement’ to get History.js going with crossroads.js in the last article, we introduced a small hardcoding that’s causing the about “BUG”.

In Router.js’ activateCrossRoads function the last line instructs crossroads to route to the root location (‘/’).

crossroads.parse(‘/’);

This is why even though the browser is pointing to settings page, the page is showing the greetings from the Index page.

Let’s fix this.

The inline function call that’s being bound to the stateChanged event actually needs to be called on first Initialization as well. So let’s refactor it out to a separate function called routeCrossroads.

function routeCrossRoads()
{
var State = History.getState();
if (State.data.urlPath) {
return crossroads.parse(State.data.urlPath);
}
else {
if (State.hash.length > 1) {
var fullHash = State.hash;
var quesPos = fullHash.indexOf(‘?’);
if (quesPos > 0) {
var hashPath = fullHash.slice(0, quesPos);
return crossroads.parse(hashPath);
}
else {
return crossroads.parse(fullHash);
}
}
else {
return crossroads.parse(‘/’);
}
}
}

I have made some changes to the code in the else section. Actually the condition to check for ‘?’ is there to handle IE9 properly. In case of IE9, History appends a state Id to the URL after ‘?’. Crossroads doesn’t need the state ID hence I strip it out. But the initial condition is incomplete because if there are no ? in the URL we need the entire path (fullHash). I have fixed this in the above code.

Once the above function is in place, the activateCrossroads function becomes as follows:

function activateCrossroads() {
History.Adapter.bind(window, “statechange”,
routeCrossRoads);
crossroads.normalizeFn = crossroads.NORM_AS_OBJECT;
//crossroads.parse(‘/’);
routeCrossRoads();
}

As we can see we have commented out the default routing to the root ‘/’.

With everything in place if we run the app now, and try to type if /settings at the end of the URL we’ll get the correct settings page.

image

But again, before the Settings page is loaded you will briefly see the red banner saying ‘Index from Server’ flash briefly before navigating to the ‘’Settings’ page. This is because for all URLs we are returning the same Index page from the Server. How do we fix this, if the answer wasn’t obvious, read on Smile

Technique 2: Sending a Server Side page for every URL

Now that our SPA is capable of responding to all URLs specified in our web app, let’s dig deeper to see how we can respond to all pages equally.

This section is experimental on my part, if there are better ways to do this, please sound off in the comments section.

Updating the MVC routing

First thing to do is to update the Routetable to add independent routes for Home/Index and (say) Settings/Index pages.

We’ll keep the ‘catch all’ route that we defined earlier so that if there are any routes that are not defined on the server but defined on the client, we send them off to the home page to load the old fashioned way.

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”);
    routes.MapRoute(
name: “Default”,
url: “{Controller}/{action}/{id}”,
defaults: new { action = “Index”, id = UrlParameter.Optional }
);

routes.MapRoute(
name: “
CatchAll“,
url: “{*pathinfo}”,
defaults: new { Controller=”Home”, action = “Index”, id = UrlParameter.Optional }
);
}

With the above routing in place, lets add a new controller called SettingsController and a new cshtml at Views/Settings/Index.cshtml

The Settings Index file again is a simple one saying it’s coming from server! It uses the _Layout.cshtml file for the header.

@{
Layout = “~/Views/Shared/_Layout.cshtml”;
}
<div id=”page1″ class=”container” style=”padding-top:50px” data-bind=”component: { name: route().page, params: route }”>
<div class=”container”>
<h1>Settings from Server</h1>
</div>
</div>

Now when we run the application and type in /settings we’ll get the server page first and then the client page will get loaded, so you’ll see a brief flash saying ‘Settings from Server’.

In real world the duplication of cshtml and html templates may be a significant effort, I am looking at making things easier in that front and will blog about it if I find anything better. As of now, if you want the best user experience for all types of users (as well as the search engine), this is an extra bit of work that you have to do. It’s not as bad as it sounds either, all you have to do is use the same service that you call during the HTTP GET operation from client, and bind that data to the CSHTML. You can skip elaborate styling and limit it to having correct markup laid out in an orderly fashion.

Code

As usual the code for this article is up on Github. I have branched this code off as Part5: https://github.com/sumitkm/BuildingSpaUsingKO/tree/Part5

Conclusion

With that we conclude this part of the series where we saw how we can handle bookmarked URLs on the server and client. This goes a long way in helping users who use accessibility tools use your site effectively.

More fine tuning will be required on the client side router to handle all scenarios but that’s for another day.

Next we’ll see how to handle URL parameters and more involved routing like Tabs.

Tagged , , , , ,
%d bloggers like this: