[CORD-1653] Adding a debug tab in model details

Change-Id: I6c3be4227309cbeb2dd7ab6252c1312dfd00fb18
diff --git a/src/app/core/debug/debug-model.html b/src/app/core/debug/debug-model.html
new file mode 100644
index 0000000..cf74189
--- /dev/null
+++ b/src/app/core/debug/debug-model.html
@@ -0,0 +1,22 @@
+<!--
+Copyright 2017-present Open Networking Foundation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<div class="row" ng-if="vm.ngModel[field]" ng-repeat="field in vm.debugFields">
+    <div class="col-xs-12">
+        <label>{{vm.toLabel(field)}}</label>
+        <pre>{{vm.parseField(field, vm.ngModel[field])}}</pre>
+    </div>
+</div>
\ No newline at end of file
diff --git a/src/app/core/debug/debug-model.spec.ts b/src/app/core/debug/debug-model.spec.ts
new file mode 100644
index 0000000..46b6344
--- /dev/null
+++ b/src/app/core/debug/debug-model.spec.ts
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as angular from 'angular';
+import 'angular-mocks';
+import 'angular-ui-router';
+import {xosDebugModel} from './debug-model';
+
+const MockConfigHelpers = {
+  toLabel: jasmine.createSpy('toLabel')
+};
+
+const model = {
+  policed: 1503009920,
+  backend_register: '{\"next_run\": 0, \"last_success\": 1502860176.52445, \"exponent\": 0}',
+  backend_status: '1 - OK',
+  id: 1,
+  backend_need_delete: true,
+  self_content_type_id: 'core.instance',
+  backend_need_reap: false,
+  no_sync: false,
+  updated: 1503009920,
+  deleted: false,
+  policy_status: '2 - AuthorizationFailure(Authorization Failed: SSL exception connecting to https://192.168.108.119:5000/v2.0/tokens,) // Exception(Ansible playbook failed. // Error in creating the server, please check logs,) // The VM is available but not Active. state:ERROR,)',
+  lazy_blocked: false,
+  enacted: 1503009920,
+  enabled: 1503009920,
+  leaf_model_name: 'Instance',
+  created: 1503009920,
+  write_protect: false,
+  no_policy: false,
+  class_names: 'Instance,XOSBase'
+};
+
+describe('The xosDebugModel component', () => {
+  let scope, rootScope, element, compile , isolatedScope;
+
+  const compileElement = () => {
+
+    if (!scope) {
+      scope = rootScope.$new();
+    }
+
+    element = angular.element(`<xos-debug-model ng-model="model"></xos-debug-model>`);
+    compile(element)(scope);
+    scope.$digest();
+    isolatedScope = element.isolateScope().vm;
+  };
+
+  beforeEach(() => {
+    angular.module('xosDebugModel', [])
+      .component('xosDebugModel', xosDebugModel)
+      .value('ConfigHelpers', MockConfigHelpers);
+    angular.mock.module('xosDebugModel');
+
+    inject(($compile: ng.ICompileService, $rootScope: ng.IScope) => {
+      rootScope = $rootScope;
+      compile = $compile;
+    });
+  });
+
+  it('should have a toLabel method', () => {
+    compileElement();
+    expect(isolatedScope.toLabel).toBeDefined();
+    isolatedScope.toLabel('a');
+    expect(MockConfigHelpers.toLabel).toHaveBeenCalledWith('a');
+  });
+
+  describe('the parseField method', () => {
+    beforeEach(() => {
+      scope = rootScope.$new();
+      scope.model = model;
+      compileElement();
+    });
+
+    it('should convert dates', () => {
+      const dateFields = ['created', 'updated', 'enacted', 'policed'];
+
+      dateFields.forEach(f => {
+        const date = isolatedScope.parseField(f, model[f]);
+        expect(date).toEqual('Thu Aug 17 2017 15:45:20 GMT-0700 (PDT)');
+      });
+    });
+
+    it('should convert strings to JSON', () => {
+      const res = isolatedScope.parseField('backend_register', model['backend_register']);
+      expect(res.next_run).toBe(0);
+      expect(res.exponent).toBe(0);
+    });
+
+    it('should parse backend_status and policy_status', () => {
+      const policy = isolatedScope.parseField('policy_status', model['policy_status']);
+      expect(policy.match(/\n/g).length).toBe(3);
+    });
+  });
+});
diff --git a/src/app/core/debug/debug-model.ts b/src/app/core/debug/debug-model.ts
new file mode 100644
index 0000000..9760520
--- /dev/null
+++ b/src/app/core/debug/debug-model.ts
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// NOTE this component will render the hidden model fields for debug purposes
+
+import {IXosConfigHelpersService} from '../services/helpers/config.helpers';
+export class XosDebugModelController {
+  static $inject = [
+    'ConfigHelpers'
+  ];
+
+  public debugFields: string[];
+
+  constructor(
+    private ConfigHelpers: IXosConfigHelpersService
+  ) {
+
+  }
+
+  $onInit() {
+    this.debugFields = this.ConfigHelpers.form_excluded_fields;
+  }
+
+  public toLabel(string: string): string {
+    return this.ConfigHelpers.toLabel(string);
+  }
+
+  // NOTE each field has his own format, so make it human readable
+  public parseField(fieldName: string, value: any): any {
+    switch (fieldName) {
+      case 'created':
+      case 'updated':
+      case 'enacted':
+      case 'policed':
+        return new Date(parseInt(value, 10) * 1000).toString();
+      case 'backend_register':
+        return JSON.parse(value);
+      case 'policy_status':
+      case 'backend_status':
+        return value
+          .split(' // ')
+          .join('\n');
+      default:
+        return value;
+    }
+  }
+}
+
+export const xosDebugModel: angular.IComponentOptions = {
+  template: require('./debug-model.html'),
+    controllerAs: 'vm',
+    controller: XosDebugModelController,
+    bindings: {
+      ngModel: '=',
+    }
+};
diff --git a/src/app/core/debug/debug.html b/src/app/core/debug/debug-summary.html
similarity index 81%
rename from src/app/core/debug/debug.html
rename to src/app/core/debug/debug-summary.html
index 2590708..9807ac9 100644
--- a/src/app/core/debug/debug.html
+++ b/src/app/core/debug/debug-summary.html
@@ -34,5 +34,12 @@
                 <i class="fa fa-remove text-danger" ng-hide="vm.debugStatus.events"></i>
             </td>
         </tr>
+        <tr>
+            <td>Models</td>
+            <td class="text-right">
+                <i class="fa fa-check text-success" ng-show="vm.debugStatus.modelsTab"></i>
+                <i class="fa fa-remove text-danger" ng-hide="vm.debugStatus.modelsTab"></i>
+            </td>
+        </tr>
     </tbody>
 </table>
\ No newline at end of file
diff --git a/src/app/core/debug/debug.ts b/src/app/core/debug/debug-summary.ts
similarity index 85%
rename from src/app/core/debug/debug.ts
rename to src/app/core/debug/debug-summary.ts
index fe5d178..f037080 100644
--- a/src/app/core/debug/debug.ts
+++ b/src/app/core/debug/debug-summary.ts
@@ -16,7 +16,7 @@
 
 import {IXosDebugStatus, IXosDebugService} from './debug.service';
 
-class XosDebugComponentController {
+class XosDebugSummaryController {
   static $inject = ['$scope', 'XosDebug'];
   public debugStatus: IXosDebugStatus;
 
@@ -33,8 +33,8 @@
   }
 }
 
-export const xosDebugComponent: angular.IComponentOptions = {
-  template: require('./debug.html'),
+export const xosDebugSummary: angular.IComponentOptions = {
+  template: require('./debug-summary.html'),
   controllerAs: 'vm',
-  controller: XosDebugComponentController
+  controller: XosDebugSummaryController
 };
diff --git a/src/app/core/debug/debug.service.spec.ts b/src/app/core/debug/debug.service.spec.ts
index 0d27fa6..a26d8cc 100644
--- a/src/app/core/debug/debug.service.spec.ts
+++ b/src/app/core/debug/debug.service.spec.ts
@@ -56,16 +56,16 @@
   it('should disable the global debug status', () => {
     spyOn(window.localStorage, 'getItem')
       .and.returnValue('true');
-    service.toggleGlobalDebug();
-    expect(window.localStorage.setItem).toHaveBeenCalledWith('debug', 'false');
+    service.toggleDebug('global');
+    expect(window.localStorage.setItem).toHaveBeenCalledWith('debug-global', 'false');
     expect(service.status.global).toBeFalsy();
     expect($scope.$broadcast).toHaveBeenCalledWith('xos.debug.status', service.status);
   });
   it('should enable the global debug status', () => {
     spyOn(window.localStorage, 'getItem')
       .and.returnValue('false');
-    service.toggleGlobalDebug();
-    expect(window.localStorage.setItem).toHaveBeenCalledWith('debug', 'true');
+    service.toggleDebug('global');
+    expect(window.localStorage.setItem).toHaveBeenCalledWith('debug-global', 'true');
     expect(service.status.global).toBeTruthy();
     expect($scope.$broadcast).toHaveBeenCalledWith('xos.debug.status', service.status);
   });
diff --git a/src/app/core/debug/debug.service.ts b/src/app/core/debug/debug.service.ts
index 3c6c2c8..f23fa06 100644
--- a/src/app/core/debug/debug.service.ts
+++ b/src/app/core/debug/debug.service.ts
@@ -19,13 +19,13 @@
 export interface IXosDebugStatus {
   global: boolean;
   events: boolean;
+  modelsTab: boolean;
 }
 
 export interface IXosDebugService {
   status: IXosDebugStatus;
   setupShortcuts(): void;
-  toggleGlobalDebug(): void;
-  toggleEventDebug(): void;
+  toggleDebug(type: 'global' | 'events' | 'modelsTab'): void;
 }
 
 export class XosDebugService implements IXosDebugService {
@@ -34,7 +34,8 @@
 
   public status: IXosDebugStatus = {
     global: false,
-    events: false
+    events: false,
+    modelsTab: false
   };
 
   constructor (
@@ -42,51 +43,40 @@
     private $scope: ng.IScope,
     private XosKeyboardShortcut: IXosKeyboardShortcutService
   ) {
-    const debug = window.localStorage.getItem('debug');
+    const debug = window.localStorage.getItem('debug-global');
     this.status.global = (debug === 'true');
 
-    const debugEvent = window.localStorage.getItem('debug-event');
+    const debugEvent = window.localStorage.getItem('debug-events');
     this.status.events = (debugEvent === 'true');
+
+    const debugModelsTab = window.localStorage.getItem('debug-modelsTab');
+    this.status.modelsTab = (debugModelsTab === 'true');
   }
 
   public setupShortcuts(): void {
     this.XosKeyboardShortcut.registerKeyBinding({
       key: 'D',
-      cb: () => this.toggleGlobalDebug(),
+      cb: () => this.toggleDebug('global'),
       description: 'Toggle debug messages in browser console'
     }, 'global');
 
     this.XosKeyboardShortcut.registerKeyBinding({
       key: 'E',
-      cb: () => this.toggleEventDebug(),
+      cb: () => this.toggleDebug('events'),
       description: 'Toggle debug messages for WS events in browser console'
     }, 'global');
   }
 
-  public toggleGlobalDebug(): void {
-    if (window.localStorage.getItem('debug') === 'true') {
-      this.$log.info(`[XosDebug] Disabling debug`);
-      window.localStorage.setItem('debug', 'false');
-      this.status.global = false;
+  public toggleDebug(type: 'global' | 'events' | 'modelsTab'): void {
+    if (window.localStorage.getItem(`debug-${type}`) === 'true') {
+      this.$log.info(`[XosDebug] Disabling ${type} debug`);
+      window.localStorage.setItem(`debug-${type}`, 'false');
+      this.status[type] = false;
     }
     else {
-      window.localStorage.setItem('debug', 'true');
-      this.$log.info(`[XosDebug] Enabling debug`);
-      this.status.global = true;
-    }
-    this.$scope.$broadcast('xos.debug.status', this.status);
-  }
-
-  public toggleEventDebug(): void {
-    if (window.localStorage.getItem('debug-event') === 'true') {
-      this.$log.info(`[XosDebug] Disabling debug for WS events`);
-      window.localStorage.setItem('debug-event', 'false');
-      this.status.events = false;
-    }
-    else {
-      window.localStorage.setItem('debug-event', 'true');
-      this.$log.info(`[XosDebug] Enabling debug for WS events`);
-      this.status.events = true;
+      this.$log.info(`[XosDebug] Enabling ${type} debug`);
+      window.localStorage.setItem(`debug-${type}`, 'true');
+      this.status[type] = true;
     }
     this.$scope.$broadcast('xos.debug.status', this.status);
   }
diff --git a/src/app/core/index.ts b/src/app/core/index.ts
index 0172223..523bae6 100644
--- a/src/app/core/index.ts
+++ b/src/app/core/index.ts
@@ -43,8 +43,9 @@
 import {XosDebouncer} from './services/helpers/debounce.helper';
 import {ArrayToListFilter} from './table/array-to-list.filter';
 import {xosLoader} from './loader/loader';
-import {xosDebugComponent} from './debug/debug';
+import {xosDebugSummary} from './debug/debug-summary';
 import {XosDebugService} from './debug/debug.service';
+import {xosDebugModel} from './debug/debug-model';
 
 export const xosCore = 'xosCore';
 
@@ -80,6 +81,7 @@
   .component('xosValidation', xosValidation)
   .component('xosSidePanel', xosSidePanel)
   .component('xosKeyBindingPanel', xosKeyBindingPanel)
-  .component('xosDebug', xosDebugComponent)
+  .component('xosDebugSummary', xosDebugSummary)
+  .component('xosDebugModel', xosDebugModel)
   .filter('pagination', PaginationFilter)
   .filter('arrayToList', ArrayToListFilter);
diff --git a/src/app/core/key-binding/key-binding-panel.html b/src/app/core/key-binding/key-binding-panel.html
index df26318..ed198fd 100644
--- a/src/app/core/key-binding/key-binding-panel.html
+++ b/src/app/core/key-binding/key-binding-panel.html
@@ -49,6 +49,6 @@
 </div>
 <div class="row">
     <div class="col-xs-12">
-        <xos-debug></xos-debug>
+        <xos-debug-summary></xos-debug-summary>
     </div>
 </div>
\ No newline at end of file
diff --git a/src/app/core/services/helpers/config.helpers.ts b/src/app/core/services/helpers/config.helpers.ts
index 81901e3..c177568 100644
--- a/src/app/core/services/helpers/config.helpers.ts
+++ b/src/app/core/services/helpers/config.helpers.ts
@@ -45,6 +45,7 @@
 
 export interface IXosConfigHelpersService {
   excluded_fields: string[];
+  form_excluded_fields: string[];
   modelFieldsToColumnsCfg(model: IXosModeldef): IXosTableColumn[];
   modelToTableCfg(model: IXosModeldef, modelName: string): IXosTableCfg;
   modelFieldToInputCfg(fields: IXosModelDefsField[]): IXosFormInput[];
diff --git a/src/app/core/services/keyboard-shortcut.spec.ts b/src/app/core/services/keyboard-shortcut.spec.ts
index a4a15dd..fa9bf41 100644
--- a/src/app/core/services/keyboard-shortcut.spec.ts
+++ b/src/app/core/services/keyboard-shortcut.spec.ts
@@ -157,7 +157,8 @@
         global: [
           {
             key: 'a',
-            cb: 'cb'
+            cb: 'cb',
+            modifiers: undefined
           }
         ],
         view: []
diff --git a/src/app/core/services/keyboard-shortcut.ts b/src/app/core/services/keyboard-shortcut.ts
index 72a59a5..b8678f7 100644
--- a/src/app/core/services/keyboard-shortcut.ts
+++ b/src/app/core/services/keyboard-shortcut.ts
@@ -102,7 +102,6 @@
     $('body').on('keydown', (e) => {
 
       const pressedKey = this.whatKey(e.which);
-
       if (!pressedKey) {
         return;
       }
@@ -141,7 +140,7 @@
     }
 
     binding.key = binding.key.toLowerCase();
-    if (_.find(this.keyMapping.global, {key: binding.key}) || _.find(this.keyMapping.view, {key: binding.key})) {
+    if (_.find(this.keyMapping.global, {key: binding.key, modifiers: binding.modifiers}) || _.find(this.keyMapping.view, {key: binding.key, modifiers: binding.modifiers})) {
       this.$log.warn(`[XosKeyboardShortcut] A shortcut for key "${binding.key}" has already been registered`);
       return;
     }
diff --git a/src/app/core/table/table.html b/src/app/core/table/table.html
index b8fe9f4..b7aaa96 100644
--- a/src/app/core/table/table.html
+++ b/src/app/core/table/table.html
@@ -14,8 +14,6 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 -->
-
-
 <div ng-show="vm.data.length > 0 && vm.loader == false">
     <div class="row" ng-if="vm.config.filter == 'fulltext'">
         <div class="col-xs-12">
@@ -66,7 +64,7 @@
             </tr>
         </tbody>
         <tbody>
-        <tr ng-repeat="item in vm.data | filter:vm.query | orderBy:vm.orderBy:vm.reverse | pagination:vm.currentPage * vm.config.pagination.pageSize | limitTo: (vm.config.pagination.pageSize || vm.data.length) track by $index">
+        <tr ng-class="{active: vm.config.selectedRow == $index}" ng-repeat="item in (vm.config.filteredData = (vm.data | filter:vm.query | orderBy:vm.orderBy:vm.reverse | pagination:vm.currentPage * vm.config.pagination.pageSize | limitTo: (vm.config.pagination.pageSize || vm.data.length))) track by $index">
             <td ng-repeat="col in vm.config.columns" xos-link-wrapper>
                 <span ng-if="!col.type || col.type === 'text'">{{item[col.prop]}}</span>
                 <span ng-if="col.type === 'boolean'">
diff --git a/src/app/core/table/table.scss b/src/app/core/table/table.scss
index 1189a9f..b20895b 100644
--- a/src/app/core/table/table.scss
+++ b/src/app/core/table/table.scss
@@ -56,5 +56,4 @@
       margin-left: $padding-base-horizontal;
     }
   }
-
 }
\ No newline at end of file
diff --git a/src/app/core/table/table.ts b/src/app/core/table/table.ts
index 46a5ca5..6c8d95c 100644
--- a/src/app/core/table/table.ts
+++ b/src/app/core/table/table.ts
@@ -52,6 +52,8 @@
   };
   order?: IXosTableCgfOrder;
   filter?: string;
+  selectedRow?: number;
+  filteredData?: any[];
   actions?: any[]; // TODO create interface
 }
 
@@ -145,8 +147,6 @@
       this.currentPage = 0;
     }
 
-    // this.columns = this.config.columns;
-
   }
 
   public goToPage = (n) => {
diff --git a/src/app/views/crud/crud.html b/src/app/views/crud/crud.html
index b650520..adeaaa4 100644
--- a/src/app/views/crud/crud.html
+++ b/src/app/views/crud/crud.html
@@ -48,6 +48,11 @@
                 <xos-form ng-model="vm.model" config="vm.formCfg"></xos-form>
             </div>
         </uib-tab>
+        <uib-tab ng-if="vm.debugTab" heading="Debug">
+            <div class="panel-body">
+                <xos-debug-model ng-model="vm.model"></xos-debug-model>
+            </div>
+        </uib-tab>
         <uib-tab ng-if="vm.getRelatedItemId(r, vm.model)" ng-repeat="r in vm.related.manytoone" heading="{{r.model}} {{vm.getHumanReadableOnField(r)}}">
             <div class="panel-body">
                 <xos-form ng-model="vm.relatedModels.manytoone[r.model][r.on_field].model" config="vm.relatedModels.manytoone[r.model][r.on_field].formConfig"></xos-form>
diff --git a/src/app/views/crud/crud.ts b/src/app/views/crud/crud.ts
index 9599823..325790f 100644
--- a/src/app/views/crud/crud.ts
+++ b/src/app/views/crud/crud.ts
@@ -25,6 +25,8 @@
 import {IXosModelDiscovererService} from '../../datasources/helpers/model-discoverer.service';
 import './crud.scss';
 import {IXosCrudRelationService} from './crud.relations.service';
+import {IXosDebugService, IXosDebugStatus} from '../../core/debug/debug.service';
+import {IXosKeyboardShortcutService} from '../../core/services/keyboard-shortcut';
 
 export interface IXosModelRelation {
   model: string;
@@ -43,7 +45,9 @@
     'ModelRest',
     'StoreHelpers',
     'XosModelDiscoverer',
-    'XosCrudRelation'
+    'XosCrudRelation',
+    'XosDebug',
+    'XosKeyboardShortcut'
   ];
 
   // bindings
@@ -64,6 +68,7 @@
     manytoone: {},
     onetomany: {}
   };
+  public debugTab: boolean;
 
   constructor(
     private $scope: angular.IScope,
@@ -75,7 +80,9 @@
     private ModelRest: IXosResourceService,
     private StoreHelpers: IStoreHelpersService,
     private XosModelDiscovererService: IXosModelDiscovererService,
-    private XosCrudRelation: IXosCrudRelationService
+    private XosCrudRelation: IXosCrudRelationService,
+    private XosDebug: IXosDebugService,
+    private XosKeyboardShortcut: IXosKeyboardShortcutService
   ) {
     this.$log.info('[XosCrud] Setup', $state.current.data);
 
@@ -88,10 +95,15 @@
     // TODO get the proper URL from model discoverer
     this.baseUrl = '#/' + this.model.clientUrl.replace(':id?', '');
 
-
     this.tableCfg = this.model.tableCfg;
     this.formCfg = this.model.formCfg;
 
+    this.debugTab = this.XosDebug.status.modelsTab;
+    this.$scope.$on('xos.debug.status', (e, status: IXosDebugStatus) => {
+      this.debugTab = status.modelsTab;
+      this.$scope.$apply();
+    });
+
     this.store.query(this.data.model)
       .subscribe(
         (event) => {
@@ -122,9 +134,76 @@
         const resource = this.ModelRest.getResource(endpoint);
         this.model = new resource({});
       }
+
+      this.XosKeyboardShortcut.registerKeyBinding({
+        key: 'A',
+        cb: () => this.XosDebug.toggleDebug('modelsTab'),
+        description: 'Toggle Debug tab in model details view'
+      }, 'view');
+
+      this.XosKeyboardShortcut.registerKeyBinding({
+        key: 'delete',
+        cb: () => {
+          this.$state.go(this.$state.current.name, {id: null});
+        },
+        description: 'Go back to the list view'
+      }, 'view');
+    }
+    // list page
+    else {
+      this.tableCfg.selectedRow = -1;
+
+      this.XosKeyboardShortcut.registerKeyBinding({
+        key: 'Tab',
+        cb: () => this.iterateItems(),
+        description: 'Iterate trough items in the list'
+      }, 'view');
+
+      this.XosKeyboardShortcut.registerKeyBinding({
+        key: 'Enter',
+        cb: () => {
+          if (this.tableCfg.selectedRow < 0) {
+            return;
+          }
+          this.$state.go(this.$state.current.name, {id: this.tableCfg.filteredData[this.tableCfg.selectedRow].id});
+        },
+        description: 'View details of selected item'
+      }, 'view');
+
+      this.XosKeyboardShortcut.registerKeyBinding({
+        key: 'Delete',
+        cb: () => {
+          if (this.tableCfg.selectedRow < 0) {
+            return;
+          }
+          const deleteFn = _.find(this.tableCfg.actions, {label: 'delete'});
+          deleteFn.cb(this.tableCfg.filteredData[this.tableCfg.selectedRow]);
+        },
+        description: 'View details of selected item'
+      }, 'view');
+
+      // FIXME XosKeyboardShortcut modifiers does not look to work
+      // this.XosKeyboardShortcut.registerKeyBinding({
+      //   key: 'Tab',
+      //   modifiers: ['alt'],
+      //   cb: () => {
+      //     this.tableCfg.selectedRow = -1;
+      //   },
+      //   description: 'Clear selected item'
+      // }, 'view');
     }
   }
 
+  public iterateItems() {
+    const rowCount = this.tableCfg.filteredData.length > this.tableCfg.pagination.pageSize ? this.tableCfg.pagination.pageSize : this.tableCfg.filteredData.length;
+    if ((this.tableCfg.selectedRow + 1) < rowCount) {
+      this.tableCfg.selectedRow++;
+    }
+    else {
+      this.tableCfg.selectedRow = 0;
+    }
+    this.$scope.$apply();
+  }
 
   public getRelatedItemId(relation: IXosModelRelation, item: any): boolean {
     return this.XosCrudRelation.existsRelatedItem(relation, item);