Added instructions and tests

Change-Id: I18e491c4a0c188866dcad1f9db52c4f781054e62
diff --git a/views/ngXosViews/UITutorial/.eslintrc b/views/ngXosViews/UITutorial/.eslintrc
index c852748..a5bbd02 100644
--- a/views/ngXosViews/UITutorial/.eslintrc
+++ b/views/ngXosViews/UITutorial/.eslintrc
@@ -21,10 +21,10 @@
         "eqeqeq": [2, "smart"],
         "no-alert": 1,
         "key-spacing": [1, { "beforeColon": false, "afterColon": true }],
-        "indent": [2, 2],
+        "indent": [2, 2, { "SwitchCase": 1 }],
         "no-irregular-whitespace": 1,
         "eol-last": 0,
-        "max-nested-callbacks": [2, 4],
+        "max-nested-callbacks": [1, 5],
         "comma-spacing": [1, {"before": false, "after": true}],
         "no-trailing-spaces": [1, { skipBlankLines: true }],
         "no-unused-vars": [1, {"vars": "all", "args": "after-used"}],
diff --git a/views/ngXosViews/UITutorial/spec/errorHandler.test.js b/views/ngXosViews/UITutorial/spec/errorHandler.test.js
new file mode 100644
index 0000000..3f46330
--- /dev/null
+++ b/views/ngXosViews/UITutorial/spec/errorHandler.test.js
@@ -0,0 +1,22 @@
+'use strict';
+
+describe('The ErrorHandler service', () => {
+  
+  var ErrorHandler, done;
+
+  beforeEach(module('xos.UITutorial'));
+  beforeEach(module('templates'));
+
+  beforeEach(inject(function (_ErrorHandler_) {
+    // The injector unwraps the underscores (_) from around the parameter names when matching
+    ErrorHandler = _ErrorHandler_;
+    done = jasmine.createSpy('done');
+  }));
+
+  describe('the print method', () => {
+    it('should return an html template', () => {
+      ErrorHandler.print('myError', done);
+      expect(done).toHaveBeenCalledWith(`<span class="error">[ERROR] myError</span>`)
+    });
+  });
+});
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/spec/exploreCmd.test.js b/views/ngXosViews/UITutorial/spec/exploreCmd.test.js
new file mode 100644
index 0000000..a5d7cfe
--- /dev/null
+++ b/views/ngXosViews/UITutorial/spec/exploreCmd.test.js
@@ -0,0 +1,197 @@
+'use strict';
+
+describe('The ExploreCmd service', () => {
+  
+  var ExploreCmd, ErrorHandler, ResponseHandler, done, shell, ResourceMock, rootScope;
+
+  beforeEach(module('xos.UITutorial'));
+  beforeEach(module('templates'));
+
+  beforeEach(() => {
+    module(function ($provide) {
+      $provide.value('Resource', ResourceMock);
+    })
+  });
+
+  beforeEach(inject(function (_ExploreCmd_, _ErrorHandler_, _ResponseHandler_, $rootScope) {
+    // The injector unwraps the underscores (_) from around the parameter names when matching
+    ExploreCmd = _ExploreCmd_;
+    ErrorHandler = _ErrorHandler_;
+    ResponseHandler = _ResponseHandler_;
+    rootScope = $rootScope;
+    done = jasmine.createSpy('done');
+    shell = {
+      setCommandHandler: jasmine.createSpy('setCommandHandler'),
+      bestMatch: jasmine.createSpy('bestMatch')
+    };
+    // binding the mock shell to the service (for easy testing)
+    ExploreCmd.shell = shell;
+    spyOn(console, 'error');
+
+    ResourceMock = {
+      query: jasmine.createSpy('query').and.callFake(() => {
+        var deferred = $q.defer();
+        deferred.resolve('Remote call result');
+        return {$promise: deferred.promise};
+      })
+    };
+  }));
+
+  it('should set the resouce command handler', () => {
+    ExploreCmd.setup(shell);
+    expect(shell.setCommandHandler).toHaveBeenCalledWith('resource', { exec: ExploreCmd.resourceExec, completion: ExploreCmd.resourceCompletion })
+  });
+
+  describe('the resourceCompletion function', () => {
+
+    beforeEach(() => {
+      spyOn(ExploreCmd, 'getAvailableResources').and.returnValue(['Sites', 'Slices']);
+    });
+
+    it('should suggest a resource list', () => {
+      ExploreCmd.resourceCompletion('resource', '', {text: 'resource'}, done)
+      expect(shell.bestMatch).toHaveBeenCalledWith('', [ 'list', 'Sites', 'Slices' ]);
+
+      ExploreCmd.resourceCompletion('resource', 'S', {text: 'resource S'}, done)
+      expect(shell.bestMatch).toHaveBeenCalledWith('S', [ 'list', 'Sites', 'Slices' ]);
+    });
+
+    it('should suggest a method list', () => {
+      ExploreCmd.resourceCompletion('resource', '', {text: 'resource Sites '}, done)
+      expect(shell.bestMatch).toHaveBeenCalledWith('', [ 'query', 'get', 'save', '$save', 'delete' ]);
+
+      ExploreCmd.resourceCompletion('resource', 'q', {text: 'resource Sites q'}, done)
+      expect(shell.bestMatch).toHaveBeenCalledWith('q', [ 'query', 'get', 'save', '$save', 'delete' ]);
+    });
+  });
+
+  describe('the resourceExec function', () => {
+
+    beforeEach(() => {
+      spyOn(ExploreCmd, 'listAvailableResources');
+      spyOn(ExploreCmd, 'consumeResource');
+    });
+
+    it('should list available resources', () => {
+      ExploreCmd.resourceExec('explore', ['list'], done);
+      expect(ExploreCmd.listAvailableResources).toHaveBeenCalledWith(done);
+    });
+
+    it('should use a resource', () => {
+      ExploreCmd.resourceExec('explore', ['Resource', 'query'], done);
+      expect(ExploreCmd.consumeResource).toHaveBeenCalledWith('Resource', 'query', [], done);
+    });
+  });
+
+  describe('the getAvailableResources function', () => {
+
+    beforeEach(() => {
+      spyOn(angular, 'module').and.returnValue({
+        _invokeQueue: [
+          ['$provide', 'service', ['Sites', ['$resource']]],
+          ['$provide', 'service', ['Slices', ['$q', '$resource']]],
+          ['$provide', 'factory', ['_', []]],
+          ['$provide', 'service', ['helper', ['Slices']]]
+        ]
+      });
+    });
+
+    it('should return a list of resources in the angular app', () => {
+      const resources = ExploreCmd.getAvailableResources();
+      expect(resources).toEqual(['Sites', 'Slices']);
+    });
+  });
+
+  describe('the listAvailableResources function', () => {
+    beforeEach(() => {
+      spyOn(ExploreCmd, 'getAvailableResources').and.returnValue(['Sites', 'Slices']);
+    });
+
+    it('should format resource in an html template', () => {
+      ExploreCmd.listAvailableResources(done);
+      expect(ExploreCmd.getAvailableResources).toHaveBeenCalled();
+      expect(done).toHaveBeenCalledWith(`Sites<br/>Slices<br/>`);
+    });
+  });
+
+  describe('the consumeResource function', () => {
+    beforeEach(() => {
+      spyOn(ExploreCmd, 'getAvailableResources').and.returnValue(['Resource', 'Fake']);
+      spyOn(ErrorHandler, 'print');
+    });
+
+    it('should notify that a resource does not exists', () => {
+      ExploreCmd.consumeResource('Test', null, null, done);
+      expect(ErrorHandler.print).toHaveBeenCalledWith(`Resource "Test" does not exists`, done)
+    });
+
+    it('should notify that a method does not exists', () => {
+      ExploreCmd.consumeResource('Resource', 'test', null, done);
+      expect(ErrorHandler.print).toHaveBeenCalledWith(`Method "test" not allowed`, done)
+    });
+
+    const methodsWithParams = ['get', '$save', 'delete'];
+    methodsWithParams.forEach(method => {
+      it(`should notify that the ${method} method require parameters`, () => {
+        ExploreCmd.consumeResource('Resource', method, [], done);
+        expect(ErrorHandler.print).toHaveBeenCalledWith(`Method "${method}" require parameters`, done)
+      });
+    });
+
+    it('should not accept id as parameter for the query method', () => {
+      ExploreCmd.consumeResource('Resource', 'query', ['{id:1}'], done);
+      expect(ErrorHandler.print).toHaveBeenCalledWith(`Is not possible to use "id" as filter in method "query", use "get" instead!`, done)
+    });
+
+    it('should notify the user in case of malformed parameters', () => {
+      ExploreCmd.consumeResource('Resource', 'query', ['{child: 3}'], done);
+      expect(ErrorHandler.print).toHaveBeenCalledWith(`Parameter is not valid, it shoudl be in the form of: <code>{id:1}</code>, with no spaces`, done)
+      pending();
+    });
+
+    describe('when called with the correct parameters', () => {
+
+      let deferred;
+      beforeEach(inject(($q) => {
+        spyOn(ResponseHandler, 'parse');
+
+        deferred = $q.defer();
+        ResourceMock = {
+          query: jasmine.createSpy('query').and.callFake(function(){
+            return {$promise: deferred.promise};
+          })
+        };
+      }));
+
+      it('should notify the user if an error occurred while loading the resource', () => {
+        ExploreCmd.consumeResource('Fake', 'query', [], done);
+        expect(console.error).toHaveBeenCalled();
+        expect(ErrorHandler.print).toHaveBeenCalledWith('Failed to inject resource "Fake"', done);
+      });
+
+      it('should call a resource and return the results', () => {
+        ExploreCmd.consumeResource('Resource', 'query', [], done);
+        deferred.resolve([]);
+        rootScope.$apply();
+        expect(ErrorHandler.print).not.toHaveBeenCalled()
+        expect(ResponseHandler.parse).toHaveBeenCalledWith([], 'Resource.query()', done);
+      });
+
+      it('should call a resource with parameters and return the results', () => {
+        ExploreCmd.consumeResource('Resource', 'query', ['{child:3}'], done);
+        deferred.resolve([]);
+        rootScope.$apply();
+        expect(ErrorHandler.print).not.toHaveBeenCalled()
+        expect(ResponseHandler.parse).toHaveBeenCalledWith([], 'Resource.query({"child":3})', done);
+      });
+
+      it('should call a resource and display a not found message', () => {
+        ExploreCmd.consumeResource('Resource', 'query', [], done);
+        deferred.reject({status: 404, data: {detail: 'Not Found.'}});
+        rootScope.$apply();
+        expect(ErrorHandler.print).toHaveBeenCalledWith('Resource with method "query" and parameters {} Not Found.', done)
+        expect(ResponseHandler.parse).not.toHaveBeenCalled();
+      });
+    });
+  });
+});
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/spec/responseHandler.test.js b/views/ngXosViews/UITutorial/spec/responseHandler.test.js
new file mode 100644
index 0000000..4ce26be
--- /dev/null
+++ b/views/ngXosViews/UITutorial/spec/responseHandler.test.js
@@ -0,0 +1,46 @@
+'use strict';
+
+describe('The ResponseHandler service', () => {
+  
+  var ResponseHandler, done;
+
+  beforeEach(module('xos.UITutorial'));
+  beforeEach(module('templates'));
+
+  beforeEach(inject(function (_ResponseHandler_) {
+    // The injector unwraps the underscores (_) from around the parameter names when matching
+    ResponseHandler = _ResponseHandler_;
+    done = jasmine.createSpy('done');
+  }));
+
+  describe('the parse method', () => {
+    it('should return an html template for a collection', () => {
+      const collection = [
+        {id: 1, deleted: true, name: 'one'},
+        {id: 2, deleted: true, name: 'two'}
+      ];
+
+      const collectionHtml = `
+      <div>
+        <p>Corresponding js code: <code>jsCode</code></p>
+        <div class="json"><div class="jsonCollection">[<div class="jsonObject">{"id":1,"name":"one"},</code></div><div class="jsonObject">{"id":2,"name":"two"}</code></div>]</div></div>
+      </div>
+    `;
+      ResponseHandler.parse(collection, 'jsCode', done);
+      expect(done).toHaveBeenCalledWith(collectionHtml)
+    });
+
+    it('should return an html template for an object', () => {
+      const object = {id: 1, deleted: true, name: 'one'};
+
+      const objectHtml = `
+      <div>
+        <p>Corresponding js code: <code></code></p>
+        <div class="json"><div class="jsonObject">{"id":1,"name":"one"}</code></div></div>
+      </div>
+    `;
+      ResponseHandler.parse(object, '', done);
+      expect(done).toHaveBeenCalledWith(objectHtml)
+    });
+  });
+});
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/spec/sample.test.js b/views/ngXosViews/UITutorial/spec/sample.test.js
deleted file mode 100644
index 9e16127..0000000
--- a/views/ngXosViews/UITutorial/spec/sample.test.js
+++ /dev/null
@@ -1,37 +0,0 @@
-'use strict';
-
-describe('The User List', () => {
-  
-  var scope, element, isolatedScope, httpBackend;
-
-  beforeEach(module('xos.UITutorial'));
-  beforeEach(module('templates'));
-
-  beforeEach(inject(function($httpBackend, $compile, $rootScope){
-    
-    httpBackend = $httpBackend;
-    // Setting up mock request
-    $httpBackend.expectGET('/api/core/users/?no_hyperlinks=1').respond([
-      {
-        email: 'matteo.scandolo@gmail.com',
-        firstname: 'Matteo',
-        lastname: 'Scandolo' 
-      }
-    ]);
-  
-    scope = $rootScope.$new();
-    element = angular.element('<users-list></users-list>');
-    $compile(element)(scope);
-    scope.$digest();
-    isolatedScope = element.isolateScope().vm;
-  }));
-
-  xit('should load 1 users', () => {
-    httpBackend.flush();
-    expect(isolatedScope.users.length).toBe(1);
-    expect(isolatedScope.users[0].email).toEqual('matteo.scandolo@gmail.com');
-    expect(isolatedScope.users[0].firstname).toEqual('Matteo');
-    expect(isolatedScope.users[0].lastname).toEqual('Scandolo');
-  });
-
-});
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/spec/shell.test.js b/views/ngXosViews/UITutorial/spec/shell.test.js
new file mode 100644
index 0000000..19ea63e
--- /dev/null
+++ b/views/ngXosViews/UITutorial/spec/shell.test.js
@@ -0,0 +1,29 @@
+'use strict';
+
+describe('The Js Shell directive', () => {
+  
+  var scope, element, isolatedScope, shellSpy;
+
+  beforeEach(module('xos.UITutorial'));
+  beforeEach(module('templates'));
+
+  beforeEach(inject(function($compile, $rootScope){
+    scope = $rootScope.$new();
+    element = angular.element('<js-shell></js-shell>');
+    $compile(element)(scope);
+    scope.$digest();
+    isolatedScope = element.isolateScope().vm;
+    spyOn(isolatedScope.shell, 'setCommandHandler');
+    spyOn(isolatedScope.shell, 'activate');
+  }));
+
+  // NOTE see http://stackoverflow.com/questions/38906605/angular-jasmine-testing-immediatly-invoked-functions-inside-a-directive-contr
+
+  xit('should register the explore command', () => {
+    expect(isolatedScope.shell.setCommandHandler).toHaveBeenCalled();
+  });
+
+  xit('should activate the shell', () => {
+    expect(isolatedScope.shell.activate).toHaveBeenCalled();
+  });
+});
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/spec/templateHandler.test.js b/views/ngXosViews/UITutorial/spec/templateHandler.test.js
new file mode 100644
index 0000000..0af68b9
--- /dev/null
+++ b/views/ngXosViews/UITutorial/spec/templateHandler.test.js
@@ -0,0 +1,22 @@
+'use strict';
+
+describe('The TemplateHandler service', () => {
+  
+  var TemplateHandler;
+
+  beforeEach(module('xos.UITutorial'));
+  beforeEach(module('templates'));
+
+  beforeEach(inject(function (_TemplateHandler_) {
+    TemplateHandler = _TemplateHandler_;
+  }));
+
+  const templates = ['error', 'instructions', 'resourcesResponse'];
+
+  templates.forEach(t => {
+    it(`should have a ${t} template`, () => {
+      expect(TemplateHandler[t]).toBeDefined();
+      expect(angular.isFunction(TemplateHandler[t])).toBeTruthy();
+    });
+  });
+});
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/src/css/main.css b/views/ngXosViews/UITutorial/src/css/main.css
index 3684934..d008cb5 100644
--- a/views/ngXosViews/UITutorial/src/css/main.css
+++ b/views/ngXosViews/UITutorial/src/css/main.css
@@ -7,14 +7,19 @@
       height: 400px;
       background: #002b36;
       padding: 10px;
-      overflow: scroll; }
+      overflow: scroll;
+      color: #93a1a1; }
       #xosUITutorial js-shell #shell-panel .prompt {
         color: #d33682; }
+      #xosUITutorial js-shell #shell-panel .input {
+        color: #268bd2; }
       #xosUITutorial js-shell #shell-panel .cursor {
         color: #2aa198; }
       #xosUITutorial js-shell #shell-panel .error {
         color: #dc322f; }
-      #xosUITutorial js-shell #shell-panel #shell-view {
-        color: #93a1a1; }
-        #xosUITutorial js-shell #shell-panel #shell-view > div > div {
-          color: #268bd2; }
+      #xosUITutorial js-shell #shell-panel #shell-view > div > div {
+        color: #268bd2; }
+      #xosUITutorial js-shell #shell-panel #shell-view .jsonObject {
+        white-space: nowrap; }
+      #xosUITutorial js-shell #shell-panel #shell-view .jsonCollection > .jsonObject {
+        margin-left: 10px; }
diff --git a/views/ngXosViews/UITutorial/src/index.html b/views/ngXosViews/UITutorial/src/index.html
index 6819b46..d325f9c 100644
--- a/views/ngXosViews/UITutorial/src/index.html
+++ b/views/ngXosViews/UITutorial/src/index.html
@@ -39,6 +39,7 @@
 <!-- inject:js -->
 <script src="/vendor/ng-xos-lib/dist/ngXosHelpers.min.js"></script>
 <script src="/.tmp/main.js"></script>
+<script src="/.tmp/templateHandler.js"></script>
 <script src="/.tmp/responseHandler.js"></script>
 <script src="/.tmp/exploreCmd.js"></script>
 <script src="/.tmp/errorHandler.js"></script>
diff --git a/views/ngXosViews/UITutorial/src/js/errorHandler.js b/views/ngXosViews/UITutorial/src/js/errorHandler.js
index dbe41f4..aa75d38 100644
--- a/views/ngXosViews/UITutorial/src/js/errorHandler.js
+++ b/views/ngXosViews/UITutorial/src/js/errorHandler.js
@@ -1,10 +1,9 @@
 (function () {
   'use strict';
   angular.module('xos.UITutorial')
-  .service('ErrorHandler', function(){
+  .service('ErrorHandler', function(TemplateHandler){
     this.print = (msg, done) => {
-      const errorTpl = _.template(`<span class="error">[ERROR] <%= msg %></span>`);
-      done(errorTpl({msg: msg}));
+      done(TemplateHandler.error({msg: msg}));
     };
   });
 })();
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/src/js/exploreCmd.js b/views/ngXosViews/UITutorial/src/js/exploreCmd.js
index d4bba90..0aba8fa 100644
--- a/views/ngXosViews/UITutorial/src/js/exploreCmd.js
+++ b/views/ngXosViews/UITutorial/src/js/exploreCmd.js
@@ -3,36 +3,41 @@
   angular.module('xos.UITutorial')
   .service('ExploreCmd', function($injector, ResponseHandler, ErrorHandler){
 
-    this.setup = (shell) => {
-      shell.setCommandHandler('resource', {
-        exec: (cmd, args, done) => {
-          switch(args[0]){
-            case 'list':
-              return listAvailableResources(done);
-              break;
-            default:
-              // use the resource
-              const resourceName = args.shift();
-              const method = args.shift();
-              return consumeResource(resourceName, method, args, done);
-          }
-        },
-        completion: function(cmd, arg, line, callback) {
-          const args = ['list'].concat(getAvailableResources());
-          if(line.text.match(/resource\s[A-Z][a-z]+\s/)){
-            // if arg is a resource, then return available methods
-            if(args.indexOf(arg) !== -1){
-              arg = '';
-            }
-            const methods = ['query', 'get', 'save', '$save', 'delete'];
-            return callback(shell.bestMatch(arg, methods));
-          }
-          return callback(shell.bestMatch(arg, args));
+    this.resourceExec = (cmd, args, done) => {
+      switch(args[0]){
+        case 'list':
+          return this.listAvailableResources(done);
+          break;
+        default:
+          // use the resource
+          const resourceName = args.shift();
+          const method = args.shift();
+          return this.consumeResource(resourceName, method, args, done);
+      }
+    };
+
+    this.resourceCompletion = (cmd, arg, line, done) => {
+      const args = ['list'].concat(this.getAvailableResources());
+      if(line.text.match(/resource\s[A-Z][a-z]+\s/)){
+        // if arg is a resource, then return available methods
+        if(args.indexOf(arg) !== -1){
+          arg = '';
         }
+        const methods = ['query', 'get', 'save', '$save', 'delete'];
+        return done(this.shell.bestMatch(arg, methods));
+      }
+      return done(this.shell.bestMatch(arg, args));
+    };
+
+    this.setup = (shell) => {
+      this.shell = shell;
+      shell.setCommandHandler('resource', {
+        exec: this.resourceExec,
+        completion: this.resourceCompletion
       });
     };
 
-    const getAvailableResources = () => {
+    this.getAvailableResources = () => {
       return angular.module('xos.helpers')._invokeQueue
           .filter((d) => {
             if(d[1] !== 'service'){
@@ -44,17 +49,18 @@
           .reduce((list, i) => list.concat([i[2][0]]), []);
     }
 
-    const listAvailableResources = (done) => {
-      const resources = getAvailableResources()
+    this.listAvailableResources = (done) => {
+      // TODO use a template
+      const resources = this.getAvailableResources()
           .reduce((html, i) => `${html}${i}<br/>`, '');
       done(resources);
     }
 
-    const consumeResource = (resourceName, method, args, done) => {
+    this.consumeResource = (resourceName, method, args, done) => {
 
       // TODO if not resourceName/method print cmd instructions
 
-      if(getAvailableResources().indexOf(resourceName) === -1){
+      if(this.getAvailableResources().indexOf(resourceName) === -1){
         return ErrorHandler.print(`Resource "${resourceName}" does not exists`, done);
       }
 
@@ -62,36 +68,43 @@
         return ErrorHandler.print(`Method "${method}" not allowed`, done);
       }
 
+      // TODO @Teo if get/delete check for arguments
+      let params = {};
+
+      // if the method require arguments checks for them
+      if(['get', '$save', 'delete'].indexOf(method) !== -1){
+        if(args.length === 0){
+          return ErrorHandler.print(`Method "${method}" require parameters`, done);
+        }
+      }
+
+      // if there are arguments parse them
+      // TODO wrap in a try catch, we have no guarantee that a user insert the correct params
+      if(args.length > 0){
+        params = eval(`(${args[0]})`);
+      }
+
+      // if it is a query is not possible to use id as parameter
+      if(method === 'query' && angular.isDefined(params.id)){
+        return ErrorHandler.print(`Is not possible to use "id" as filter in method "${method}", use "get" instead!`, done);
+      }
+
       let Resource;
       try{
         Resource = $injector.get(resourceName);
-
-        // TODO @Teo if get/delete check for arguments
-        let params = {};
-
-        // if the method require arguments checks for them
-        if(['get', '$save', 'delete'].indexOf(method) !== -1){
-          if(args.length === 0){
-            return ErrorHandler.print(`Method "${method}" require parameters`, done);
-          }
-        }
-
-        // if there are arguments parse them
-        if(args.length > 0){
-          params = eval(`(${args[0]})`);
-        }
-
-        // if it is a query is not possible to use id as parameter
-        if(method === 'query' && angular.isDefined(params.id)){
-          return ErrorHandler.print(`Is not possible to use "id" as filter in method "${method}", use "get" instead!`, done);
-        }
-
         Resource[method](params).$promise
         .then(res => {
-          return ResponseHandler.parse(res, done);
+          const jsCode = `${resourceName}.${method}(${Object.keys(params).length > 0 ? JSON.stringify(params): ''})`;
+          return ResponseHandler.parse(res, jsCode, done);
+        })
+        .catch(e => {
+          if(e.status === 404){
+            return ErrorHandler.print(`${resourceName} with method "${method}" and parameters ${JSON.stringify(params)} ${e.data.detail}`, done);
+          }
         });
       }
       catch(e){
+        console.error(e);
         return ErrorHandler.print(`Failed to inject resource "${resourceName}"`, done);
       }
     };
diff --git a/views/ngXosViews/UITutorial/src/js/main.js b/views/ngXosViews/UITutorial/src/js/main.js
index bc5c27e..c89f1da 100644
--- a/views/ngXosViews/UITutorial/src/js/main.js
+++ b/views/ngXosViews/UITutorial/src/js/main.js
@@ -16,7 +16,7 @@
 .config(function($httpProvider){
   $httpProvider.interceptors.push('NoHyperlinks');
 })
-.directive('jsShell', function(){
+.directive('jsShell', function(TemplateHandler){
   return {
     restrict: 'E',
     scope: {},
@@ -24,23 +24,28 @@
     controllerAs: 'vm',
     templateUrl: 'templates/js-shell.tpl.html',
     controller: function(ExploreCmd){
-      var history = new Josh.History({ key: 'helloworld.history'});
-      var shell = Josh.Shell({history: history});
+      var history = new Josh.History({ key: 'jsshell.history'});
+      this.shell = Josh.Shell({history: history});
 
-      shell.onNewPrompt(function(done) {
+      this.shell.onNewPrompt(done => {
         done('[ngXosLib] $ ');
       });
 
-      shell.setCommandHandler('explore', {
+      this.shell.setCommandHandler('explore', {
         exec: (cmd, args, done) => {
-          ExploreCmd.setup(shell);
-          done();
+          ExploreCmd.setup(this.shell);
+          done(TemplateHandler.instructions({
+            title: `You can now explore the API use angular $resouces!`,
+            messages: [
+              `Use <code>resource list</code> to list all the available resources and <code>resource {resoureName} {method} {?paramters}</code> to call the API.`,
+              `An example command is <code>resource Slices query</code>`,
+              `You can also provide paramters with <code>resource Slices query {max_instances: 10}</code>`
+            ]
+          }));
         }
       });
 
-
-
-      shell.activate();
+      this.shell.activate();
     }
   };
 });
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/src/js/responseHandler.js b/views/ngXosViews/UITutorial/src/js/responseHandler.js
index 801129e..e343dda 100644
--- a/views/ngXosViews/UITutorial/src/js/responseHandler.js
+++ b/views/ngXosViews/UITutorial/src/js/responseHandler.js
@@ -1,27 +1,47 @@
 (function () {
   'use strict';
   angular.module('xos.UITutorial')
-  .service('ResponseHandler', function(){
-    this.parse = (res, done) => {
-      var compiled = _.template('<div><pre><%- JSON.stringify(val,null,1) %></div></pre>');
-      var compiledArray = _.template('<% _.forEach(valueArr, function(item) { %><div><pre><%- JSON.stringify(item) %></pre></div><%}); %>');
-      var resFunc = function (res) {
-        let retVar;
-        let exclude = ['deleted','enabled','enacted','exposed_ports','lazy_blocked','created','validators','controllers','backend_status','backend_register','policed','no_policy','write_protect','no_sync','updated'];
-        if(_.isArray(res)) {
-          retVar = [];
-          retVar = _.map(res, (o)=> {
-            return _.omit(o, exclude);
-          });
-          retVar = compiledArray({'valueArr':retVar});
-        }
-        else{
-          retVar = _.omit(res,exclude);
-          retVar = compiled({'val':retVar} );
-        }
-        return retVar;
+  .service('ResponseHandler', function(TemplateHandler){
+
+    const exclude = [
+      'deleted',
+      'enabled',
+      'enacted',
+      'exposed_ports',
+      'lazy_blocked',
+      'created',
+      'validators',
+      'controllers',
+      'backend_status',
+      'backend_register',
+      'policed',
+      'no_policy',
+      'write_protect',
+      'no_sync',
+      'updated'
+    ];
+
+    this.parseObject = (obj, comma = '') => {
+      obj = _.omit(obj, exclude);
+      return TemplateHandler.jsonObject({'obj': obj, comma: comma});
+    };
+
+    this.parseCollection = (array) => {
+      array = array.map((o, i) => `${this.parseObject(o, i === (array.length - 1) ? '':',')}`);
+      return TemplateHandler.jsonCollection({'collection': array});
+    };
+
+    this.parse = (res, jsCode, done) => {
+      if(_.isArray(res)) {
+        res = this.parseCollection(res);
       }
-      done( resFunc(res));
+      else{
+        res = this.parseObject(res);
+      }
+      done(TemplateHandler.resourcesResponse({
+        jsCode: jsCode,
+        res: res
+      }));
     };
   });
 })();
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/src/js/templateHandler.js b/views/ngXosViews/UITutorial/src/js/templateHandler.js
new file mode 100644
index 0000000..752b214
--- /dev/null
+++ b/views/ngXosViews/UITutorial/src/js/templateHandler.js
@@ -0,0 +1,26 @@
+(function () {
+  'use strict';
+  angular.module('xos.UITutorial')
+  .service('TemplateHandler', function(_){
+    
+    this.error = _.template(`<span class="error">[ERROR] <%= msg %></span>`);
+
+    this.instructions = _.template(`
+      <div>
+        <strong><%= title %></strong>
+        <% _.forEach(messages, function(m) { %><p><%= m %></p><% }); %>
+      </div>
+    `);
+
+    this.resourcesResponse = _.template(`
+      <div>
+        <p>Corresponding js code: <code><%= jsCode %></code></p>
+        <div class="json"><%= res %></div>
+      </div>
+    `);
+
+    this.jsonObject = _.template(`<div class="jsonObject"><%= JSON.stringify(obj) %><%=comma%></code></div>`);
+
+    this.jsonCollection = _.template(`<div class="jsonCollection">[<% _.forEach(collection, function(item) { %><%= item %><%}); %>]</div>`);
+  });
+})();
\ No newline at end of file
diff --git a/views/ngXosViews/UITutorial/src/sass/main.scss b/views/ngXosViews/UITutorial/src/sass/main.scss
index 83ea377..f3b0496 100644
--- a/views/ngXosViews/UITutorial/src/sass/main.scss
+++ b/views/ngXosViews/UITutorial/src/sass/main.scss
@@ -28,11 +28,16 @@
       background: $background;
       padding: 10px;
       overflow: scroll;
+      color: $emph;
 
       .prompt {
         color: $magenta;
       }
 
+      .input {
+        color: $blue;
+      }
+
       .cursor {
         color: $cyan;
       }
@@ -42,11 +47,20 @@
       }
 
       #shell-view {
-        color: $emph;
 
         >div>div{
           color: $blue;
         }
+
+        .jsonObject {
+          white-space: nowrap;
+        }
+
+        .jsonCollection {
+          >.jsonObject{
+            margin-left: 10px;
+          }
+        }
       }
     }
   }
diff --git a/views/ngXosViews/UITutorial/src/templates/js-shell.tpl.html b/views/ngXosViews/UITutorial/src/templates/js-shell.tpl.html
index 543006d..8799fd4 100644
--- a/views/ngXosViews/UITutorial/src/templates/js-shell.tpl.html
+++ b/views/ngXosViews/UITutorial/src/templates/js-shell.tpl.html
@@ -1,3 +1,6 @@
 <div id="shell-panel">
+  <div>
+    Type <code>help</code> or hit <code>TAB</code> for a list of commands.
+  </div>
   <div id="shell-view"></div>
 </div>
\ No newline at end of file