Form and field directives, form loading spinner
This commit is contained in:
parent
8716c50f81
commit
d78dfac43c
|
@ -4,6 +4,7 @@
|
||||||
'angular-jwt',
|
'angular-jwt',
|
||||||
'ngAnimate',
|
'ngAnimate',
|
||||||
|
|
||||||
|
'bit.directives',
|
||||||
'bit.services',
|
'bit.services',
|
||||||
|
|
||||||
'bit.global',
|
'bit.global',
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
angular
|
||||||
|
.module('bit.directives', []);
|
|
@ -0,0 +1,30 @@
|
||||||
|
angular
|
||||||
|
.module('bit.directives')
|
||||||
|
|
||||||
|
.directive('bitField', function () {
|
||||||
|
var linkFn = function (scope, element, attrs, ngModel) {
|
||||||
|
ngModel.$registerError = registerError;
|
||||||
|
ngModel.$validators.validate = validator;
|
||||||
|
|
||||||
|
function validator() {
|
||||||
|
ngModel.$setValidity('bit', true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerError() {
|
||||||
|
ngModel.$setValidity('bit', false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
require: 'ngModel',
|
||||||
|
restrict: 'A',
|
||||||
|
compile: function (elem, attrs) {
|
||||||
|
if (!attrs.name || attrs.name === '') {
|
||||||
|
throw 'bit-field element does not have a valid name attribute';
|
||||||
|
}
|
||||||
|
|
||||||
|
return linkFn;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
|
@ -0,0 +1,35 @@
|
||||||
|
angular
|
||||||
|
.module('bit.directives')
|
||||||
|
|
||||||
|
.directive('bitForm', function ($rootScope, validationService) {
|
||||||
|
return {
|
||||||
|
require: 'form',
|
||||||
|
restrict: 'A',
|
||||||
|
link: function (scope, element, attrs, formCtrl) {
|
||||||
|
var watchPromise = attrs.bitForm || null;
|
||||||
|
if (watchPromise) {
|
||||||
|
scope.$watch(watchPromise, formSubmitted.bind(null, formCtrl, scope));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function formSubmitted(form, scope, promise) {
|
||||||
|
if (!promise || !promise.then) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset errors
|
||||||
|
form.$errors = null;
|
||||||
|
|
||||||
|
// start loading
|
||||||
|
form.$loading = true;
|
||||||
|
|
||||||
|
promise.then(function success(response) {
|
||||||
|
form.$loading = false;
|
||||||
|
}, function failure(reason) {
|
||||||
|
form.$loading = false;
|
||||||
|
validationService.addErrors(form, reason);
|
||||||
|
scope.$broadcast('show-errors-check-validity');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -4,23 +4,25 @@
|
||||||
.factory('cipherService', function (cryptoService, $q) {
|
.factory('cipherService', function (cryptoService, $q) {
|
||||||
var _service = {};
|
var _service = {};
|
||||||
|
|
||||||
_service.encryptSite = function (site, callback) {
|
_service.encryptSite = function (site) {
|
||||||
var model = {};
|
var model = {};
|
||||||
|
|
||||||
cryptoService.encrypt(site.name, function (nameCipherString) {
|
return $q(function (resolve, reject) {
|
||||||
model.name = nameCipherString;
|
encrypt(site.name).then(function (cs) {
|
||||||
cryptoService.encrypt(site.uri, function (uriCipherString) {
|
model.name = cs;
|
||||||
model.uri = uriCipherString;
|
return encrypt(site.uri);
|
||||||
cryptoService.encrypt(site.username, function (usernameCipherString) {
|
}).then(function (cs) {
|
||||||
model.username = usernameCipherString;
|
model.uri = cs;
|
||||||
cryptoService.encrypt(site.password, function (passwordCipherString) {
|
return encrypt(site.username);
|
||||||
model.password = passwordCipherString;
|
}).then(function (cs) {
|
||||||
cryptoService.encrypt(site.notes, function (notesCipherString) {
|
model.username = cs;
|
||||||
model.notes = notesCipherString;
|
return encrypt(site.password);
|
||||||
callback(model);
|
}).then(function (cs) {
|
||||||
});
|
model.password = cs;
|
||||||
});
|
return encrypt(site.notes);
|
||||||
});
|
}).then(function (cs) {
|
||||||
|
model.notes = cs;
|
||||||
|
resolve(model);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -73,5 +75,13 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function encrypt(plaintextString) {
|
||||||
|
return $q(function (resolve, reject) {
|
||||||
|
cryptoService.encrypt(plaintextString, function (cipherString) {
|
||||||
|
resolve(cipherString);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return _service;
|
return _service;
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
angular
|
||||||
|
.module('bit.services')
|
||||||
|
|
||||||
|
.factory('validationService', function () {
|
||||||
|
var _service = {};
|
||||||
|
|
||||||
|
_service.addErrors = function (form, reason) {
|
||||||
|
var data = reason.data;
|
||||||
|
var defaultErrorMessage = 'An unexpected error has occured.';
|
||||||
|
form.$errors = [];
|
||||||
|
|
||||||
|
if (!data || !angular.isObject(data)) {
|
||||||
|
form.$errors.push(defaultErrorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.validationErrors) {
|
||||||
|
if (data.message) {
|
||||||
|
form.$errors.push(data.message);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
form.$errors.push(defaultErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var key in data.validationErrors) {
|
||||||
|
if (!data.validationErrors.hasOwnProperty(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < data.validationErrors[key].length; i++) {
|
||||||
|
_service.addError(form, key, data.validationErrors[key][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_service.addError = function (form, key, errorMessage, clearExistingErrors) {
|
||||||
|
if (clearExistingErrors || !form.$errors) {
|
||||||
|
form.$errors = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var pushError = true;
|
||||||
|
for (var i = 0; i < form.$errors.length; i++) {
|
||||||
|
if (form.$errors[i] === errorMessage) {
|
||||||
|
pushError = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pushError) {
|
||||||
|
form.$errors.push(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key && key !== '' && form[key] && form[key].$registerError) {
|
||||||
|
form[key].$registerError();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return _service;
|
||||||
|
});
|
|
@ -1,16 +1,29 @@
|
||||||
angular
|
angular
|
||||||
.module('bit.vault')
|
.module('bit.vault')
|
||||||
|
|
||||||
.controller('vaultAddSiteController', function ($scope, $state, siteService, cipherService) {
|
.controller('vaultAddSiteController', function ($scope, $state, siteService, cipherService, $q) {
|
||||||
$scope.site = {
|
$scope.site = {
|
||||||
folderId: null
|
folderId: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$('#name').focus();
|
||||||
|
$('.list-section-item').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$(this).find('input[type="text"], textarea, select').focus();
|
||||||
|
var checkbox = $(this).find('input[type="checkbox"]');
|
||||||
|
if (checkbox.length > 0) {
|
||||||
|
checkbox.prop('checked', !checkbox.is(':checked'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$scope.savePromise = null;
|
||||||
$scope.save = function (model) {
|
$scope.save = function (model) {
|
||||||
cipherService.encryptSite(model, function (siteModel) {
|
$scope.savePromise = cipherService.encryptSite(model).then(function (siteModel) {
|
||||||
var site = new Site(siteModel, true);
|
var site = new Site(siteModel, true);
|
||||||
siteService.saveWithServer(site, function () {
|
return site;
|
||||||
$scope.close();
|
}).then(function (site) {
|
||||||
|
return saveSite(site, function (site) {
|
||||||
|
alert('Saved ' + site.id + '!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -18,4 +31,14 @@
|
||||||
$scope.close = function () {
|
$scope.close = function () {
|
||||||
$state.go('tabs.vault', { animation: 'out-slide-down' });
|
$state.go('tabs.vault', { animation: 'out-slide-down' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function saveSite(site) {
|
||||||
|
return $q(function (resolve, reject) {
|
||||||
|
siteService.saveWithServer(site, function (site) {
|
||||||
|
resolve(site);
|
||||||
|
}, function (error) {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<form name="theForm" ng-submit="theForm.$valid && save(site)">
|
<form name="theForm" ng-submit="theForm.$valid && save(site)" bit-form="savePromise">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<a ng-click="close()" href>Close</a>
|
<a ng-click="close()" href="">Close</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<button type="submit" class="btn btn-link">Save</button>
|
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">Save</button>
|
||||||
|
<i class="fa fa-spinner fa-lg" ng-show="theForm.$loading" ng-class="{'fa-spin' : theForm.$loading}"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="title">Add Site</div>
|
<div class="title">Add Site</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,19 +18,19 @@
|
||||||
<div class="list-section-items">
|
<div class="list-section-items">
|
||||||
<div class="list-section-item">
|
<div class="list-section-item">
|
||||||
<label for="name">Name</label>
|
<label for="name">Name</label>
|
||||||
<input id="name" type="text" ng-model="site.name">
|
<input id="name" type="text" name="Name" ng-model="site.name" bit-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-section-item">
|
<div class="list-section-item">
|
||||||
<label for="uri">URI</label>
|
<label for="uri">URI</label>
|
||||||
<input id="uri" type="text" ng-model="site.uri">
|
<input id="uri" type="text" name="Uri" ng-model="site.uri" bit-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-section-item">
|
<div class="list-section-item">
|
||||||
<label for="username">Username</label>
|
<label for="username">Username</label>
|
||||||
<input id="username" type="text" ng-model="site.username">
|
<input id="username" type="text" name="Username" ng-model="site.username" bit-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-section-item">
|
<div class="list-section-item">
|
||||||
<label for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
<input id="password" type="password" ng-model="site.password">
|
<input id="password" type="password" name="Password" ng-model="site.password" bit-field>
|
||||||
</div>
|
</div>
|
||||||
<a class="list-section-item" href="#">
|
<a class="list-section-item" href="#">
|
||||||
Generate Password
|
Generate Password
|
||||||
|
@ -41,7 +42,7 @@
|
||||||
<div class="list-section-items">
|
<div class="list-section-items">
|
||||||
<div class="list-section-item">
|
<div class="list-section-item">
|
||||||
<label for="folder">Folder</label>
|
<label for="folder">Folder</label>
|
||||||
<select id="folder">
|
<select id="folder" name="FolderId">
|
||||||
<option>Blue</option>
|
<option>Blue</option>
|
||||||
<option selected>Green</option>
|
<option selected>Green</option>
|
||||||
<option>Red</option>
|
<option>Red</option>
|
||||||
|
@ -49,7 +50,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="list-section-item list-section-item-checkbox">
|
<div class="list-section-item list-section-item-checkbox">
|
||||||
<label for="favorite">Favorite</label>
|
<label for="favorite">Favorite</label>
|
||||||
<input id="favorite" type="checkbox" ng-model="site.favorite">
|
<input id="favorite" name="Favorite" type="checkbox" ng-model="site.favorite" bit-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,7 +60,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="list-section-items">
|
<div class="list-section-items">
|
||||||
<div class="list-section-item">
|
<div class="list-section-item">
|
||||||
<textarea id="notes" rows="5" ng-model="site.notes"></textarea>
|
<textarea id="notes" name="Notes" rows="5" ng-model="site.notes" bit-field></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -26,10 +26,15 @@
|
||||||
<script src="../models/dataModels.js"></script>
|
<script src="../models/dataModels.js"></script>
|
||||||
<script src="../models/domainModels.js"></script>
|
<script src="../models/domainModels.js"></script>
|
||||||
|
|
||||||
|
<script src="app/directives/directivesModule.js"></script>
|
||||||
|
<script src="app/directives/formDirective.js"></script>
|
||||||
|
<script src="app/directives/fieldDirective.js"></script>
|
||||||
|
|
||||||
<script src="app/services/servicesModule.js"></script>
|
<script src="app/services/servicesModule.js"></script>
|
||||||
<script src="app/services/backgroundService.js"></script>
|
<script src="app/services/backgroundService.js"></script>
|
||||||
<script src="app/services/loginService.js"></script>
|
<script src="app/services/loginService.js"></script>
|
||||||
<script src="app/services/cipherService.js"></script>
|
<script src="app/services/cipherService.js"></script>
|
||||||
|
<script src="app/services/validationService.js"></script>
|
||||||
|
|
||||||
<script src="app/global/globalModule.js"></script>
|
<script src="app/global/globalModule.js"></script>
|
||||||
<script src="app/global/mainController.js"></script>
|
<script src="app/global/mainController.js"></script>
|
||||||
|
|
|
@ -42,6 +42,12 @@
|
||||||
display: block;
|
display: block;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fa-spinner {
|
||||||
|
padding: 15px;
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
|
@ -54,6 +60,12 @@
|
||||||
display: block;
|
display: block;
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fa-spinner {
|
||||||
|
padding: 15px;
|
||||||
|
display: block;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,19 +51,19 @@ function initSiteService() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
SiteService.prototype.saveWithServer = function (site, callback) {
|
SiteService.prototype.saveWithServer = function (site, successCallback, errorCallback) {
|
||||||
if (!callback || typeof callback !== 'function') {
|
|
||||||
throw 'callback function required';
|
|
||||||
}
|
|
||||||
|
|
||||||
var self = this,
|
var self = this,
|
||||||
request = new SiteRequest(site);
|
request = new SiteRequest(site);
|
||||||
|
|
||||||
if (!site.id) {
|
if (!site.id) {
|
||||||
self.apiService.postSite(request, apiSuccess, handleError);
|
self.apiService.postSite(request, apiSuccess, function (response) {
|
||||||
|
handleError(response, errorCallback)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.apiService.putSite(site.id, request, apiSuccess, handleError);
|
self.apiService.putSite(site.id, request, apiSuccess, function (response) {
|
||||||
|
handleError(response, errorCallback)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function apiSuccess(response) {
|
function apiSuccess(response) {
|
||||||
|
@ -71,7 +71,9 @@ function initSiteService() {
|
||||||
userService.getUserId(function (userId) {
|
userService.getUserId(function (userId) {
|
||||||
var data = new SiteData(response, userId);
|
var data = new SiteData(response, userId);
|
||||||
self.upsert(data, function () {
|
self.upsert(data, function () {
|
||||||
callback(site);
|
if (successCallback) {
|
||||||
|
successCallback(site);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -161,18 +163,27 @@ function initSiteService() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
SiteService.prototype.deleteWithServer = function (id, callback) {
|
SiteService.prototype.deleteWithServer = function (id, successCallback, errorCallback) {
|
||||||
if (!callback || typeof callback !== 'function') {
|
if (!callback || typeof callback !== 'function') {
|
||||||
throw 'callback function required';
|
throw 'callback function required';
|
||||||
}
|
}
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
self.apiService.deleteCipher(id, function (response) {
|
self.apiService.deleteCipher(id, function (response) {
|
||||||
self.delete(id, callback);
|
self.delete(id, successCallback);
|
||||||
}, handleError);
|
}, function (response) {
|
||||||
|
handleError(response, errorCallback)
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleError() {
|
function handleError(error, callback) {
|
||||||
// TODO: check for unauth or forbidden and logout
|
if (error.status == 401 || error.status == 403) {
|
||||||
|
// TODO: logout
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
callback(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue