Form and field directives, form loading spinner

This commit is contained in:
Kyle Spearrin 2016-09-10 17:13:29 -04:00
parent 8716c50f81
commit d78dfac43c
11 changed files with 234 additions and 42 deletions

View File

@ -4,6 +4,7 @@
'angular-jwt',
'ngAnimate',
'bit.directives',
'bit.services',
'bit.global',

View File

@ -0,0 +1,2 @@
angular
.module('bit.directives', []);

View File

@ -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;
}
};
});

View File

@ -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');
});
}
});

View File

@ -4,23 +4,25 @@
.factory('cipherService', function (cryptoService, $q) {
var _service = {};
_service.encryptSite = function (site, callback) {
_service.encryptSite = function (site) {
var model = {};
cryptoService.encrypt(site.name, function (nameCipherString) {
model.name = nameCipherString;
cryptoService.encrypt(site.uri, function (uriCipherString) {
model.uri = uriCipherString;
cryptoService.encrypt(site.username, function (usernameCipherString) {
model.username = usernameCipherString;
cryptoService.encrypt(site.password, function (passwordCipherString) {
model.password = passwordCipherString;
cryptoService.encrypt(site.notes, function (notesCipherString) {
model.notes = notesCipherString;
callback(model);
});
});
});
return $q(function (resolve, reject) {
encrypt(site.name).then(function (cs) {
model.name = cs;
return encrypt(site.uri);
}).then(function (cs) {
model.uri = cs;
return encrypt(site.username);
}).then(function (cs) {
model.username = cs;
return encrypt(site.password);
}).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;
});

View File

@ -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;
});

View File

@ -1,16 +1,29 @@
angular
.module('bit.vault')
.controller('vaultAddSiteController', function ($scope, $state, siteService, cipherService) {
.controller('vaultAddSiteController', function ($scope, $state, siteService, cipherService, $q) {
$scope.site = {
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) {
cipherService.encryptSite(model, function (siteModel) {
$scope.savePromise = cipherService.encryptSite(model).then(function (siteModel) {
var site = new Site(siteModel, true);
siteService.saveWithServer(site, function () {
$scope.close();
return site;
}).then(function (site) {
return saveSite(site, function (site) {
alert('Saved ' + site.id + '!');
});
});
};
@ -18,4 +31,14 @@
$scope.close = function () {
$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);
});
});
}
});

View File

@ -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="left">
<a ng-click="close()" href>Close</a>
<a ng-click="close()" href="">Close</a>
</div>
<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 class="title">Add Site</div>
</div>
@ -17,19 +18,19 @@
<div class="list-section-items">
<div class="list-section-item">
<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 class="list-section-item">
<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 class="list-section-item">
<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 class="list-section-item">
<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>
<a class="list-section-item" href="#">
Generate Password
@ -41,7 +42,7 @@
<div class="list-section-items">
<div class="list-section-item">
<label for="folder">Folder</label>
<select id="folder">
<select id="folder" name="FolderId">
<option>Blue</option>
<option selected>Green</option>
<option>Red</option>
@ -49,7 +50,7 @@
</div>
<div class="list-section-item list-section-item-checkbox">
<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>
@ -59,7 +60,7 @@
</div>
<div class="list-section-items">
<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>

View File

@ -26,10 +26,15 @@
<script src="../models/dataModels.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/backgroundService.js"></script>
<script src="app/services/loginService.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/mainController.js"></script>

View File

@ -42,6 +42,12 @@
display: block;
float: left;
}
.fa-spinner {
padding: 15px;
display: block;
float: left;
}
}
.right {
@ -54,6 +60,12 @@
display: block;
float: right;
}
.fa-spinner {
padding: 15px;
display: block;
float: right;
}
}
}

View File

@ -51,19 +51,19 @@ function initSiteService() {
});
};
SiteService.prototype.saveWithServer = function (site, callback) {
if (!callback || typeof callback !== 'function') {
throw 'callback function required';
}
SiteService.prototype.saveWithServer = function (site, successCallback, errorCallback) {
var self = this,
request = new SiteRequest(site);
if (!site.id) {
self.apiService.postSite(request, apiSuccess, handleError);
self.apiService.postSite(request, apiSuccess, function (response) {
handleError(response, errorCallback)
});
}
else {
self.apiService.putSite(site.id, request, apiSuccess, handleError);
self.apiService.putSite(site.id, request, apiSuccess, function (response) {
handleError(response, errorCallback)
});
}
function apiSuccess(response) {
@ -71,7 +71,9 @@ function initSiteService() {
userService.getUserId(function (userId) {
var data = new SiteData(response, userId);
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') {
throw 'callback function required';
}
var self = this;
self.apiService.deleteCipher(id, function (response) {
self.delete(id, callback);
}, handleError);
self.delete(id, successCallback);
}, function (response) {
handleError(response, errorCallback)
});
};
function handleError() {
// TODO: check for unauth or forbidden and logout
function handleError(error, callback) {
if (error.status == 401 || error.status == 403) {
// TODO: logout
}
if (callback) {
callback(error);
}
}
};