From 702f30f4dc61c5ca3c4747dd41170631716a414f Mon Sep 17 00:00:00 2001
From: Roberto Sánchez <roberto.sanchez@curisit.net>
Date: Wed, 08 Jan 2014 14:13:28 +0000
Subject: [PATCH] #394 feature - Added toaster for non intrusive messages

---
 securis/src/main/resources/static/admin.html               |   29 
 securis/src/main/resources/static/js/bootstrap-dialog.js   |  654 +++++++++++++++++++++++++
 securis/src/main/resources/static/js/catalogs.json         |    2 
 securis/src/main/resources/static/js/commons.js            |  224 ++++++++
 securis/src/main/resources/static/js/admin.js              |  129 +++-
 securis/src/main/resources/static/js/toaster.js            |  145 +++++
 securis/src/main/resources/static/css/toaster.css          |  207 +++++++
 securis/src/main/resources/static/css/bootstrap-dialog.css |  120 ++++
 8 files changed, 1,458 insertions(+), 52 deletions(-)

diff --git a/securis/src/main/resources/static/admin.html b/securis/src/main/resources/static/admin.html
index 5e04c00..5c6f344 100644
--- a/securis/src/main/resources/static/admin.html
+++ b/securis/src/main/resources/static/admin.html
@@ -5,12 +5,14 @@
 <base href="/">
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
-<title></title>
+<title>SeCuris</title>
 <meta name="description" content="">
 <meta name="viewport" content="width=device-width">
 
-<link rel="stylesheet"
-	href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
+<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
+<link rel="stylesheet" href="/css/bootstrap-dialog.css">
+<link rel="stylesheet" href="/css/toaster.css">
+	
 <style>
 body {
 	padding-top: 50px;
@@ -103,8 +105,6 @@
 						<ul class="nav navbar-nav">
 							<li><a ng-click="editNew()"><span class="glyphicon glyphicon-plus"></span>
 									New</a></li>
-							<li><a ng-click="edit()"><span class="glyphicon glyphicon-pencil"></span>
-									Edit</a></li>
 							<li><a ng-click="cancel()"> <span
 									class="glyphicon glyphicon-ban-circle"></span> Cancel
 							</a></li>
@@ -164,11 +164,14 @@
 						</tr>
 					</thead>
 					<tbody>
-						<tr ng-repeat="row in list | filter:searchText">
-							<td ng-repeat="field in catalogMetadata.list_fields" ng-bind="row[field] "></td>
+						<tr ng-repeat="row in list | filter:searchText" ng-dblclick="edit(row)">
+							<td ng-repeat="field in catalogMetadata.list_fields" ng-bind="print(field, row[field])"></td>
 						
-							<td><span ng-click="editRow()"
-								class="glyphicon glyphicon-pencil"></span></td>
+							<td><span ng-click="edit(row)"
+								class="glyphicon glyphicon-pencil"></span>
+								<span ng-click="delete(row)"
+								class="glyphicon glyphicon-remove"></span>
+								</td>
 						</tr>
 					</tbody>
 					<tfoot>
@@ -199,6 +202,12 @@
 		src="/js/angular-resource.min.js"></script>
 	<script type="text/javascript"
 		src="/js/angular-animate.min.js"></script>
+	<script type="text/javascript"
+		src="/js/bootstrap-dialog.js"></script>
+	<script type="text/javascript"
+		src="/js/toaster.js"></script>
+		
+
  <!-- 
 	<script
 		src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
@@ -222,5 +231,7 @@
         	});
 			
         </script>
+
+        <toaster-container toaster-options="{'time-out': 3000}"></toaster-container>
 </body>
 </html>
\ No newline at end of file
diff --git a/securis/src/main/resources/static/css/bootstrap-dialog.css b/securis/src/main/resources/static/css/bootstrap-dialog.css
new file mode 100644
index 0000000..32a001d
--- /dev/null
+++ b/securis/src/main/resources/static/css/bootstrap-dialog.css
@@ -0,0 +1,120 @@
+.bootstrap-dialog {
+
+}
+.bootstrap-dialog .modal-header {
+    border-top-left-radius: 4px;
+    border-top-right-radius: 4px;
+}
+.bootstrap-dialog .bootstrap-dialog-title {
+    color: #fff;
+    display: inline-block;
+}
+.bootstrap-dialog.type-default .bootstrap-dialog-title {
+    color: #333;
+}
+.bootstrap-dialog.size-normal .bootstrap-dialog-title {
+    font-size: 16px;
+}
+.bootstrap-dialog.size-large .bootstrap-dialog-title {
+    font-size: 24px;
+}
+.bootstrap-dialog .bootstrap-dialog-close-button {
+    float: right;
+    filter:alpha(opacity=90);
+    -moz-opacity:0.9;
+    -khtml-opacity: 0.9;
+    opacity: 0.9;
+}
+.bootstrap-dialog.size-normal .bootstrap-dialog-close-button {
+    font-size: 20px;
+}
+.bootstrap-dialog.size-large .bootstrap-dialog-close-button {
+    font-size: 30px;
+}
+.bootstrap-dialog .bootstrap-dialog-close-button:hover {
+    cursor: pointer;
+    filter: alpha(opacity=100);
+    -moz-opacity: 1;
+    -khtml-opacity: 1;
+    opacity: 1;
+}
+.bootstrap-dialog.size-normal .bootstrap-dialog-message {
+    font-size: 14px;
+}
+.bootstrap-dialog.size-large .bootstrap-dialog-message {
+    font-size: 18px;
+}
+.bootstrap-dialog.type-default .modal-header {
+    background-color: #fff;
+}
+.bootstrap-dialog.type-info .modal-header {
+    background-color: #5bc0de;
+}
+.bootstrap-dialog.type-primary .modal-header {
+    background-color: #428bca;
+}
+.bootstrap-dialog.type-success .modal-header {
+    background-color: #5cb85c;
+}
+.bootstrap-dialog.type-warning .modal-header {
+    background-color: #f0ad4e;
+}
+.bootstrap-dialog.type-danger .modal-header {
+    background-color: #d9534f;
+}
+.bootstrap-dialog .bootstrap-dialog-button-icon {
+    margin-right: 3px;
+}
+
+/**
+ * Icon animation
+ * Copied from font-awesome: http://fontawesome.io/
+ **/
+.icon-spin {
+    display: inline-block;
+    -moz-animation: spin 2s infinite linear;
+    -o-animation: spin 2s infinite linear;
+    -webkit-animation: spin 2s infinite linear;
+    animation: spin 2s infinite linear;
+}
+@-moz-keyframes spin {
+    0% {
+    -moz-transform: rotate(0deg);
+}
+100% {
+    -moz-transform: rotate(359deg);
+}
+}
+@-webkit-keyframes spin {
+    0% {
+    -webkit-transform: rotate(0deg);
+}
+100% {
+    -webkit-transform: rotate(359deg);
+}
+}
+@-o-keyframes spin {
+    0% {
+    -o-transform: rotate(0deg);
+}
+100% {
+    -o-transform: rotate(359deg);
+}
+}
+@-ms-keyframes spin {
+    0% {
+    -ms-transform: rotate(0deg);
+}
+100% {
+    -ms-transform: rotate(359deg);
+}
+}
+@keyframes spin {
+    0% {
+    transform: rotate(0deg);
+}
+100% {
+    transform: rotate(359deg);
+}
+}
+/** End of icon animation **/
\ No newline at end of file
diff --git a/securis/src/main/resources/static/css/toaster.css b/securis/src/main/resources/static/css/toaster.css
new file mode 100644
index 0000000..b2afe4b
--- /dev/null
+++ b/securis/src/main/resources/static/css/toaster.css
@@ -0,0 +1,207 @@
+/*
+ * Toastr
+ * Version 2.0.1
+ * Copyright 2012 John Papa and Hans Fj�llemark.  
+ * All Rights Reserved.  
+ * Use, reproduction, distribution, and modification of this code is subject to the terms and 
+ * conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
+ *
+ * Author: John Papa and Hans Fj�llemark
+ * Project: https://github.com/CodeSeven/toastr
+ */
+.toast-title {
+  font-weight: bold;
+}
+.toast-message {
+  -ms-word-wrap: break-word;
+  word-wrap: break-word;
+}
+.toast-message a,
+.toast-message label {
+  color: #ffffff;
+}
+.toast-message a:hover {
+  color: #cccccc;
+  text-decoration: none;
+}
+
+.toast-close-button {
+  position: relative;
+  right: -0.3em;
+  top: -0.3em;
+  float: right;
+  font-size: 20px;
+  font-weight: bold;
+  color: #ffffff;
+  -webkit-text-shadow: 0 1px 0 #ffffff;
+  text-shadow: 0 1px 0 #ffffff;
+  opacity: 0.8;
+  -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
+  filter: alpha(opacity=80);
+}
+.toast-close-button:hover,
+.toast-close-button:focus {
+  color: #000000;
+  text-decoration: none;
+  cursor: pointer;
+  opacity: 0.4;
+  -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
+  filter: alpha(opacity=40);
+}
+
+/*Additional properties for button version
+ iOS requires the button element instead of an anchor tag.
+ If you want the anchor version, it requires `href="#"`.*/
+button.toast-close-button {
+  padding: 0;
+  cursor: pointer;
+  background: transparent;
+  border: 0;
+  -webkit-appearance: none;
+}
+.toast-top-full-width {
+  top: 0;
+  right: 0;
+  width: 100%;
+}
+.toast-bottom-full-width {
+  bottom: 0;
+  right: 0;
+  width: 100%;
+}
+.toast-top-left {
+  top: 12px;
+  left: 12px;
+}
+.toast-top-right {
+  top: 12px;
+  right: 12px;
+}
+.toast-bottom-right {
+  right: 12px;
+  bottom: 12px;
+}
+.toast-bottom-left {
+  bottom: 12px;
+  left: 12px;
+}
+#toast-container {
+  position: fixed;
+  z-index: 999999;
+  /*overrides*/
+
+}
+#toast-container * {
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+}
+#toast-container > div {
+  margin: 0 0 6px;
+  padding: 15px 15px 15px 50px;
+  width: 300px;
+  -moz-border-radius: 3px 3px 3px 3px;
+  -webkit-border-radius: 3px 3px 3px 3px;
+  border-radius: 3px 3px 3px 3px;
+  background-position: 15px center;
+  background-repeat: no-repeat;
+  -moz-box-shadow: 0 0 12px #999999;
+  -webkit-box-shadow: 0 0 12px #999999;
+  box-shadow: 0 0 12px #999999;
+  color: #ffffff;
+  opacity: 0.8;
+  -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
+  filter: alpha(opacity=80);
+}
+#toast-container > :hover {
+  -moz-box-shadow: 0 0 12px #000000;
+  -webkit-box-shadow: 0 0 12px #000000;
+  box-shadow: 0 0 12px #000000;
+  opacity: 1;
+  -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
+  filter: alpha(opacity=100);
+  cursor: pointer;
+}
+#toast-container > .toast-info {
+  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important;
+}
+#toast-container > .toast-error {
+  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important;
+}
+#toast-container > .toast-success {
+  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important;
+}
+#toast-container > .toast-warning {
+  background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important;
+}
+#toast-container.toast-top-full-width > div,
+#toast-container.toast-bottom-full-width > div {
+  width: 96%;
+  margin: auto;
+}
+.toast {
+  background-color: #030303;
+}
+.toast-success {
+  background-color: #51a351;
+}
+.toast-error {
+  background-color: #bd362f;
+}
+.toast-info {
+  background-color: #2f96b4;
+}
+.toast-warning {
+  background-color: #f89406;
+}
+/*Responsive Design*/
+@media all and (max-width: 240px) {
+  #toast-container > div {
+    padding: 8px 8px 8px 50px;
+    width: 11em;
+  }
+  #toast-container .toast-close-button {
+    right: -0.2em;
+    top: -0.2em;
+}
+  }
+@media all and (min-width: 241px) and (max-width: 480px) {
+  #toast-container  > div {
+    padding: 8px 8px 8px 50px;
+    width: 18em;
+  }
+  #toast-container .toast-close-button {
+    right: -0.2em;
+    top: -0.2em;
+}
+}
+@media all and (min-width: 481px) and (max-width: 768px) {
+  #toast-container > div {
+    padding: 15px 15px 15px 50px;
+    width: 25em;
+  }
+}
+
+ /*
+  * AngularJS-Toaster
+  * Version 0.3
+ */
+#toast-container > div.ng-enter,
+#toast-container > div.ng-leave
+{ 
+    -webkit-transition: 1000ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
+    -moz-transition: 1000ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
+    -ms-transition: 1000ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
+    -o-transition: 1000ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
+    transition: 1000ms cubic-bezier(0.250, 0.250, 0.750, 0.750) all;
+} 
+
+#toast-container > div.ng-enter.ng-enter-active, 
+#toast-container > div.ng-leave {
+    opacity: 0.8;
+}
+
+#toast-container > div.ng-leave.ng-leave-active,
+#toast-container > div.ng-enter {
+    opacity: 0;
+}
\ No newline at end of file
diff --git a/securis/src/main/resources/static/js/admin.js b/securis/src/main/resources/static/js/admin.js
index 759b8a2..7bd2645 100644
--- a/securis/src/main/resources/static/js/admin.js
+++ b/securis/src/main/resources/static/js/admin.js
@@ -1,7 +1,7 @@
 (function() {
 	'use strict';
 
-	var app = angular.module('app', [ 'ngRoute', 'ngAnimate', 'ngResource' ]);
+	var app = angular.module('app', [ 'ngRoute', 'ngAnimate', 'ngResource', 'toaster' ]);
 
 	app.directive(
 					'catalogField',
@@ -22,7 +22,7 @@
 						};
 					});
 
-	app.factory('Catalogs', function($http, $resource) {
+	app.factory('Catalogs', function($http, $resource, toaster) {
 		var CatalogsService = {
 			resources : {
 				application : $resource('/application/:appId', {
@@ -68,7 +68,25 @@
 				return CatalogsService.data ? CatalogsService.data[index] : {};
 			},
 			save: function(catalog, data) {
-				console.log('save ???? ' );
+				var resource = CatalogsService.resources[catalog.toLowerCase()];
+				function success(data) {
+					console.log('success')
+					console.log(data)
+					toaster.pop('success', "Data saved sucessfully in " + catalog);
+				}
+				function fail(data, status) {
+					var errorMsg = {500: 'Server error', 404: 'Item to modify was not found'}[data.status]
+					toaster.pop('error', "Error saving data in " + catalog, errorMsg);
+					console.log('error')
+					console.error(data)
+					console.error(status)
+				}
+				if (data.id && data.id !== '')
+					return resource.update(data, success, fail)
+				else
+					return resource.save(data, success, fail)
+			},
+			remove: function(catalog, data) {
 				var resource = CatalogsService.resources[catalog.toLowerCase()];
 				function success(data) {
 					console.log('success')
@@ -79,10 +97,7 @@
 					console.error(data)
 					console.error(status)
 				}
-				if (data.id && data.id !== '')
-					return resource.update(data, success, fail)
-				else
-					return resource.save(data, success, fail)
+				return resource.remove({}, data, success, fail)
 			},
 			query: function(catalog, callback) {
 				console.log('HI catalog ???? ' + catalog);
@@ -108,38 +123,58 @@
 			'$scope',
 			'$http',
 			'Catalogs',
-			function($scope, $http, catalogs) {
+			function($scope, $http, Catalogs) {
 				$scope.formu = {};
 				$scope.catalogIndex = 0;
-				$scope.catalogs = catalogs.list(function() {
-					$scope.catalogMetadata = catalogs.getMetadata($scope.catalogIndex);
-					$scope.list = catalogs.query(catalogs.getResource($scope.catalogIndex));
+				$scope.catalogs = Catalogs.list(function() {
+					$scope.catalogMetadata = Catalogs.getMetadata($scope.catalogIndex);
+					$scope.list = Catalogs.query(Catalogs.getResource($scope.catalogIndex));
 				}); 
 
 				$scope.catalogMetadata = {};
 				$scope.selectCatalog = function(index, $event) {
 					$scope.catalogIndex = index;
-					$scope.catalogMetadata = catalogs.getMetadata($scope.catalogIndex);
-					$scope.list = catalogs.query(catalogs.getResource($scope.catalogIndex));
+					$scope.catalogMetadata = Catalogs.getMetadata($scope.catalogIndex);
+					$scope.list = Catalogs.query(Catalogs.getResource($scope.catalogIndex));
 					console.log($event);
 				}
-			} ]);
-
-	app.controller('CatalogFormCtrl', [ '$scope', '$http', 'Catalogs',
-			function($scope, $http, Catalogs) {
-				$scope.showForm = false;
-				$scope.scope = $scope;
-				console.log('Form: currentCatalog:' + $scope.cataLogIndex);
-				$scope.editNew = function() {
+				$scope.edit = function(data) {
 					$scope.showForm = true;
-					$scope.isNew = true;
+					$scope.isNew = false;
+					for (var k in data) {
+						if (k.indexOf('$') !== 0) $scope.formu[k] = data[k]
+					}
+					// TODO: Load in formu values for Form
 					// $scope.formu = {};
 				}
-				$scope.edit = function() {
-					$scope.showForm = true;
+				$scope.delete = function(data) {
+					BootstrapDialog.confirm('The record will be deleted, are you sure?', function(result){
+			            if(result) {
+							var catalogName = Catalogs.getResource($scope.catalogIndex);
+							var promise = Catalogs.remove(catalogName, data).$promise;
+							promise.then(function(data) {
+								$scope.list = Catalogs.query(catalogName);
+							});
+			            }
+			        });
+					$scope.showForm = false;
 					$scope.isNew = false;
 					// TODO: Load in formu values for Form
 					// $scope.formu = {};
+				}
+
+			} ]);
+
+	app.controller('CatalogFormCtrl', [ '$scope', '$http', 'toaster', 'Catalogs',
+			function($scope, $http, toaster, Catalogs) {
+				$scope.showForm = false;
+				$scope.scope = $scope;
+				console.log('Form: currentCatalog:' + $scope.cataLogIndex);
+
+				$scope.editNew = function() {
+					$scope.showForm = true;
+					$scope.isNew = true;
+					$scope.formu = {};
 				}
 				$scope.cancel = function() {
 					$scope.showForm = false;
@@ -151,34 +186,44 @@
 					} else {
 						var catalogName = Catalogs.getResource($scope.catalogIndex);
 						var promise = Catalogs.save(catalogName, $scope.formu).$promise;
-						console.log('================================================================================================================================')
-						console.log(promise)
-						promise.then(function(data) {
-							console.log('Command returned OK form promise')
-							console.log(data)
+						promise.then(function(data, otro) {
+							if ($scope.isNew) {
+								$scope.formu = {}
+								$('#name').focus();
+							} else {
+								$scope.cancel();
+							}
+						//	$scope.formu = {};
 							$scope.$parent.list = Catalogs.query(catalogName);
-							
+						}, function(error, otro) {
+							console.log('then error');
+							console.log(error);
+							console.log(otro);							
 						});
+						
 					}
 				}
 			} ]);
 
-	app.controller('CatalogListCtrl', [ '$scope', '$http', 'Catalogs', 
-			function($scope, $http, Catalogs) {
+	app.controller('CatalogListCtrl', [ '$scope', '$http', '$filter', 'Catalogs', 
+			function($scope, $http, $filter, Catalogs) {
 				console.log('List: currentCatalog: ' + $scope.currentCatalog);
-				$scope.myFilter = function(field) {
-					if (field === 'creationTimestamp')
-						return 'date';
-					else '';
+				var _indexOfField = function(name) {
+					if (!$scope.catalogMetadata) return -1;
+					for (var i = $scope.catalogMetadata.fields.length - 1; i >= 0 && $scope.catalogMetadata.fields[i].name !== name; i--);
+					return i;
+				}
+				
+				$scope.print = function(name, value) {
+					var index = _indexOfField(name);
+					if (index === -1) return value;
+					var type = $scope.catalogMetadata.fields[index].type;
+					
+					return type === 'date' ? $filter('date')(value, 'yyyy-MM-dd') : value;
 				}
 				
 				$scope.display = function(name) {
-					var _indexOf = function(name) {
-						if (!$scope.catalogMetadata) return -1;
-						for (var i = $scope.catalogMetadata.fields.length - 1; i >= 0 && $scope.catalogMetadata.fields[i].name !== name; i--);
-						return i;
-					}
-					var index = _indexOf(name);
+					var index = _indexOfField(name);
 					return index === -1 ? '' : $scope.catalogMetadata.fields[index].display;
 				}
 
diff --git a/securis/src/main/resources/static/js/bootstrap-dialog.js b/securis/src/main/resources/static/js/bootstrap-dialog.js
new file mode 100644
index 0000000..e4da030
--- /dev/null
+++ b/securis/src/main/resources/static/js/bootstrap-dialog.js
@@ -0,0 +1,654 @@
+/* ================================================
+ * Make use of Twitter Bootstrap's modal more monkey-friendly.
+ * 
+ * For Bootstrap 3.
+ * 
+ * javanoob@hotmail.com
+ * 
+ * Licensed under The MIT License.
+ * ================================================ */
+var BootstrapDialog = null;
+!function($) {
+    "use strict";
+
+    BootstrapDialog = function(options) {
+        this.defaultOptions = {
+            id: BootstrapDialog.newGuid(),
+            type: BootstrapDialog.TYPE_PRIMARY,
+            size: BootstrapDialog.SIZE_NORMAL,
+            cssClass: '',
+            title: null,
+            message: null,
+            buttons: [],
+            closable: true,
+            spinicon: BootstrapDialog.ICON_SPINNER,
+            data: {},
+            onshow: null,
+            onhide: null,
+            autodestroy: true
+        };
+        this.indexedButtons = {};
+        this.realized = false;
+        this.opened = false;
+        this.initOptions(options);
+        this.holdThisInstance();
+    };
+
+    /**
+     *  Some constants.
+     */
+    BootstrapDialog.NAMESPACE = 'bootstrap-dialog';
+
+    BootstrapDialog.TYPE_DEFAULT = 'type-default';
+    BootstrapDialog.TYPE_INFO = 'type-info';
+    BootstrapDialog.TYPE_PRIMARY = 'type-primary';
+    BootstrapDialog.TYPE_SUCCESS = 'type-success';
+    BootstrapDialog.TYPE_WARNING = 'type-warning';
+    BootstrapDialog.TYPE_DANGER = 'type-danger';
+
+    BootstrapDialog.DEFAULT_TEXTS = {};
+    BootstrapDialog.DEFAULT_TEXTS[BootstrapDialog.TYPE_DEFAULT] = 'Information';
+    BootstrapDialog.DEFAULT_TEXTS[BootstrapDialog.TYPE_INFO] = 'Information';
+    BootstrapDialog.DEFAULT_TEXTS[BootstrapDialog.TYPE_PRIMARY] = 'Information';
+    BootstrapDialog.DEFAULT_TEXTS[BootstrapDialog.TYPE_SUCCESS] = 'Success';
+    BootstrapDialog.DEFAULT_TEXTS[BootstrapDialog.TYPE_WARNING] = 'Warning';
+    BootstrapDialog.DEFAULT_TEXTS[BootstrapDialog.TYPE_DANGER] = 'Danger';
+
+    BootstrapDialog.SIZE_NORMAL = 'size-normal';
+    BootstrapDialog.SIZE_LARGE = 'size-large';
+
+    BootstrapDialog.BUTTON_SIZES = {};
+    BootstrapDialog.BUTTON_SIZES[BootstrapDialog.SIZE_NORMAL] = '';
+    BootstrapDialog.BUTTON_SIZES[BootstrapDialog.SIZE_LARGE] = 'btn-lg';
+
+    BootstrapDialog.ICON_SPINNER = 'glyphicon glyphicon-asterisk';
+
+    /**
+     * Open / Close all created dialogs all at once.
+     */
+    BootstrapDialog.dialogs = {};
+    BootstrapDialog.openAll = function() {
+        $.each(BootstrapDialog.dialogs, function(id, dialogInstance) {
+            dialogInstance.open();
+        });
+    };
+    BootstrapDialog.closeAll = function() {
+        $.each(BootstrapDialog.dialogs, function(id, dialogInstance) {
+            dialogInstance.close();
+        });
+    };
+
+    BootstrapDialog.prototype = {
+        constructor: BootstrapDialog,
+        initOptions: function(options) {
+            this.options = $.extend(true, this.defaultOptions, options);
+
+            return this;
+        },
+        holdThisInstance: function() {
+            BootstrapDialog.dialogs[this.getId()] = this;
+
+            return this;
+        },
+        initModalStuff: function() {
+            this.setModal(this.createModal())
+            .setModalDialog(this.createModalDialog())
+            .setModalContent(this.createModalContent())
+            .setModalHeader(this.createModalHeader())
+            .setModalBody(this.createModalBody())
+            .setModalFooter(this.createModalFooter());
+
+            this.getModal().append(this.getModalDialog());
+            this.getModalDialog().append(this.getModalContent());
+            this.getModalContent()
+            .append(this.getModalHeader())
+            .append(this.getModalBody())
+            .append(this.getModalFooter());
+
+            return this;
+        },
+        createModal: function() {
+            return $('<div class="modal fade" tabindex="-1" id="' + this.getId() + '"></div>');
+        },
+        getModal: function() {
+            return this.$modal;
+        },
+        setModal: function($modal) {
+            this.$modal = $modal;
+
+            return this;
+        },
+        createModalDialog: function() {
+            return $('<div class="modal-dialog"></div>');
+        },
+        getModalDialog: function() {
+            return this.$modalDialog;
+        },
+        setModalDialog: function($modalDialog) {
+            this.$modalDialog = $modalDialog;
+
+            return this;
+        },
+        createModalContent: function() {
+            return $('<div class="modal-content"></div>');
+        },
+        getModalContent: function() {
+            return this.$modalContent;
+        },
+        setModalContent: function($modalContent) {
+            this.$modalContent = $modalContent;
+
+            return this;
+        },
+        createModalHeader: function() {
+            return $('<div class="modal-header"></div>');
+        },
+        getModalHeader: function() {
+            return this.$modalHeader;
+        },
+        setModalHeader: function($modalHeader) {
+            this.$modalHeader = $modalHeader;
+
+            return this;
+        },
+        createModalBody: function() {
+            return $('<div class="modal-body"></div>');
+        },
+        getModalBody: function() {
+            return this.$modalBody;
+        },
+        setModalBody: function($modalBody) {
+            this.$modalBody = $modalBody;
+
+            return this;
+        },
+        createModalFooter: function() {
+            return $('<div class="modal-footer"></div>');
+        },
+        getModalFooter: function() {
+            return this.$modaFooter;
+        },
+        setModalFooter: function($modaFooter) {
+            this.$modaFooter = $modaFooter;
+
+            return this;
+        },
+        createDynamicContent: function(rawContent) {
+            var content = null;
+            if (typeof rawContent === 'function') {
+                content = rawContent.call(rawContent, this);
+            } else {
+                content = rawContent;
+            }
+            if (typeof content === 'string') {
+                content = this.formatStringContent(content);
+            }
+
+            return content;
+        },
+        formatStringContent: function(content) {
+            return content.replace(/\r\n/g, '<br />').replace(/[\r\n]/g, '<br />');
+        },
+        setData: function(key, value) {
+            this.options.data[key] = value;
+
+            return this;
+        },
+        getData: function(key) {
+            return this.options.data[key];
+        },
+        setId: function(id) {
+            this.options.id = id;
+
+            return this;
+        },
+        getId: function() {
+            return this.options.id;
+        },
+        getType: function() {
+            return this.options.type;
+        },
+        setType: function(type) {
+            this.options.type = type;
+
+            return this;
+        },
+        getSize: function() {
+            return this.options.size;
+        },
+        setSize: function(size) {
+            this.options.size = size;
+
+            return this;
+        },
+        getCssClass: function() {
+            return this.options.cssClass;
+        },
+        setCssClass: function(cssClass){
+            this.options.cssClass = cssClass;
+
+            return this;
+        },
+        getTitle: function() {
+            return this.options.title;
+        },
+        setTitle: function(title) {
+            this.options.title = title;
+
+            return this;
+        },
+        getMessage: function() {
+            return this.options.message;
+        },
+        setMessage: function(message) {
+            this.options.message = message;
+
+            return this;
+        },
+        isClosable: function() {
+            return this.options.closable;
+        },
+        setClosable: function(closable) {
+            this.options.closable = closable;
+            this.updateClosable();
+
+            return this;
+        },
+        getSpinicon: function() {
+            return this.options.spinicon;
+        },
+        setSpinicon: function(spinicon) {
+            this.options.spinicon = spinicon;
+
+            return this;
+        },
+        addButton: function(button) {
+            this.options.buttons.push(button);
+
+            return this;
+        },
+        addButtons: function(buttons) {
+            var that = this;
+
+            $.each(buttons, function(index, button) {
+                that.addButton(button);
+            });
+
+            return this;
+        },
+        getButtons: function() {
+            return this.options.buttons;
+        },
+        setButtons: function(buttons) {
+            this.options.buttons = buttons;
+
+            return this;
+        },
+        /**
+         * If there is id provided for a button option, it will be in dialog.indexedButtons list.
+         * 
+         * In that case you can use dialog.getButton(id) to find the button.
+         * 
+         * @param {type} id
+         * @returns {undefined}
+         */
+        getButton: function(id) {
+            if (typeof this.indexedButtons[id] !== 'undefined') {
+                return this.indexedButtons[id];
+            }
+
+            return null;
+        },
+        getButtonSize: function() {
+            if (typeof BootstrapDialog.BUTTON_SIZES[this.getSize()] !== 'undefined') {
+                return BootstrapDialog.BUTTON_SIZES[this.getSize()];
+            }
+
+            return '';
+        },
+        isAutodestroy: function() {
+            return this.options.autodestroy;
+        },
+        setAutodestroy: function(autodestroy) {
+            this.options.autodestroy = autodestroy;
+        },
+        getDefaultText: function() {
+            return BootstrapDialog.DEFAULT_TEXTS[this.getType()];
+        },
+        getNamespace: function(name) {
+            return BootstrapDialog.NAMESPACE + '-' + name;
+        },
+        createHeaderContent: function() {
+            var $container = $('<div></div>');
+            $container.addClass(this.getNamespace('header'));
+
+            // title
+            $container.append(this.createTitleContent());
+
+            // Close button
+            $container.append(this.createCloseButton());
+
+            return $container;
+        },
+        createTitleContent: function() {
+            var $title = $('<div></div>');
+            $title.addClass(this.getNamespace('title'));
+            $title.append(this.getTitle() !== null ? this.createDynamicContent(this.getTitle()) : this.getDefaultText());
+
+            return $title;
+        },
+        createCloseButton: function() {
+            var $container = $('<div></div>');
+            $container.addClass(this.getNamespace('close-button'));
+            var $icon = $('<button class="close">×</button>');
+            $container.append($icon);
+            $container.on('click', {dialog: this}, function(event) {
+                event.data.dialog.close();
+            });
+
+            return $container;
+        },
+        createBodyContent: function() {
+            var $container = $('<div></div>');
+            $container.addClass(this.getNamespace('body'));
+
+            // Message
+            $container.append(this.createMessageContent());
+
+            return $container;
+        },
+        createMessageContent: function() {
+            var $message = $('<div></div>');
+            $message.addClass(this.getNamespace('message'));
+            $message.append(this.createDynamicContent(this.getMessage()));
+
+            return $message;
+        },
+        createFooterContent: function() {
+            var $container = $('<div></div>');
+            $container.addClass(this.getNamespace('footer'));
+
+            // Buttons
+            $container.append(this.createFooterButtons());
+
+            return $container;
+        },
+        createFooterButtons: function() {
+            var that = this;
+            var $container = $('<div></div>');
+            $container.addClass(this.getNamespace('footer-buttons'));
+            this.indexedButtons = {};
+            $.each(this.options.buttons, function(index, button) {
+                var $button = that.createButton(button);
+                if (typeof button.id !== 'undefined') {
+                    that.indexedButtons[button.id] = $button;
+                }
+                $container.append($button);
+            });
+
+            return $container;
+        },
+        createButton: function(button) {
+            var $button = $('<button class="btn"></button>');
+            $button.addClass(this.getButtonSize());
+
+            // Icon
+            if (typeof button.icon !== undefined && $.trim(button.icon) !== '') {
+                $button.append(this.createButtonIcon(button.icon));
+            }
+
+            // Label
+            if (typeof button.label !== undefined) {
+                $button.append(button.label);
+            }
+
+            // Css class
+            if (typeof button.cssClass !== undefined && $.trim(button.cssClass) !== '') {
+                $button.addClass(button.cssClass);
+            } else {
+                $button.addClass('btn-default');
+            }
+
+            // Button on click
+            $button.on('click', {dialog: this, button: button}, function(event) {
+                var dialog = event.data.dialog;
+                var button = event.data.button;
+                if (typeof button.action === 'function') {
+                    button.action.call(this, dialog);
+                }
+
+                if (button.autospin) {
+                    var $button = $(this);
+                    $button.find('.' + dialog.getNamespace('button-icon')).remove();
+                    $button.prepend(dialog.createButtonIcon(dialog.getSpinicon()).addClass('icon-spin'));
+                }
+            });
+
+            return $button;
+        },
+        createButtonIcon: function(icon) {
+            var $icon = $('<span></span>');
+            $icon.addClass(this.getNamespace('button-icon')).addClass(icon);
+
+            return $icon;
+        },
+        /**
+         * Invoke this only after the dialog is realized.
+         * 
+         * @param {type} enable
+         * @returns {undefined}
+         */
+        enableButtons: function(enable) {
+            var $buttons = this.getModalFooter().find('.btn');
+            $buttons.prop("disabled", !enable).toggleClass('disabled', !enable);
+
+            return this;
+        },
+        /**
+         * Invoke this only after the dialog is realized.
+         * 
+         * @param {type} enable
+         * @returns {undefined}
+         */
+        updateClosable: function() {
+            if (this.isRealized()) {
+                // Backdrop, I did't find a way to change bs3 backdrop option after the dialog is popped up, so here's a new wheel.
+                var $theBigMask = this.getModal();
+                $theBigMask.off('click').on('click', {dialog: this}, function(event) {
+                    event.target === this && event.data.dialog.isClosable() && event.data.dialog.close();
+                });
+
+                // Close button
+                this.getModalHeader().find('.' + this.getNamespace('close-button')).toggle(this.isClosable());
+
+                // ESC key support
+                $theBigMask.off('keyup').on('keyup', {dialog: this}, function(event) {
+                    event.which === 27 && event.data.dialog.isClosable() && event.data.dialog.close();
+                });
+            }
+
+            return this;
+        },
+        /**
+         * Set handler for modal event 'show'.
+         * This is a setter!
+         * 
+         * @param {type} onopen
+         * @returns {_L9.BootstrapDialog.prototype}
+         */
+        onShow: function(onshow) {
+            this.options.onshow = onshow;
+
+            return this;
+        },
+        /**
+         * Set handler for modal event 'hide'.
+         * This is a setter!
+         * 
+         * @param {type} onclose
+         * @returns {_L9.BootstrapDialog.prototype}
+         */
+        onHide: function(onhide) {
+            this.options.onhide = onhide;
+
+            return this;
+        },
+        isRealized: function() {
+            return this.realized;
+        },
+        setRealized: function(realized) {
+            this.realized = realized;
+
+            return this;
+        },
+        isOpened: function() {
+            return this.opened;
+        },
+        setOpened: function(opened) {
+            this.opened = opened;
+
+            return this;
+        },
+        handleModalEvents: function() {
+            this.getModal().on('show.bs.modal', {dialog: this}, function(event) {
+                var dialog = event.data.dialog;
+                typeof dialog.options.onshow === 'function' && dialog.options.onshow(dialog);
+                dialog.showPageScrollBar(true);
+            });
+            this.getModal().on('hide.bs.modal', {dialog: this}, function(event) {
+                var dialog = event.data.dialog;
+                typeof dialog.options.onhide === 'function' && dialog.options.onhide(dialog);
+            });
+            this.getModal().on('hidden.bs.modal', {dialog: this}, function(event) {
+                var dialog = event.data.dialog;
+                dialog.isAutodestroy() && $(this).remove();
+                dialog.showPageScrollBar(false);
+            });
+
+            return this;
+        },
+        showPageScrollBar: function(show) {
+            $(document.body).toggleClass('modal-open', show);
+        },
+        realize: function() {
+            this.initModalStuff();
+            this.getModal().addClass(BootstrapDialog.NAMESPACE)
+            .addClass(this.getType())
+            .addClass(this.getSize())
+            .addClass(this.getCssClass());
+            this.getModalHeader().append(this.createHeaderContent());
+            this.getModalBody().append(this.createBodyContent());
+            this.getModalFooter().append(this.createFooterContent());
+            this.getModal().modal({
+                backdrop: 'static',
+                keyboard: false,
+                show: false
+            });
+            this.handleModalEvents();
+            this.setRealized(true);
+
+            return this;
+        },
+        open: function() {
+            !this.isRealized() && this.realize();
+            this.updateClosable();
+            this.getModal().modal('show');
+            this.setOpened(true);
+
+            return this;
+        },
+        close: function() {
+            this.getModal().modal('hide');
+            if (this.isAutodestroy()) {
+                delete BootstrapDialog.dialogs[this.getId()];
+            }
+            this.setOpened(false);
+
+            return this;
+        }
+    };
+
+    /**
+     * RFC4122 version 4 compliant unique id creator.
+     *
+     * Added by https://github.com/tufanbarisyildirim/
+     *
+     *  @returns {String}
+     */
+    BootstrapDialog.newGuid = function() {
+        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+            var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
+            return v.toString(16);
+        });
+    };
+
+    /* ================================================
+     * For lazy people
+     * ================================================ */
+
+    /**
+     * Shortcut function: show
+     * 
+     * @param {type} options
+     * @returns {undefined}
+     */
+    BootstrapDialog.show = function(options) {
+        new BootstrapDialog(options).open();
+    };
+
+    /**
+     * Alert window
+     * 
+     * @param {type} message
+     * @param {type} callback
+     * @returns {undefined}
+     */
+    BootstrapDialog.alert = function(message, callback) {
+        new BootstrapDialog({
+            message: message,
+            data: {
+                'callback': callback
+            },
+            closable: false,
+            buttons: [{
+                    label: 'OK',
+                    action: function(dialog) {
+                        typeof dialog.getData('callback') === 'function' && dialog.getData('callback')(true);
+                        dialog.close();
+                    }
+                }]
+        }).open();
+    };
+
+    /**
+     * Confirm window
+     * 
+     * @param {type} message
+     * @param {type} callback
+     * @returns {undefined}
+     */
+    BootstrapDialog.confirm = function(message, callback) {
+        new BootstrapDialog({
+            title: 'Confirmation',
+            message: message,
+            closable: false,
+            data: {
+                'callback': callback
+            },
+            buttons: [{
+                    label: 'Cancel',
+                    action: function(dialog) {
+                        typeof dialog.getData('callback') === 'function' && dialog.getData('callback')(false);
+                        dialog.close();
+                    }
+                }, {
+                    label: 'OK',
+                    cssClass: 'btn-primary',
+                    action: function(dialog) {
+                        typeof dialog.getData('callback') === 'function' && dialog.getData('callback')(true);
+                        dialog.close();
+                    }
+                }]
+        }).open();
+    };
+}(window.jQuery);
diff --git a/securis/src/main/resources/static/js/catalogs.json b/securis/src/main/resources/static/js/catalogs.json
index dd7cf93..33e2983 100644
--- a/securis/src/main/resources/static/js/catalogs.json
+++ b/securis/src/main/resources/static/js/catalogs.json
@@ -1,7 +1,7 @@
 [ {
 	"name" : "Applications",
 	"resource" : "application",
-	"list_fields" : [ "name", "description", "creation_timestamp" ],
+	"list_fields" : [ "name", "description", "creationTimestamp" ],
 	"fields" : [ {
 		"name" : "id",
 		"display" : "ID",
diff --git a/securis/src/main/resources/static/js/commons.js b/securis/src/main/resources/static/js/commons.js
new file mode 100644
index 0000000..692793b
--- /dev/null
+++ b/securis/src/main/resources/static/js/commons.js
@@ -0,0 +1,224 @@
+(function() {
+	'use strict';
+
+	var app = angular.module('app', [ 'ngRoute', 'ngAnimate', 'ngResource' ]);
+
+	app.directive(
+					'catalogField',
+					function() {
+						return {
+							restrict : 'A', // only activate on element
+											// attribute
+							require : '?ngModel', // get a hold of
+													// NgModelController
+							link : function(scope, element, attrs, ngModel) {
+								if (!ngModel)
+									return; // do nothing if no ng-model
+								// TODO: Replace the hard-coded form ID by the
+								// appropiate dynamic field
+								scope.catalogForm[attrs.name] = scope.catalogForm['{{field.name}}'];
+								scope.catalogForm[attrs.name].$name = attrs.name;
+							}
+						};
+					});
+
+	app.factory('Catalogs', function($http, $resource) {
+		var CatalogsService = {
+			resources : {
+				application : $resource('/application/:appId', {
+					appId : '@id'
+				}, {
+					update : {
+						method : "PUT"
+					},
+					test: {
+						url: '/application/:appId',
+						method : "DELETE",
+						params : {
+							appId : '@id'
+						}
+					}
+				}),
+				user : $resource('/user/:userId', {
+					userId : '@id'
+				}, {
+					update : {
+						method : "PUT"
+					}
+				}),
+				licensetype : $resource('/licenseType/:licenseTypeId', {
+					licenseTypeId : '@id'
+				}, {
+					update : {
+						method : "PUT"
+					}
+				})
+
+			},
+			list : function(initFn) {
+				$http.get('/js/catalogs.json').success(function(data) {
+					console.log(data);
+					CatalogsService.data = data;
+					initFn();
+				})
+				return CatalogsService;
+			},
+			getName : function(index) {
+				return CatalogsService.data ? CatalogsService.data[index].name
+						: '';
+			},
+			getResource : function(index) {
+				return CatalogsService.data ? CatalogsService.data[index].resource
+						: '';
+			},
+			getMetadata : function(index) {
+				return CatalogsService.data ? CatalogsService.data[index] : {};
+			},
+			save: function(catalog, data) {
+				var resource = CatalogsService.resources[catalog.toLowerCase()];
+				function success(data) {
+					console.log('success')
+					console.log(data)
+				}
+				function fail(data, status) {
+					console.log('error')
+					console.error(data)
+					console.error(status)
+				}
+				if (data.id && data.id !== '')
+					return resource.update(data, success, fail)
+				else
+					return resource.save(data, success, fail)
+			},
+			remove: function(catalog, data) {
+				var resource = CatalogsService.resources[catalog.toLowerCase()];
+				function success(data) {
+					console.log('success')
+					console.log(data)
+				}
+				function fail(data, status) {
+					console.log('error')
+					console.error(data)
+					console.error(status)
+				}
+				return resource.remove({}, data, success, fail)
+			},
+			query: function(catalog, callback) {
+				console.log('HI catalog ???? ' + catalog);
+				var resource = CatalogsService.resources[catalog.toLowerCase()];
+				function success(data) {
+					console.log('success')
+					console.log(data)
+				}
+				function fail(data, status) {
+					console.log('error')
+					console.error(data)
+					console.error(status)
+				}
+				return resource.query({}, success, fail);
+			}
+		}
+
+		return CatalogsService;
+
+	});
+
+	app.controller('CatalogsCtrl', [
+			'$scope',
+			'$http',
+			'Catalogs',
+			function($scope, $http, Catalogs) {
+				$scope.formu = {};
+				$scope.catalogIndex = 0;
+				$scope.catalogs = Catalogs.list(function() {
+					$scope.catalogMetadata = Catalogs.getMetadata($scope.catalogIndex);
+					$scope.list = Catalogs.query(Catalogs.getResource($scope.catalogIndex));
+				}); 
+
+				$scope.catalogMetadata = {};
+				$scope.selectCatalog = function(index, $event) {
+					$scope.catalogIndex = index;
+					$scope.catalogMetadata = Catalogs.getMetadata($scope.catalogIndex);
+					$scope.list = Catalogs.query(Catalogs.getResource($scope.catalogIndex));
+					console.log($event);
+				}
+				$scope.edit = function(data) {
+					$scope.showForm = true;
+					$scope.isNew = false;
+					for (var k in data) {
+						if (k.indexOf('$') !== 0) $scope.formu[k] = data[k]
+					}
+					// TODO: Load in formu values for Form
+					// $scope.formu = {};
+				}
+				$scope.delete = function(data) {
+					BootstrapDialog.confirm('The record will be deleted, are you sure?', function(result){
+			            if(result) {
+							var catalogName = Catalogs.getResource($scope.catalogIndex);
+							var promise = Catalogs.remove(catalogName, data).$promise;
+							promise.then(function(data) {
+								$scope.list = Catalogs.query(catalogName);
+							});
+			            }
+			        });
+					$scope.showForm = false;
+					$scope.isNew = false;
+					// TODO: Load in formu values for Form
+					// $scope.formu = {};
+				}
+
+			} ]);
+
+	app.controller('CatalogFormCtrl', [ '$scope', '$http', 'Catalogs',
+			function($scope, $http, Catalogs) {
+				$scope.showForm = false;
+				$scope.scope = $scope;
+				console.log('Form: currentCatalog:' + $scope.cataLogIndex);
+
+				$scope.editNew = function() {
+					$scope.showForm = true;
+					$scope.isNew = true;
+					// $scope.formu = {};
+				}
+				$scope.cancel = function() {
+					$scope.showForm = false;
+				}
+
+				$scope.saveCatalog = function() {
+					if ($scope.catalogForm.$invalid) {
+						alert(JSON.stringify($scope.catalogForm))
+					} else {
+						var catalogName = Catalogs.getResource($scope.catalogIndex);
+						var promise = Catalogs.save(catalogName, $scope.formu).$promise;
+						promise.then(function(data) {
+							$scope.$parent.list = Catalogs.query(catalogName);
+						});
+					}
+				}
+			} ]);
+
+	app.controller('CatalogListCtrl', [ '$scope', '$http', '$filter', 'Catalogs', 
+			function($scope, $http, $filter, Catalogs) {
+				console.log('List: currentCatalog: ' + $scope.currentCatalog);
+				var _indexOfField = function(name) {
+					if (!$scope.catalogMetadata) return -1;
+					for (var i = $scope.catalogMetadata.fields.length - 1; i >= 0 && $scope.catalogMetadata.fields[i].name !== name; i--);
+					return i;
+				}
+				
+				$scope.print = function(name, value) {
+					var index = _indexOfField(name);
+					if (index === -1) return value;
+					var type = $scope.catalogMetadata.fields[index].type;
+					
+					return type === 'date' ? $filter('date')(value, 'yyyy-MM-dd') : value;
+				}
+				
+				$scope.display = function(name) {
+					var index = _indexOfField(name);
+					return index === -1 ? '' : $scope.catalogMetadata.fields[index].display;
+				}
+
+			} ]);
+
+})();
diff --git a/securis/src/main/resources/static/js/toaster.js b/securis/src/main/resources/static/js/toaster.js
new file mode 100644
index 0000000..e5127d0
--- /dev/null
+++ b/securis/src/main/resources/static/js/toaster.js
@@ -0,0 +1,145 @@
+'use strict';
+ 
+/*
+ * AngularJS Toaster
+ * Version: 0.4.1
+ *
+ * Copyright 2013 Jiri Kavulak.  
+ * All Rights Reserved.  
+ * Use, reproduction, distribution, and modification of this code is subject to the terms and 
+ * conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
+ *
+ * Author: Jiri Kavulak
+ * Related to project of John Papa and Hans Fjällemark
+ */
+ 
+angular.module('toaster', ['ngAnimate'])
+.service('toaster', ['$rootScope', function ($rootScope) {
+    this.pop = function (type, title, body, timeout, bodyOutputType) {
+        this.toast = {
+            type: type,
+            title: title,
+            body: body,
+            timeout: timeout,
+            bodyOutputType: bodyOutputType
+        };
+        $rootScope.$broadcast('toaster-newToast');
+    };
+}])
+.constant('toasterConfig', {
+  				'tap-to-dismiss': true,
+					'newest-on-top': true,
+					//'fade-in': 1000,            // done in css
+					//'on-fade-in': undefined,    // not implemented
+					//'fade-out': 1000,           // done in css
+					// 'on-fade-out': undefined,  // not implemented
+					//'extended-time-out': 1000,    // not implemented
+					'time-out': 5000, // Set timeOut and extendedTimeout to 0 to make it sticky
+					'icon-classes': {
+						error: 'toast-error',
+						info: 'toast-info',
+						success: 'toast-success',
+						warning: 'toast-warning'
+					},
+					'body-output-type': '', // Options: '', 'trustedHtml', 'template'
+					'body-template': 'toasterBodyTmpl.html',
+					'icon-class': 'toast-info',
+					'position-class': 'toast-top-right',
+					'title-class': 'toast-title',
+					'message-class': 'toast-message'
+				})
+.directive('toasterContainer', ['$compile', '$timeout', '$sce', 'toasterConfig', 'toaster',
+function ($compile, $timeout, $sce, toasterConfig, toaster) {
+  return {
+    replace: true,
+    restrict: 'EA',
+    link: function (scope, elm, attrs){
+      
+      var id = 0;
+      
+      var mergedConfig = toasterConfig;
+      if (attrs.toasterOptions) {
+          angular.extend(mergedConfig, scope.$eval(attrs.toasterOptions));
+      }
+      
+      scope.config = {
+          position: mergedConfig['position-class'],
+          title: mergedConfig['title-class'],
+          message: mergedConfig['message-class'],
+          tap: mergedConfig['tap-to-dismiss']
+      };
+      
+      function addToast (toast){
+        toast.type = mergedConfig['icon-classes'][toast.type];
+        if (!toast.type)
+            toast.type = mergedConfig['icon-class'];
+        
+        id++;
+        angular.extend(toast, { id: id });
+        
+        switch(toast.bodyOutputType)
+        {
+          case 'trustedHtml':
+            toast.html = $sce.trustAsHtml(toast.body);
+            break;
+          case 'template':
+            toast.bodyTemplate = mergedConfig['body-template'];
+            break;
+        }
+        
+        var timeout = typeof(toast.timeout) == "number" ? toast.timeout : mergedConfig['time-out'];
+        if (timeout > 0)
+            setTimeout(toast, timeout);
+        
+        if (mergedConfig['newest-on-top'] === true)
+            scope.toasters.unshift(toast);
+        else
+            scope.toasters.push(toast);
+      }
+      
+      function setTimeout(toast, time){
+          toast.timeout= $timeout(function (){ 
+              scope.removeToast(toast.id);
+            }, time);
+      }
+      
+      scope.toasters = [];
+      scope.$on('toaster-newToast', function () {
+        addToast(toaster.toast);
+      });
+    },
+    controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
+      
+      $scope.stopTimer = function(toast){
+        if(toast.timeout)
+          $timeout.cancel(toast.timeout);
+      };
+      
+      $scope.removeToast = function (id){
+        var i = 0;
+        for (i; i < $scope.toasters.length; i++){
+            if($scope.toasters[i].id === id)
+                break;
+        }
+        $scope.toasters.splice(i, 1);
+      };
+      
+      $scope.remove = function(id){
+        if ($scope.config.tap === true){
+            $scope.removeToast(id);
+        }
+      };
+    }],
+    template:
+    '<div  id="toast-container" ng-class="config.position">' +
+        '<div ng-repeat="toaster in toasters" class="toast" ng-class="toaster.type" ng-click="remove(toaster.id)" ng-mouseover="stopTimer(toaster)">' +
+          '<div ng-class="config.title">{{toaster.title}}</div>' +
+          '<div ng-class="config.message" ng-switch on="toaster.bodyOutputType">' +
+            '<div ng-switch-when="trustedHtml" ng-bind-html="toaster.html"></div>' +
+            '<div ng-switch-when="template"><div ng-include="toaster.bodyTemplate"></div></div>' +
+            '<div ng-switch-default >{{toaster.body}}</div>' +
+          '</div>' +
+        '</div>' +
+    '</div>'
+  };
+}]);

--
Gitblit v1.3.2