Creating Stores and using Observables

Change-Id: I214692e64df065beaddee0e0ec8759de540c269d
diff --git a/src/app/config/app.config.ts b/src/app/config/app.config.ts
index 2357f77..7b6fa12 100644
--- a/src/app/config/app.config.ts
+++ b/src/app/config/app.config.ts
@@ -6,6 +6,6 @@
 }
 
 export const AppConfig: IAppConfig = {
-    apiEndpoint: 'http://xos-rest-gw:3000/api',
-    websocketClient: 'http://xos-rest-gw:3000'
+    apiEndpoint: 'http://xos.dev:3000/api',
+    websocketClient: 'http://xos.dev:3000'
 };
diff --git a/src/app/core/login/login.ts b/src/app/core/login/login.ts
index 51dc7c9..e5881dd 100644
--- a/src/app/core/login/login.ts
+++ b/src/app/core/login/login.ts
@@ -1,4 +1,4 @@
-import {AuthService} from '../../rest/auth.rest';
+import {AuthService} from '../../datasources/rest/auth.rest';
 
 class LoginCtrl {
   static $inject = ['AuthService', '$state'];
diff --git a/src/app/core/table/table.scss b/src/app/core/table/table.scss
new file mode 100644
index 0000000..fbf6ee7
--- /dev/null
+++ b/src/app/core/table/table.scss
@@ -0,0 +1,41 @@
+table {
+  width: 100%;
+  max-width: 100%;
+  margin-bottom: 20px;
+  border-collapse: collapse !important;
+  background: darken(grey, 20);
+  border: 1px solid darken(grey, 35);
+
+  td, th {
+  }
+  > tbody > tr > th,
+  > tfoot > tr > th,
+  > thead > tr > td,
+  > tbody > tr > td,
+  > tfoot > tr > td {
+    padding: 8px;
+    line-height: 1.42857143;
+    vertical-align: top;
+    border-top: 1px solid #ddd;
+  }
+  > thead > tr > th {
+    vertical-align: bottom;
+    border-bottom: 2px solid #ddd;
+    text-align: left;
+    padding: 8px;
+  }
+  > caption + thead > tr:first-child > th,
+  > colgroup + thead > tr:first-child > th,
+  > thead:first-child > tr:first-child > th,
+  > caption + thead > tr:first-child > td,
+  > colgroup + thead > tr:first-child > td,
+  > thead:first-child > tr:first-child > td {
+    border-top: 0;
+  }
+  > tbody + tbody {
+    border-top: 2px solid #ddd;
+  }
+  .table .table {
+    background-color: #fff;
+  }
+}
\ No newline at end of file
diff --git a/src/app/core/table/table.ts b/src/app/core/table/table.ts
index 43360c6..a837600 100644
--- a/src/app/core/table/table.ts
+++ b/src/app/core/table/table.ts
@@ -1,6 +1,7 @@
 // TODO fininsh to import all methods from https://github.com/opencord/ng-xos-lib/blob/master/src/ui_components/dumbComponents/table/table.component.js
 // TODO import tests
 
+import './table.scss';
 import * as _ from 'lodash';
 
 interface IXosTableCgfOrder {
@@ -19,8 +20,6 @@
   public columns: any[];
   public orderBy: string;
   public reverse: boolean;
-
-  private data: any[];
   private config: IXosTableCfg;
 
 
@@ -34,13 +33,13 @@
     }
 
     // handle default ordering
-    if (this.config.order && angular.isObject(this.config.order)){
+    if (this.config.order && angular.isObject(this.config.order)) {
       this.reverse = this.config.order.reverse || false;
       this.orderBy = this.config.order.field || 'id';
     }
 
     // if columns with type 'custom' are provided
-    // check that a custom formatte3 is provided too
+    // check that a custom formatter is provided too
     let customCols = _.filter(this.config.columns, {type: 'custom'});
     if (angular.isArray(customCols) && customCols.length > 0) {
       _.forEach(customCols, (col) => {
@@ -51,7 +50,7 @@
     }
 
     // if columns with type 'icon' are provided
-    // check that a custom formatte3 is provided too
+    // check that a custom formatter is provided too
     let iconCols = _.filter(this.config.columns, {type: 'icon'});
     if (angular.isArray(iconCols) && iconCols.length > 0) {
       _.forEach(iconCols, (col) => {
diff --git a/src/app/datasources/helpers/store.helpers.ts b/src/app/datasources/helpers/store.helpers.ts
new file mode 100644
index 0000000..dfb1e7a
--- /dev/null
+++ b/src/app/datasources/helpers/store.helpers.ts
@@ -0,0 +1,34 @@
+import {BehaviorSubject} from 'rxjs';
+import * as _ from 'lodash';
+import {IWSEvent} from '../websocket/global';
+
+export interface IStoreHelpersService {
+  updateCollection(event: IWSEvent, subject: BehaviorSubject<any>): BehaviorSubject<any>;
+}
+
+export class StoreHelpers {
+  public updateCollection(event: IWSEvent, subject: BehaviorSubject<any>): BehaviorSubject<any> {
+    const collection: any[] = subject.value;
+    const index: number = _.findIndex(collection, (i) => {
+      return i.id === event.msg.object.id;
+    });
+    const exist: boolean = index > -1;
+    const isDeleted: boolean = _.includes(event.msg.changed_fields, 'deleted');
+       // remove
+    if (exist && isDeleted) {
+       _.remove(collection, {id: event.msg.object.id});
+     }
+    // Replace item at index using native splice
+    else if (exist && !isDeleted) {
+       collection.splice(index, 1, event.msg.object);
+     }
+    // if the element is not deleted add it
+    else if (!exist && !isDeleted) {
+      collection.push(event.msg.object);
+     }
+
+    subject.next(collection);
+
+    return subject;
+    }
+}
diff --git a/src/app/datasources/index.ts b/src/app/datasources/index.ts
new file mode 100644
index 0000000..4473541
--- /dev/null
+++ b/src/app/datasources/index.ts
@@ -0,0 +1,20 @@
+import {CoreRest} from './rest/core.rest';
+import {SlicesRest} from './rest/slices.rest';
+import {AuthService} from './rest/auth.rest';
+import {WebSocketEvent} from './websocket/global';
+import {SliceStore} from './stores/slices.store';
+import {StoreHelpers} from './helpers/store.helpers';
+
+export const xosRest = 'xosDataSources';
+
+angular
+  .module('xosDataSources', ['ngCookies'])
+  .service('CoreRest', CoreRest)
+  .service('SlicesRest', SlicesRest)
+  .service('AuthService', AuthService)
+  .service('WebSocket', WebSocketEvent);
+
+angular
+  .module('xosDataSources')
+  .service('StoreHelpers', StoreHelpers)
+  .service('SlicesStore', SliceStore);
diff --git a/src/app/rest/auth.rest.ts b/src/app/datasources/rest/auth.rest.ts
similarity index 95%
rename from src/app/rest/auth.rest.ts
rename to src/app/datasources/rest/auth.rest.ts
index c72b51e..a94599a 100644
--- a/src/app/rest/auth.rest.ts
+++ b/src/app/datasources/rest/auth.rest.ts
@@ -1,4 +1,4 @@
-import {AppConfig} from '../config/app.config';
+import {AppConfig} from '../../config/app.config';
 import IHttpPromiseCallbackArg = angular.IHttpPromiseCallbackArg;
 export interface IAuthRequestData {
   username: string;
diff --git a/src/app/rest/core.rest.ts b/src/app/datasources/rest/core.rest.ts
similarity index 87%
rename from src/app/rest/core.rest.ts
rename to src/app/datasources/rest/core.rest.ts
index aace3aa..2c10e10 100644
--- a/src/app/rest/core.rest.ts
+++ b/src/app/datasources/rest/core.rest.ts
@@ -1,4 +1,4 @@
-import {AppConfig} from '../config/app.config';
+import {AppConfig} from '../../config/app.config';
 export class CoreRest {
 
   /** @ngInject */
diff --git a/src/app/datasources/rest/slices.rest.ts b/src/app/datasources/rest/slices.rest.ts
new file mode 100644
index 0000000..a5b7e5c
--- /dev/null
+++ b/src/app/datasources/rest/slices.rest.ts
@@ -0,0 +1,21 @@
+import {AppConfig} from '../../config/app.config';
+
+export interface IXosResourceService {
+  getResource(): ng.resource.IResourceClass<any>;
+}
+
+export class SlicesRest implements IXosResourceService {
+  static $inject = ['$resource'];
+  private resource: angular.resource.IResourceClass<any>;
+
+  /** @ngInject */
+  constructor(
+    private $resource: ng.resource.IResourceService
+  ) {
+    this.resource = this.$resource(`${AppConfig.apiEndpoint}/core/slices/`);
+  }
+
+  public getResource(): ng.resource.IResourceClass<ng.resource.IResource<any>> {
+    return this.resource;
+  }
+}
diff --git a/src/app/datasources/stores/slices.store.ts b/src/app/datasources/stores/slices.store.ts
new file mode 100644
index 0000000..9c3389f
--- /dev/null
+++ b/src/app/datasources/stores/slices.store.ts
@@ -0,0 +1,43 @@
+/// <reference path="../../../../typings/index.d.ts"/>
+
+import {BehaviorSubject, Observable} from 'rxjs/Rx';
+import {IWSEvent, IWSEventService} from '../websocket/global';
+import {IXosResourceService} from '../rest/slices.rest';
+import {IStoreHelpersService} from '../helpers/store.helpers';
+
+export interface  IStoreService {
+  query(): Observable<any>;
+}
+
+export class SliceStore {
+  static $inject = ['WebSocket', 'StoreHelpers', 'SlicesRest'];
+  private _slices: BehaviorSubject<any[]> = new BehaviorSubject([]);
+  constructor(
+    private webSocket: IWSEventService,
+    private storeHelpers: IStoreHelpersService,
+    private sliceService: IXosResourceService
+  ) {
+    this.loadInitialData();
+    this.webSocket.list()
+      .filter((e: IWSEvent) => e.model === 'Slice')
+      .subscribe(
+        (event: IWSEvent) => {
+          this.storeHelpers.updateCollection(event, this._slices);
+        }
+      );
+  }
+
+  query() {
+    return this._slices.asObservable();
+  }
+
+  private loadInitialData() {
+    this.sliceService.getResource().query().$promise
+      .then(
+        res => {
+          this._slices.next(res);
+        },
+        err => console.log('Error retrieving Slices', err)
+      );
+  }
+}
diff --git a/src/app/datasources/websocket/global.ts b/src/app/datasources/websocket/global.ts
new file mode 100644
index 0000000..aaf142a
--- /dev/null
+++ b/src/app/datasources/websocket/global.ts
@@ -0,0 +1,31 @@
+import * as io from 'socket.io-client';
+import {Subject, Observable} from 'rxjs/Rx';
+import {AppConfig} from '../../config/app.config';
+
+export interface IWSEvent {
+  model: string;
+  msg: {
+    changed_fields: string[],
+    object?: any,
+    pk?: number
+  };
+}
+
+export interface IWSEventService {
+  list(): Observable<IWSEvent>;
+}
+
+export class WebSocketEvent {
+  private _events: Subject<IWSEvent> = new Subject<IWSEvent>();
+    private socket;
+    constructor() {
+      console.log('socket.io');
+      this.socket = io(AppConfig.websocketClient);
+      this.socket.on('event', (data: IWSEvent): void => {
+          this._events.next(data);
+        });
+    }
+    list() {
+      return this._events.asObservable();
+    }
+}
diff --git a/src/app/rest/index.ts b/src/app/rest/index.ts
deleted file mode 100644
index d28084b..0000000
--- a/src/app/rest/index.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import {CoreRest} from './core.rest';
-import {SlicesRest} from './slices.rest';
-import {AuthService} from './auth.rest';
-
-export const xosRest = 'xosRest';
-
-angular
-  .module('xosRest', ['ngCookies'])
-  .service('CoreRest', CoreRest)
-  .service('SlicesRest', SlicesRest)
-  .service('AuthService', AuthService);
diff --git a/src/app/rest/slices.rest.ts b/src/app/rest/slices.rest.ts
deleted file mode 100644
index 0d1d8a1..0000000
--- a/src/app/rest/slices.rest.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import {AppConfig} from '../config/app.config';
-
-export interface IXosResourceService {
-  getResource(): ng.resource.IResourceClass<any>;
-}
-
-export class SlicesRest implements IXosResourceService{
-  static $inject = ['$resource'];
-
-  /** @ngInject */
-  constructor(
-    private $resource: ng.resource.IResourceService
-  ) {
-
-  }
-
-  public getResource(): ng.resource.IResourceClass<ng.resource.IResource<any>> {
-    return this.$resource(`${AppConfig.apiEndpoint}/core/slices/`);
-  }
-}
diff --git a/src/app/views/crud/crud.ts b/src/app/views/crud/crud.ts
index b35c104..853669c 100644
--- a/src/app/views/crud/crud.ts
+++ b/src/app/views/crud/crud.ts
@@ -1,37 +1,42 @@
-import {IXosResourceService} from '../../rest/slices.rest';
 import {IXosTableCfg} from '../../core/table/table';
+import {IStoreService} from '../../datasources/stores/slices.store';
 export interface IXosCrudData {
   title: string;
-  resource: string;
+  store: string;
   xosTableCfg: IXosTableCfg;
 }
 
 class CrudController {
-  static $inject = ['$state', '$injector'];
+  // TODO dynamically inject store
+  static $inject = ['$state', '$injector', '$scope'];
 
   public data: IXosCrudData;
   public tableCfg: IXosTableCfg;
   public title: string;
-  public resourceName: string;
-  public resource: ng.resource.IResourceClass<ng.resource.IResource<any>>;
+  public storeName: string;
+  public store: IStoreService;
   public tableData: any[];
 
   constructor(
     private $state: angular.ui.IStateService,
-    private $injector: angular.Injectable<any>
+    private $injector: angular.Injectable<any>,
+    private $scope: angular.IScope
   ) {
     this.data = this.$state.current.data;
-    console.log('xosCrud', this.data);
     this.tableCfg = this.data.xosTableCfg;
     this.title = this.data.title;
-    this.resourceName = this.data.resource;
-    this.resource = this.$injector.get(this.resourceName).getResource();
+    this.storeName = this.data.store;
+    this.store = this.$injector.get(this.storeName);
 
-    this.resource
-      .query().$promise
-      .then(res => {
-        this.tableData = res;
-      });
+    this.store.query()
+      .subscribe(
+        (event) => {
+          // NOTE Observable mess with $digest cycles, we need to schedule the expression later
+          $scope.$evalAsync(() => {
+            this.tableData = event;
+          });
+        }
+      );
   }
 }