docs: function() {
          var queue = [],
              isProcessing = ko.observable(false),
              currentActivation,
              currentInstruction,
              activeItem = activator.create();
  
          var router = {
              /**
               * The route handlers that are registered. Each handler consists of a `routePattern` and a `callback`.
               * @property {object[]} handlers
               */
              handlers: [],
              /**
               * The route configs that are registered.
               * @property {object[]} routes
               */
              routes: [],
              /**
               * The route configurations that have been designated as displayable in a nav ui (nav:true).
               * @property {KnockoutObservableArray} navigationModel
               */
              navigationModel: ko.observableArray([]),
              /**
               * The active item/screen based on the current navigation state.
               * @property {Activator} activeItem
               */
              activeItem: activeItem,
              /**
               * Indicates that the router (or a child router) is currently in the process of navigating.
               * @property {KnockoutComputed} isNavigating
               */
              isNavigating: ko.computed(function() {
                  var current = activeItem();
                  var processing = isProcessing();
                  var currentRouterIsProcesing = current
                      && current.router
                      && current.router != router
                      && current.router.isNavigating() ? true : false;
                  return  processing || currentRouterIsProcesing;
              }),
              /**
               * An observable surfacing the active routing instruction that is currently being processed or has recently finished processing.
               * The instruction object has `config`, `fragment`, `queryString`, `params` and `queryParams` properties.
               * @property {KnockoutObservable} activeInstruction
               */
              activeInstruction:ko.observable(null),
              __router__:true
          };
  
          events.includeIn(router);
  
          activeItem.settings.areSameItem = function (currentItem, newItem, currentActivationData, newActivationData) {
              if (currentItem == newItem) {
                  return compareArrays(currentActivationData, newActivationData);
              }
  
              return false;
          };
  
          function hasChildRouter(instance) {
              return instance.router && instance.router.parent == router;
          }
  
          function setCurrentInstructionRouteIsActive(flag) {
              if (currentInstruction && currentInstruction.config.isActive) {
                  currentInstruction.config.isActive(flag)
              }
          }
  
          function completeNavigation(instance, instruction) {
              system.log('Navigation Complete', instance, instruction);
  
              var fromModuleId = system.getModuleId(currentActivation);
              if (fromModuleId) {
                  router.trigger('router:navigation:from:' + fromModuleId);
              }
  
              currentActivation = instance;
  
              setCurrentInstructionRouteIsActive(false);
              currentInstruction = instruction;
              setCurrentInstructionRouteIsActive(true);
  
              var toModuleId = system.getModuleId(currentActivation);
              if (toModuleId) {
                  router.trigger('router:navigation:to:' + toModuleId);
              }
  
              if (!hasChildRouter(instance)) {
                  router.updateDocumentTitle(instance, instruction);
              }
  
              rootRouter.explicitNavigation = false;
              rootRouter.navigatingBack = false;
              router.trigger('router:navigation:complete', instance, instruction, router);
          }
  
          function cancelNavigation(instance, instruction) {
              system.log('Navigation Cancelled');
  
              router.activeInstruction(currentInstruction);
  
              if (currentInstruction) {
                  router.navigate(currentInstruction.fragment, false);
              }
  
              isProcessing(false);
              rootRouter.explicitNavigation = false;
              rootRouter.navigatingBack = false;
              router.trigger('router:navigation:cancelled', instance, instruction, router);
          }
  
          function redirect(url) {
              system.log('Navigation Redirecting');
  
              isProcessing(false);
              rootRouter.explicitNavigation = false;
              rootRouter.navigatingBack = false;
              router.navigate(url, { trigger: true, replace: true });
          }
  
          function activateRoute(activator, instance, instruction) {
              rootRouter.navigatingBack = !rootRouter.explicitNavigation && currentActivation != instruction.fragment;
              router.trigger('router:route:activating', instance, instruction, router);
  
              activator.activateItem(instance, instruction.params).then(function(succeeded) {
                  if (succeeded) {
                      var previousActivation = currentActivation;
                      completeNavigation(instance, instruction);
  
                      if (hasChildRouter(instance)) {
                          var fullFragment = instruction.fragment;
                          if (instruction.queryString) {
                              fullFragment += "?" + instruction.queryString;
                          }
  
                          instance.router.loadUrl(fullFragment);
                      }
  
                      if (previousActivation == instance) {
                          router.attached();
                          router.compositionComplete();
                      }
                  } else if(activator.settings.lifecycleData && activator.settings.lifecycleData.redirect){
                      redirect(activator.settings.lifecycleData.redirect);
                  }else{
                      cancelNavigation(instance, instruction);
                  }
  
                  if (startDeferred) {
                      startDeferred.resolve();
                      startDeferred = null;
                  }
              }).fail(function(err){
                  system.error(err);
              });;
          }
  
          /**
           * Inspects routes and modules before activation. Can be used to protect access by cancelling navigation or redirecting.
           * @method guardRoute
           * @param {object} instance The module instance that is about to be activated by the router.
           * @param {object} instruction The route instruction. The instruction object has config, fragment, queryString, params and queryParams properties.
           * @return {Promise|Boolean|String} If a boolean, determines whether or not the route should activate or be cancelled. If a string, causes a redirect to the specified route. Can also be a promise for either of these value types.
           */
          function handleGuardedRoute(activator, instance, instruction) {
              var resultOrPromise = router.guardRoute(instance, instruction);
              if (resultOrPromise) {
                  if (resultOrPromise.then) {
                      resultOrPromise.then(function(result) {
                          if (result) {
                              if (system.isString(result)) {
                                  redirect(result);
                              } else {
                                  activateRoute(activator, instance, instruction);
                              }
                          } else {
                              cancelNavigation(instance, instruction);
                          }
                      });
                  } else {
                      if (system.isString(resultOrPromise)) {
                          redirect(resultOrPromise);
                      } else {
                          activateRoute(activator, instance, instruction);
                      }
                  }
              } else {
                  cancelNavigation(instance, instruction);
              }
          }
  
          function ensureActivation(activator, instance, instruction) {
              if (router.guardRoute) {
                  handleGuardedRoute(activator, instance, instruction);
              } else {
                  activateRoute(activator, instance, instruction);
              }
          }
  
          function canReuseCurrentActivation(instruction) {
              return currentInstruction
                  && currentInstruction.config.moduleId == instruction.config.moduleId
                  && currentActivation
                  && ((currentActivation.canReuseForRoute && currentActivation.canReuseForRoute.apply(currentActivation, instruction.params))
                  || (!currentActivation.canReuseForRoute && currentActivation.router && currentActivation.router.loadUrl));
          }
  
          function dequeueInstruction() {
              if (isProcessing()) {
                  return;
              }
  
              var instruction = queue.shift();
              queue = [];
  
              if (!instruction) {
                  return;
              }
  
              isProcessing(true);
              router.activeInstruction(instruction);
  
              if (canReuseCurrentActivation(instruction)) {
                  ensureActivation(activator.create(), currentActivation, instruction);
              } else {
                  system.acquire(instruction.config.moduleId).then(function(module) {
                      var instance = system.resolveObject(module);
                      ensureActivation(activeItem, instance, instruction);
                  }).fail(function(err){
                          system.error('Failed to load routed module (' + instruction.config.moduleId + '). Details: ' + err.message);
                      });
              }
          }
  
          function queueInstruction(instruction) {
              queue.unshift(instruction);
              dequeueInstruction();
          }
  
          // Given a route, and a URL fragment that it matches, return the array of
          // extracted decoded parameters. Empty or unmatched parameters will be
          // treated as `null` to normalize cross-browser behavior.
          function createParams(routePattern, fragment, queryString) {
              var params = routePattern.exec(fragment).slice(1);
  
              for (var i = 0; i < params.length; i++) {
                  var current = params[i];
                  params[i] = current ? decodeURIComponent(current) : null;
              }
  
              var queryParams = router.parseQueryString(queryString);
              if (queryParams) {
                  params.push(queryParams);
              }
  
              return {
                  params:params,
                  queryParams:queryParams
              };
          }
  
          function configureRoute(config){
              router.trigger('router:route:before-config', config, router);
  
              if (!system.isRegExp(config)) {
                  config.title = config.title || router.convertRouteToTitle(config.route);
                  config.moduleId = config.moduleId || router.convertRouteToModuleId(config.route);
                  config.hash = config.hash || router.convertRouteToHash(config.route);
                  config.routePattern = routeStringToRegExp(config.route);
              }else{
                  config.routePattern = config.route;
              }
  
              config.isActive = config.isActive || ko.observable(false);
              router.trigger('router:route:after-config', config, router);
              router.routes.push(config);
  
              router.route(config.routePattern, function(fragment, queryString) {
                  var paramInfo = createParams(config.routePattern, fragment, queryString);
                  queueInstruction({
                      fragment: fragment,
                      queryString:queryString,
                      config: config,
                      params: paramInfo.params,
                      queryParams:paramInfo.queryParams
                  });
              });
          };
  
          function mapRoute(config) {
              if(system.isArray(config.route)){
                  var isActive = config.isActive || ko.observable(false);
  
                  for(var i = 0, length = config.route.length; i < length; i++){
                      var current = system.extend({}, config);
  
                      current.route = config.route[i];
                      current.isActive = isActive;
  
                      if(i > 0){
                          delete current.nav;
                      }
  
                      configureRoute(current);
                  }
              }else{
                  configureRoute(config);
              }
  
              return router;
          }
  
          /**
           * Parses a query string into an object.
           * @method parseQueryString
           * @param {string} queryString The query string to parse.
           * @return {object} An object keyed according to the query string parameters.
           */
          router.parseQueryString = function (queryString) {
              var queryObject, pairs;
  
              if (!queryString) {
                  return null;
              }
  
              pairs = queryString.split('&');
  
              if (pairs.length == 0) {
                  return null;
              }
  
              queryObject = {};
  
              for (var i = 0; i < pairs.length; i++) {
                  var pair = pairs[i];
                  if (pair === '') {
                      continue;
                  }
  
                  var parts = pair.split('=');
                  queryObject[parts[0]] = parts[1] && decodeURIComponent(parts[1].replace(/\+/g, ' '));
              }
  
              return queryObject;
          };
  
          /**
           * Add a route to be tested when the url fragment changes.
           * @method route
           * @param {RegEx} routePattern The route pattern to test against.
           * @param {function} callback The callback to execute when the route pattern is matched.
           */
          router.route = function(routePattern, callback) {
              router.handlers.push({ routePattern: routePattern, callback: callback });
          };
  
          /**
           * Attempt to load the specified URL fragment. If a route succeeds with a match, returns `true`. If no defined routes matches the fragment, returns `false`.
           * @method loadUrl
           * @param {string} fragment The URL fragment to find a match for.
           * @return {boolean} True if a match was found, false otherwise.
           */
          router.loadUrl = function(fragment) {
              var handlers = router.handlers,
                  queryString = null,
                  coreFragment = fragment,
                  queryIndex = fragment.indexOf('?');
  
              if (queryIndex != -1) {
                  coreFragment = fragment.substring(0, queryIndex);
                  queryString = fragment.substr(queryIndex + 1);
              }
  
              if(router.relativeToParentRouter){
                  var instruction = this.parent.activeInstruction();
                  coreFragment = instruction.params.join('/');
  
                  if(coreFragment && coreFragment.charAt(0) == '/'){
                      coreFragment = coreFragment.substr(1);
                  }
  
                  if(!coreFragment){
                      coreFragment = '';
                  }
  
                  coreFragment = coreFragment.replace('//', '/').replace('//', '/');
              }
  
              coreFragment = coreFragment.replace(trailingSlash, '');
  
              for (var i = 0; i < handlers.length; i++) {
                  var current = handlers[i];
                  if (current.routePattern.test(coreFragment)) {
                      current.callback(coreFragment, queryString);
                      return true;
                  }
              }
  
              system.log('Route Not Found');
              router.trigger('router:route:not-found', fragment, router);
  
              if (currentInstruction) {
                  history.navigate(currentInstruction.fragment, { trigger:false, replace:true });
              }
  
              rootRouter.explicitNavigation = false;
              rootRouter.navigatingBack = false;
  
              return false;
          };
  
          /**
           * Updates the document title based on the activated module instance, the routing instruction and the app.title.
           * @method updateDocumentTitle
           * @param {object} instance The activated module.
           * @param {object} instruction The routing instruction associated with the action. It has a `config` property that references the original route mapping config.
           */
          router.updateDocumentTitle = function(instance, instruction) {
              if (instruction.config.title) {
                  if (app.title) {
                      document.title = instruction.config.title + " | " + app.title;
                  } else {
                      document.title = instruction.config.title;
                  }
              } else if (app.title) {
                  document.title = app.title;
              }
          };
  
          /**
           * Save a fragment into the hash history, or replace the URL state if the
           * 'replace' option is passed. You are responsible for properly URL-encoding
           * the fragment in advance.
           * The options object can contain `trigger: false` if you wish to not have the
           * route callback be fired, or `replace: true`, if
           * you wish to modify the current URL without adding an entry to the history.
           * @method navigate
           * @param {string} fragment The url fragment to navigate to.
           * @param {object|boolean} options An options object with optional trigger and replace flags. You can also pass a boolean directly to set the trigger option. Trigger is `true` by default.
           * @return {boolean} Returns true/false from loading the url.
           */
          router.navigate = function(fragment, options) {
              if(fragment && fragment.indexOf('://') != -1){
                  window.location.href = fragment;
                  return true;
              }
  
              rootRouter.explicitNavigation = true;
              return history.navigate(fragment, options);
          };
  
          /**
           * Navigates back in the browser history.
           * @method navigateBack
           */
          router.navigateBack = function() {
              history.navigateBack();
          };
  
          router.attached = function() {
              router.trigger('router:navigation:attached', currentActivation, currentInstruction, router);
          };
  
          router.compositionComplete = function(){
              isProcessing(false);
              router.trigger('router:navigation:composition-complete', currentActivation, currentInstruction, router);
              dequeueInstruction();
          };
  
          /**
           * Converts a route to a hash suitable for binding to a link's href.
           * @method convertRouteToHash
           * @param {string} route
           * @return {string} The hash.
           */
          router.convertRouteToHash = function(route) {
              if(router.relativeToParentRouter){
                  var instruction = router.parent.activeInstruction(),
                      hash = instruction.config.hash + '/' + route;
  
                  if(history._hasPushState){
                      hash = '/' + hash;
                  }
  
                  hash = hash.replace('//', '/').replace('//', '/');
                  return hash;
              }
  
              if(history._hasPushState){
                  return route;
              }
  
              return "#" + route;
          };
  
          /**
           * Converts a route to a module id. This is only called if no module id is supplied as part of the route mapping.
           * @method convertRouteToModuleId
           * @param {string} route
           * @return {string} The module id.
           */
          router.convertRouteToModuleId = function(route) {
              return stripParametersFromRoute(route);
          };
  
          /**
           * Converts a route to a displayable title. This is only called if no title is specified as part of the route mapping.
           * @method convertRouteToTitle
           * @param {string} route
           * @return {string} The title.
           */
          router.convertRouteToTitle = function(route) {
              var value = stripParametersFromRoute(route);
              return value.substring(0, 1).toUpperCase() + value.substring(1);
          };
  
          /**
           * Maps route patterns to modules.
           * @method map
           * @param {string|object|object[]} route A route, config or array of configs.
           * @param {object} [config] The config for the specified route.
           * @chainable
           * @example
   router.map([
      { route: '', title:'Home', moduleId: 'homeScreen', nav: true },
      { route: 'customer/:id', moduleId: 'customerDetails'}
   ]);
           */
          router.map = function(route, config) {
              if (system.isArray(route)) {
                  for (var i = 0; i < route.length; i++) {
                      router.map(route[i]);
                  }
  
                  return router;
              }
  
              if (system.isString(route) || system.isRegExp(route)) {
                  if (!config) {
                      config = {};
                  } else if (system.isString(config)) {
                      config = { moduleId: config };
                  }
  
                  config.route = route;
              } else {
                  config = route;
              }
  
              return mapRoute(config);
          };
  
          /**
           * Builds an observable array designed to bind a navigation UI to. The model will exist in the `navigationModel` property.
           * @method buildNavigationModel
           * @param {number} defaultOrder The default order to use for navigation visible routes that don't specify an order. The default is 100 and each successive route will be one more than that.
           * @chainable
           */
          router.buildNavigationModel = function(defaultOrder) {
              var nav = [], routes = router.routes;
              var fallbackOrder = defaultOrder || 100;
  
              for (var i = 0; i < routes.length; i++) {
                  var current = routes[i];
  
                  if (current.nav) {
                      if (!system.isNumber(current.nav)) {
                          current.nav = ++fallbackOrder;
                      }
  
                      nav.push(current);
                  }
              }
  
              nav.sort(function(a, b) { return a.nav - b.nav; });
              router.navigationModel(nav);
  
              return router;
          };
  
          /**
           * Configures how the router will handle unknown routes.
           * @method mapUnknownRoutes
           * @param {string|function} [config] If not supplied, then the router will map routes to modules with the same name.
           * If a string is supplied, it represents the module id to route all unknown routes to.
           * Finally, if config is a function, it will be called back with the route instruction containing the route info. The function can then modify the instruction by adding a moduleId and the router will take over from there.
           * @param {string} [replaceRoute] If config is a module id, then you can optionally provide a route to replace the url with.
           * @chainable
           */
          router.mapUnknownRoutes = function(config, replaceRoute) {
              var catchAllRoute = "*catchall";
              var catchAllPattern = routeStringToRegExp(catchAllRoute);
  
              router.route(catchAllPattern, function (fragment, queryString) {
                  var paramInfo = createParams(catchAllPattern, fragment, queryString);
                  var instruction = {
                      fragment: fragment,
                      queryString: queryString,
                      config: {
                          route: catchAllRoute,
                          routePattern: catchAllPattern
                      },
                      params: paramInfo.params,
                      queryParams: paramInfo.queryParams
                  };
  
                  if (!config) {
                      instruction.config.moduleId = fragment;
                  } else if (system.isString(config)) {
                      instruction.config.moduleId = config;
                      if(replaceRoute){
                          history.navigate(replaceRoute, { trigger:false, replace:true });
                      }
                  } else if (system.isFunction(config)) {
                      var result = config(instruction);
                      if (result && result.then) {
                          result.then(function() {
                              router.trigger('router:route:before-config', instruction.config, router);
                              router.trigger('router:route:after-config', instruction.config, router);
                              queueInstruction(instruction);
                          });
                          return;
                      }
                  } else {
                      instruction.config = config;
                      instruction.config.route = catchAllRoute;
                      instruction.config.routePattern = catchAllPattern;
                  }
  
                  router.trigger('router:route:before-config', instruction.config, router);
                  router.trigger('router:route:after-config', instruction.config, router);
                  queueInstruction(instruction);
              });
  
              return router;
          };
  
          /**
           * Resets the router by removing handlers, routes, event handlers and previously configured options.
           * @method reset
           * @chainable
           */
          router.reset = function() {
              currentInstruction = currentActivation = undefined;
              router.handlers = [];
              router.routes = [];
              router.off();
              delete router.options;
              return router;
          };
  
          /**
           * Makes all configured routes and/or module ids relative to a certain base url.
           * @method makeRelative
           * @param {string|object} settings If string, the value is used as the base for routes and module ids. If an object, you can specify `route` and `moduleId` separately. In place of specifying route, you can set `fromParent:true` to make routes automatically relative to the parent router's active route.
           * @chainable
           */
          router.makeRelative = function(settings){
              if(system.isString(settings)){
                  settings = {
                      moduleId:settings,
                      route:settings
                  };
              }
  
              if(settings.moduleId && !endsWith(settings.moduleId, '/')){
                  settings.moduleId += '/';
              }
  
              if(settings.route && !endsWith(settings.route, '/')){
                  settings.route += '/';
              }
  
              if(settings.fromParent){
                  router.relativeToParentRouter = true;
              }
  
              router.on('router:route:before-config').then(function(config){
                  if(settings.moduleId){
                      config.moduleId = settings.moduleId + config.moduleId;
                  }
  
                  if(settings.route){
                      if(config.route === ''){
                          config.route = settings.route.substring(0, settings.route.length - 1);
                      }else{
                          config.route = settings.route + config.route;
                      }
                  }
              });
  
              return router;
          };
  
          /**
           * Creates a child router.
           * @method createChildRouter
           * @return {Router} The child router.
           */
          router.createChildRouter = function() {
              var childRouter = createRouter();
              childRouter.parent = router;
              return childRouter;
          };
  
          return router;
      }