Added child routes, and config defined routes
Change-Id: I61c5a49a330a63737312d1eb6077aab02236a44f
diff --git a/src/app/core/nav/nav.html b/src/app/core/nav/nav.html
index 2bcdadd..a1292b8 100644
--- a/src/app/core/nav/nav.html
+++ b/src/app/core/nav/nav.html
@@ -1,8 +1,23 @@
<div class="nav">
<ul>
- <li ng-repeat="route in vm.routes" ui-sref-active="active" ng-class="vm.isRouteActive(route)">
- <a ng-if="route.state" ui-sref="{{route.state}}">{{route.label}}</a>
- <a ng-if="route.url" href="#/{{route.url}}">{{route.label}}</a>
+ <li
+ ng-repeat="route in vm.routes track by $index"
+ ui-sref-active="active"
+ ng-class="vm.isRouteActive(route)">
+ <a ng-if="route.state" ui-sref="{{route.state}}" ng-click="vm.activateRoute(route)">
+ <i ng-if="route.children" class="fa fa-chevron-right"></i>
+ {{route.label}}
+ </a>
+ <a ng-if="route.url" href="#/{{route.url}}" ng-click="vm.activateRoute(route)">
+ <i ng-if="route.children" class="fa fa-chevron-right"></i>
+ {{route.label}}
+ </a>
+ <ul class="child-routes" ng-if="route.children" ng-class="{opened: route.opened}">
+ <li ng-repeat="childRoute in route.children" ui-sref-active="active">
+ <a ng-if="childRoute.state" ui-sref="{{childRoute.state}}">{{childRoute.label}}</a>
+ <a ng-if="childRoute.url" href="#/{{childRoute.url}}">{{childRoute.label}}</a>
+ </li>
+ </ul>
</li>
</ul>
</div>
diff --git a/src/app/core/nav/nav.scss b/src/app/core/nav/nav.scss
index 5c2c85c..dbe60d7 100644
--- a/src/app/core/nav/nav.scss
+++ b/src/app/core/nav/nav.scss
@@ -15,7 +15,6 @@
> li {
display: flex;
flex-direction: column;
- padding: 10px 20px;
border-bottom: 1px solid darken(grey, 20);
&.active {
@@ -31,8 +30,29 @@
}
> a {
+ padding: 10px 20px;
cursor: pointer;
}
+
+ // child router
+ > ul {
+ height: 0;
+ overflow: hidden;
+ transition: .5s all;
+
+ > li {
+ padding-left: 20px;
+ background: darken(grey, 15);
+
+ &:hover, &.active {
+ background: darken(grey, 20);
+ }
+ }
+ }
+
+ > ul.opened {
+ height: auto;
+ }
}
}
}
diff --git a/src/app/core/nav/nav.spec.ts b/src/app/core/nav/nav.spec.ts
new file mode 100644
index 0000000..fbed8ea
--- /dev/null
+++ b/src/app/core/nav/nav.spec.ts
@@ -0,0 +1,57 @@
+/// <reference path="../../../../typings/index.d.ts" />
+
+import * as $ from 'jquery';
+import 'jasmine-jquery';
+import * as angular from 'angular';
+import 'angular-mocks';
+import {IXosNavigationRoute} from '../services/navigation';
+import {xosNav} from './nav';
+
+let element, scope: angular.IRootScopeService, compile: ng.ICompileService, isolatedScope;
+
+let baseRoutes: IXosNavigationRoute[] = [
+ {label: 'Home', state: 'xos'},
+ {label: 'Core', state: 'xos.core'}
+];
+
+const NavigationService = function(){
+ this.query = () => baseRoutes;
+};
+
+describe('Nav component', () => {
+ beforeEach(() => {
+ angular
+ .module('xosNav', ['app/core/nav/nav.html', 'ui.router'])
+ .component('xosNav', xosNav)
+ .service('NavigationService', NavigationService);
+ angular.mock.module('xosNav');
+ });
+
+ beforeEach(angular.mock.inject(($rootScope: ng.IRootScopeService, $compile: ng.ICompileService) => {
+ scope = $rootScope;
+ compile = $compile;
+ element = $compile('<xos-nav></xos-nav>')($rootScope);
+ $rootScope.$digest();
+ isolatedScope = element.isolateScope();
+
+ // clear routes
+ isolatedScope.routes = [];
+ }));
+
+ it('should render a list of routes', () => {
+ const routes = $('.nav ul li', element);
+ expect(routes.length).toBe(2);
+ });
+
+ it('should render child routes', () => {
+ baseRoutes = [
+ {label: 'Home', state: 'xos'},
+ {label: 'Core', state: 'xos.core', children: [
+ {label: 'Slices', state: 'xos.core.slices', parent: 'xos.core'}
+ ]}
+ ];
+ scope.$apply();
+ const childRouteContainer = $('.child-routes', element);
+ expect(childRouteContainer.length).toBe(1);
+ });
+});
diff --git a/src/app/core/nav/nav.ts b/src/app/core/nav/nav.ts
index d978ef0..cd249b9 100644
--- a/src/app/core/nav/nav.ts
+++ b/src/app/core/nav/nav.ts
@@ -2,10 +2,11 @@
import {IXosNavigationService, IXosNavigationRoute} from '../services/navigation';
class NavCtrl {
- static $inject = ['$state', 'NavigationService'];
+ static $inject = ['$scope', '$state', 'NavigationService'];
public routes: IXosNavigationRoute[];
constructor(
+ private $scope: ng.IScope,
private $state: angular.ui.IStateService,
private navigationService: IXosNavigationService
) {
@@ -13,12 +14,19 @@
// - Base routes (defined from configuration based on BRAND)
// - Autogenerated routes (nested somewhere)
// - Service Routes (dynamically added)
- this.routes = this.navigationService.query();
+
+ this.$scope.$watch(() => this.navigationService.query(), (routes) => {
+ this.routes = routes;
+ });
}
isRouteActive(route: IXosNavigationRoute) {
return this.$state.current.url === route.url ? 'active' : '';
}
+
+ activateRoute(route: IXosNavigationRoute) {
+ route.opened = !route.opened;
+ }
}
export const xosNav: angular.IComponentOptions = {
diff --git a/src/app/core/services/helpers/config.helpers.spec.ts b/src/app/core/services/helpers/config.helpers.spec.ts
index 44260d8..af424e4 100644
--- a/src/app/core/services/helpers/config.helpers.spec.ts
+++ b/src/app/core/services/helpers/config.helpers.spec.ts
@@ -50,13 +50,13 @@
it('should format an array of strings', () => {
let strings: string[] = ['camelCase', 'snake_case', 'kebab-case'];
let labels = ['Camel case', 'Snake case', 'Kebab case'];
- expect(service.toLabel(strings)).toEqual(labels);
+ expect(service.toLabels(strings)).toEqual(labels);
});
it('should set plural on an array of strings', () => {
let strings: string[] = ['camelCase', 'snake_case', 'kebab-case'];
let labels = ['Camel cases', 'Snake cases', 'Kebab cases'];
- expect(service.toLabel(strings, true)).toEqual(labels);
+ expect(service.toLabels(strings, true)).toEqual(labels);
});
});
diff --git a/src/app/core/services/helpers/config.helpers.ts b/src/app/core/services/helpers/config.helpers.ts
index 44d3cdc..3e0af96 100644
--- a/src/app/core/services/helpers/config.helpers.ts
+++ b/src/app/core/services/helpers/config.helpers.ts
@@ -11,6 +11,7 @@
modeldefToTableCfg(fields: IXosModelDefsField[]): any[]; // TODO use a proper interface
pluralize(string: string, quantity?: number, count?: boolean): string;
toLabel(string: string, pluralize?: boolean): string;
+ toLabels(string: string[], pluralize?: boolean): string[];
}
export class ConfigHelpers {
@@ -24,13 +25,15 @@
return pluralize(string, quantity, count);
}
- toLabel(string: string, pluralize?: boolean): string {
-
- if (angular.isArray(string)) {
- return _.map(string, s => {
+ toLabels(strings: string[], pluralize?: boolean): string[] {
+ if (angular.isArray(strings)) {
+ return _.map(strings, s => {
return this.toLabel(s, pluralize);
});
}
+ }
+
+ toLabel(string: string, pluralize?: boolean): string {
if (pluralize) {
string = this.pluralize(string);
diff --git a/src/app/core/services/navigation.spec.ts b/src/app/core/services/navigation.spec.ts
index 1096ec7..c5fb85d 100644
--- a/src/app/core/services/navigation.spec.ts
+++ b/src/app/core/services/navigation.spec.ts
@@ -6,9 +6,7 @@
let service: IXosNavigationService;
-const defaultRoutes: IXosNavigationRoute[] = [
- {label: 'Home', state: 'xos.dashboard'}
-];
+let defaultRoutes: IXosNavigationRoute[];
describe('The Navigation service', () => {
@@ -18,6 +16,7 @@
NavigationService: IXosNavigationService,
) => {
service = NavigationService;
+ defaultRoutes = angular.copy(service.query());
}));
it('should return navigation routes', () => {
@@ -31,7 +30,17 @@
];
service.add(testRoutes[0]);
service.add(testRoutes[1]);
- expect(service.query()).toEqual(defaultRoutes.concat(testRoutes));
+ const serviceRoutes = service.query();
+ expect(serviceRoutes).toEqual(defaultRoutes.concat(testRoutes));
+ });
+
+ it('should add a child route', () => {
+ const testRoute: IXosNavigationRoute = {
+ label: 'TestState', state: 'xos.test', parent: 'xos.core'
+ };
+ service.add(testRoute);
+ defaultRoutes[1].children = [testRoute];
+ expect(service.query()).toEqual(defaultRoutes);
});
it('should not add route that have both url and state', () => {
diff --git a/src/app/core/services/navigation.ts b/src/app/core/services/navigation.ts
index 04eb575..c840878 100644
--- a/src/app/core/services/navigation.ts
+++ b/src/app/core/services/navigation.ts
@@ -1,7 +1,15 @@
+/// <reference path="../../../../typings/index.d.ts" />
+
+import * as _ from 'lodash';
+import {StyleConfig} from '../../config/style.config';
+
export interface IXosNavigationRoute {
label: string;
state?: string;
url?: string;
+ parent?: string;
+ children?: [IXosNavigationRoute];
+ opened?: boolean;
}
export interface IXosNavigationService {
@@ -13,12 +21,18 @@
private routes: IXosNavigationRoute[];
constructor() {
- this.routes = [
+ const defaultRoutes = [
+ {
+ label: 'Core',
+ state: 'xos.core'
+ },
{
label: 'Home',
state: 'xos.dashboard'
}
];
+ // adding configuration defined routes
+ this.routes = StyleConfig.routes.concat(defaultRoutes).reverse();
}
query() {
@@ -29,6 +43,21 @@
if (angular.isDefined(route.state) && angular.isDefined(route.url)) {
throw new Error('[XosNavigation] You can\'t provide both state and url');
}
- this.routes.push(route);
+
+
+ if (angular.isDefined(route.parent)) {
+ // route parent should be a state for now
+ const parentRoute = _.find(this.routes, {state: route.parent});
+
+ if (angular.isArray(parentRoute.children)) {
+ parentRoute.children.push(route);
+ }
+ else {
+ parentRoute.children = [route];
+ }
+ }
+ else {
+ this.routes.push(route);
+ }
}
}
diff --git a/src/app/core/services/runtime-states.ts b/src/app/core/services/runtime-states.ts
index 401075a..bc99a8e 100644
--- a/src/app/core/services/runtime-states.ts
+++ b/src/app/core/services/runtime-states.ts
@@ -1,10 +1,10 @@
import {IXosState} from '../../../index';
export interface IRuntimeStatesService {
- addState(name: string, state: angular.ui.IState): void;
+ addState(name: string, state: ng.ui.IState): void;
}
-export function RuntimeStates($stateProvider: angular.ui.IStateProvider): angular.IServiceProvider {
- this.$get = function($state: angular.ui.IStateService) { // for example
+export function RuntimeStates($stateProvider: ng.ui.IStateProvider): ng.IServiceProvider {
+ this.$get = function($state: ng.ui.IStateService) {
return {
addState: function(name: string, state: IXosState) {
$stateProvider.state(name, state);