mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-16 01:21:48 +01:00
Form and field directives, form loading spinner
This commit is contained in:
parent
8716c50f81
commit
d78dfac43c
@ -4,6 +4,7 @@
|
||||
'angular-jwt',
|
||||
'ngAnimate',
|
||||
|
||||
'bit.directives',
|
||||
'bit.services',
|
||||
|
||||
'bit.global',
|
||||
|
2
src/popup/app/directives/directivesModule.js
Normal file
2
src/popup/app/directives/directivesModule.js
Normal file
@ -0,0 +1,2 @@
|
||||
angular
|
||||
.module('bit.directives', []);
|
30
src/popup/app/directives/fieldDirective.js
Normal file
30
src/popup/app/directives/fieldDirective.js
Normal 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;
|
||||
}
|
||||
};
|
||||
});
|
35
src/popup/app/directives/formDirective.js
Normal file
35
src/popup/app/directives/formDirective.js
Normal 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');
|
||||
});
|
||||
}
|
||||
});
|
@ -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;
|
||||
});
|
||||
|
62
src/popup/app/services/validationService.js
Normal file
62
src/popup/app/services/validationService.js
Normal 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;
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user