Skip to content

Commit

Permalink
fix(users): form bugs in validation and submission
Browse files Browse the repository at this point in the history
This commit fixes the following bugs in the users and permission page:
 1. The feedbacks on inputs render properly using glyphicons.
 Eventually, we would like to rewrite using font-awesome, but that style
 change can be shipped later.
 2. Loading buttons now actually load.  Previously there was no binding
 of the `loading-state` and that has been corrected.
 3. The edit password form was able to submit without identical
 passwords.  An end-to-end test has been written to validate this, and
 the form now rejects with a no-matching-passwords error.

Additionally, a minor style improvement for the ui-select has landed,
given nicer close button placement on the multi-select.
  • Loading branch information
Jonathan Niles authored and jniles committed Oct 9, 2016
1 parent 0852758 commit e654052
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 114 deletions.
5 changes: 5 additions & 0 deletions client/src/css/structure.css
Original file line number Diff line number Diff line change
Expand Up @@ -780,3 +780,8 @@ growl-notification.fading.ng-leave.ng-leave-active {
.bg-gray {
background-color: #efefef;
}

/* Fix for ui-select multiselect close icon */
.ui-select-multiple.ui-select-bootstrap .ui-select-match .close {
line-height: 1;
}
13 changes: 7 additions & 6 deletions client/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -653,19 +653,20 @@
"PRICE_LIST" : "Select a price list",
"PROFIT_CENTER" : "Select profit center",
"PROJECT" : "Select a Project",
"PROVINCE" : "Select a province",
"PROJECTS" : "Select Projects",
"PROVINCE" : "Select a Province",
"REFERENCE" : "Select a Reference",
"REFERENCE_GROUP" : "Select a Reference Group",
"RESULT_ACCOUNT_SCT" : "Select a Result Account section",
"ROOT_ACCOUNT" : "Select a Root Account",
"SECTOR" : "Select a sector",
"SERVICE" : "Select a Service",
"SEX" : "Select a sex",
"TRANSFER_TYPE" : "Select a transfer type",
"TYPE" : "Select a type",
"SEX" : "Select a Sex",
"TRANSFER_TYPE" : "Select a Transfer type",
"TYPE" : "Select a Type",
"USER" : "Select User",
"VILLAGE" : "Select village",
"ZS" : "Select an health zone"
"VILLAGE" : "Select a Village",
"ZS" : "Select a Health Zone"
},
"VALIDATION": {
"DATE" : "Date mistyped",
Expand Down
42 changes: 25 additions & 17 deletions client/src/partials/users/UserEditPasswordModal.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<form name="PasswordForm" bh-submit="UsersPasswordModalCtrl.submit(PasswordForm)" bh-form-defaults novalidate>
<form name="PasswordForm" bh-submit="UsersPasswordModalCtrl.submit(PasswordForm)" bh-form-defaults novalidate data-edit-password-modal>
<div class="modal-header">
<h4>{{ 'USERS.SET_PASSWORD' | translate }}</h4>
</div>
Expand All @@ -7,33 +7,41 @@ <h4>{{ 'USERS.SET_PASSWORD' | translate }}</h4>
<div class="form-group has-feedback" ng-class="{ 'has-error' : PasswordForm.$submitted && (PasswordForm.password.$invalid || !UsersPasswordModalCtrl.validPassword())}">
<label class="control-label">{{ "FORM.LABELS.PASSWORD" | translate }}</label>
<input name="password" ng-model="UsersPasswordModalCtrl.user.password" class="form-control" type="password" required>
<i ng-if="PasswordForm.$submitted && (!UsersPasswordModalCtrl.validPassword()|| PasswordForm.password.$invalid)" class="fa fa-exclamation-triangle form-control-feedback" aria-hidden="true"></i>
<i ng-if="PasswordForm.$submitted && (UsersPasswordModalCtrl.validPassword() || !PasswordForm.password.$invalid)" class="fa fa-check-circle form-control-feedback" aria-hidden="true"></i>

<span ng-show="PasswordForm.password.$dirty && !UsersPasswordModalCtrl.validPassword()" class="glyphicon glyphicon-warning-sign form-control-feedback text-danger" aria-hidden="true"></span>
<span ng-show="PasswordForm.password.$dirty && UsersPasswordModalCtrl.validPassword()" class="glyphicon glyphicon-ok form-control-feedback text-success" aria-hidden="true"></span>

</div>

<div class="form-group has-feedback" ng-class="{ 'has-error' : PasswordForm.$submitted && (PasswordForm.passwordVerify.$invalid || !UsersPasswordModalCtrl.validPassword()) }">
<label class="control-label"> {{ "FORM.LABELS.PASSWORD" | translate }} ({{ "FORM.LABELS.RETYPE" | translate }})</label>
<input name="passwordVerify" ng-model="UsersPasswordModalCtrl.user.passwordVerify" class="form-control" type="password" required>
<i ng-if="PasswordForm.$submitted && (!UsersPasswordModalCtrl.validPassword()|| PasswordForm.passwordVerify.$invalid)" class="fa fa-exclamation-triangle form-control-feedback" aria-hidden="true"></i>
<i ng-if="PasswordForm.$submitted && (UsersPasswordModalCtrl.validPassword() || !PasswordForm.passwordVerify.$invalid)" class="fa fa-check-circle form-control-feedback" aria-hidden="true"></i>
<p class="help-block" ng-if="PasswordForm.$submitted && !UsersPasswordModalCtrl.validPassword()">
{{ "FORM.VALIDATION.PASSWORD_MATCH" | translate }}
</p>
<div class="help-block" ng-messages="PasswordForm.passwordVerify.$error" ng-show="PasswordForm.$submitted">
<div ng-messages-include="partials/templates/messages.tmpl.html"></div>
</div>
<label class="control-label">
{{ "FORM.LABELS.PASSWORD" | translate }} ({{ "FORM.LABELS.RETYPE" | translate }})
</label>

<input name="passwordVerify" ng-model="UsersPasswordModalCtrl.user.passwordVerify" class="form-control" type="password" required>

<span ng-show="PasswordForm.passwordVerify.$dirty && !UsersPasswordModalCtrl.validPassword()" class="glyphicon glyphicon-warning-sign form-control-feedback text-danger" aria-hidden="true"></span>
<span ng-show="PasswordForm.passwordVerify.$dirty && UsersPasswordModalCtrl.validPassword()" class="glyphicon glyphicon-ok form-control-feedback text-success" aria-hidden="true"></span>

<p class="help-block" ng-if="PasswordForm.$submitted && !UsersPasswordModalCtrl.validPassword()" data-no-password-match>
{{ "FORM.VALIDATION.PASSWORD_MATCH" | translate }}
</p>

<div class="help-block" ng-messages="PasswordForm.passwordVerify.$error" ng-show="PasswordForm.$submitted">
<div ng-messages-include="partials/templates/messages.tmpl.html"></div>
</div>
</div>
</div>

<div class="modal-footer">
<button
id="password-cancel" type="button"
class="btn btn-default" ng-click="UsersPasswordModalCtrl.cancel()">
<button type="button"
class="btn btn-default"
ng-click="UsersPasswordModalCtrl.cancel()"
data-method="cancel">
{{ "FORM.BUTTONS.CANCEL" | translate }}
</button>

<bh-loading-button id="password-submit">
<bh-loading-button loading-state="PasswordForm.$loading">
{{ "FORM.BUTTONS.SUBMIT" | translate }}
</bh-loading-button>
</div>
Expand Down
16 changes: 7 additions & 9 deletions client/src/partials/users/UserEditPasswordModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,25 @@ function UsersPasswordModalController($state, Users, Notify) {

// checks if a valid password exists
function validPassword() {
return vm.user.password === vm.user.passwordVerify;
return vm.user.password && vm.user.password.length && vm.user.password === vm.user.passwordVerify;
}

// submits the password form
function submit(passwordForm) {

if (passwordForm.$invalid) { return; }
if (!passwordForm.$dirty) { return; }
if (!passwordForm.$dirty || !validPassword()) { return; }

// try to update the user's password
Users.updatePassword(vm.user.id, { password : vm.user.password })
.then(function () {
Notify.success('USERS.UPDATED');
$state.go('users.edit', {id : vm.user.id, creating : false}, {reload : false});
})
.catch(Notify.handleError);
.then(function () {
Notify.success('USERS.UPDATED');
$state.go('users.edit', {id : vm.user.id, creating : false}, {reload : false});
})
.catch(Notify.handleError);
}

function cancel() {
$state.go('users.edit', {id : vm.user.id, creating : false}, {reload : false});
// $uibModalInstance.dismiss();
}

Users.read($state.params.id)
Expand Down
57 changes: 40 additions & 17 deletions client/src/partials/users/user.modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@

<ui-select multiple name="projects" ng-model="UserModalCtrl.user.projects" theme="bootstrap" close-on-select="false" required>

<ui-select-match placeholder="{{ 'USERS.SELECT_PROJECTS' | translate }}">
<ui-select-match placeholder="{{ 'FORM.SELECT.PROJECTS' | translate }}">
<span>{{$item.name}}</span>
</ui-select-match>

Expand All @@ -65,38 +65,61 @@
</button>
</div>

<div ng-if="UserModalCtrl.isCreating" class="form-group has-feedback" ng-class="{ 'has-error' : UserForm.$submitted && (UserForm.password.$invalid || !UserModalCtrl.validPassword()) }">
<label class="control-label">{{ "FORM.LABELS.PASSWORD" | translate }}</label>
<input name="password" ng-model="UserModalCtrl.user.password" class="form-control" type="password" required>
<i ng-if="!UserModalCtrl.validPassword() || UserForm.password.$invalid" class="fa fa-exclamation-triangle form-control-feedback" aria-hidden="true"></i>
<i ng-if="UserModalCtrl.validPassword() || !UserForm.password.$invalid" class="fa fa-check-circle form-control-feedback" aria-hidden="true"></i>
<div class="help-block" ng-messages="UserForm.password.$error" ng-show="UserForm.$submitted">
<div ng-messages-include="partials/templates/messages.tmpl.html"></div>
<div ng-if="UserModalCtrl.isCreating">

<div class="form-group has-feedback"
ng-class="{ 'has-error' : UserForm.$submitted && !UserModalCtrl.validPassword() }"
>
<label class="control-label">
{{ "FORM.LABELS.PASSWORD" | translate }}
</label>

<input name="password" ng-model="UserModalCtrl.user.password" class="form-control" type="password" required>

<span ng-show="UserForm.password.$dirty && !UserModalCtrl.validPassword()" class="glyphicon glyphicon-warning-sign form-control-feedback text-danger" aria-hidden="true"></span>
<span ng-show="UserForm.password.$dirty && UserModalCtrl.validPassword()" class="glyphicon glyphicon-ok form-control-feedback text-success" aria-hidden="true"></span>

<div class="help-block" ng-messages="UserForm.password.$error" ng-show="UserForm.$submitted">
<div ng-messages-include="partials/templates/messages.tmpl.html"></div>
</div>
</div>
</div>

<div ng-if="UserModalCtrl.isCreating" class="form-group has-feedback" ng-class="{ 'has-error' : UserForm.$submitted && (UserForm.password.$invalid || !UserModalCtrl.validPassword()) }">
<label class="control-label"> {{ "FORM.LABELS.PASSWORD" | translate }} ({{ "FORM.LABELS.RETYPE" | translate }})</label>
<div class="form-group has-feedback"
ng-class="{ 'has-error' : UserForm.$submitted && !UserModalCtrl.validPassword() }"
>
<label class="control-label">
{{ "FORM.LABELS.PASSWORD" | translate }} ({{ "FORM.LABELS.RETYPE" | translate }})
</label>

<input name="passwordVerify" ng-model="UserModalCtrl.user.passwordVerify" class="form-control" type="password" required>
<i ng-if="!UserModalCtrl.validPassword()|| UserForm.password.$invalid" class="fa fa-exclamation-triangle form-control-feedback" aria-hidden="true"></i>
<i ng-if="UserModalCtrl.validPassword() || !UserForm.password.$invalid" class="fa fa-check-circle form-control-feedback" aria-hidden="true"></i>
<p class="help-block" ng-if="UserForm.$submitted && !UserModalCtrl.validPassword()">

<span ng-show="UserForm.passwordVerify.$dirty && !UserModalCtrl.validPassword()" class="glyphicon glyphicon-warning-sign form-control-feedback text-danger" aria-hidden="true"></span>
<span ng-show="UserForm.passwordVerify.$dirty && UserModalCtrl.validPassword()" class="glyphicon glyphicon-ok form-control-feedback text-success" aria-hidden="true"></span>

<p class="help-block" ng-if="UserForm.$submitted && !UserModalCtrl.validPassword()" data-no-password-match>
{{ "FORM.VALIDATION.PASSWORD_MATCH" | translate }}
</p>

<div class="help-block" ng-messages="UserForm.passwordVerify.$error" ng-show="UserForm.$submitted">
<div ng-messages-include="partials/templates/messages.tmpl.html"></div>
</div>
</div>
</div>
</div>

<div class="modal-footer">
<p id="user-same" ng-if="UserForm.$submitted && UserForm.$pristine && !UserModalCtrl.isCreating" class="text-warning"><i class="fa fa-warning"></i> {{ "USERS.RECORD_SAME" | translate }}</p>
<p ng-if="UserForm.$submitted && UserForm.$invalid" class="text-danger"><i class="fa fa-exclamation-triangle"></i> {{ "FORM.ERRORS.RECORD_ERROR" | translate }}</p>
<p id="user-same" ng-if="UserForm.$submitted && UserForm.$pristine && !UserModalCtrl.isCreating" class="text-warning">
<i class="fa fa-warning"></i> {{ "USERS.RECORD_SAME" | translate }}
</p>
<p ng-if="UserForm.$submitted && UserForm.$invalid" class="text-danger">
<i class="fa fa-exclamation-triangle"></i> {{ "FORM.ERRORS.RECORD_ERROR" | translate }}
</p>

<button id="user-cancel" type="button" class="btn btn-default" ng-click="UserModalCtrl.closeModal()">
<i class="fa fa-ban"></i> {{ 'FORM.BUTTONS.CANCEL' | translate }}
</button>
<bh-loading-button id="user-submit">

<bh-loading-button loading-state="UserForm.$loading">
<i class="fa fa-ok"></i> {{ 'FORM.BUTTONS.SUBMIT' | translate }}
</bh-loading-button>
</div>
Expand Down
35 changes: 18 additions & 17 deletions client/src/partials/users/user.modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,48 +16,49 @@ function UserModalController($state, Projects, Users, Notify) {
vm.validPassword = validPassword;
vm.editPassword = editPassword;

Projects.read().then(function (data) {
vm.projects = data;
})
.catch(Notify.handleError);
Projects.read()
.then(function (projects) {
vm.projects = projects;
})
.catch(Notify.handleError);

if(!vm.isCreating){
if (!vm.isCreating) {

Users.read($state.params.id)
.then(function (user) {
vm.user = user;
})
.catch(Notify.handleError);
}else{
} else {
vm.user.projects = [];
}

// submit the data to the server from all two forms (update, create)
function submit(userForm) {
var promise;

if (userForm.$invalid) { return; }
if (!userForm.$dirty) { return; }

var promise;

promise = (vm.isCreating)? Users.create(vm.user) : Users.update(vm.user.id, vm.user);

promise.then(function () {
var translateKey = (vm.isCreating) ? 'USERS.CREATED' : 'USERS.UPDATED';
promise = (vm.isCreating) ? Users.create(vm.user) : Users.update(vm.user.id, vm.user);

Notify.success(translateKey);
$state.go('users.list', null, {reload : true});
})
.catch(Notify.handleError);
promise
.then(function () {
var translateKey = (vm.isCreating) ? 'USERS.CREATED' : 'USERS.UPDATED';
Notify.success(translateKey);
$state.go('users.list', null, {reload : true});
})
.catch(Notify.handleError);
}

function closeModal (){
function closeModal () {
$state.transitionTo('users.list');
}

// make sure that the passwords exist and match.
function validPassword() {
return vm.user.password === vm.user.passwordVerify;
return vm.user.password && vm.user.password.length && vm.user.password === vm.user.passwordVerify;
}

// opens a new modal to let the user set a password
Expand Down
8 changes: 4 additions & 4 deletions client/src/partials/users/userPermission.modal.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<form name="PermissionForm" bh-submit="UserPermissionModalCtrl.submit()">
<form name="PermissionForm" bh-submit="UserPermissionModalCtrl.submit()" bh-form-defaults>
<div class="modal-header">
<ol class="headercrumb">
<li class="title">
Expand All @@ -10,7 +10,7 @@
</div>

<div class="modal-body">
<ul class="list-group" style="max-height: 600px; overflow:auto;">
<ul class="list-group" style="max-height: 70vh; overflow:auto;">
<li class="list-group-item">
<div class="checkbox">
<label>
Expand All @@ -34,8 +34,8 @@
<button type="button" class="btn btn-default" ng-click="UserPermissionModalCtrl.closeModal()">
<i class="fa fa-ban"></i> {{ 'FORM.BUTTONS.CANCEL' | translate }}
</button>
<bh-loading-button>
<bh-loading-button loading-state="PermissionForm.$loading">
<i class="fa fa-floppy-o"></i> {{ "FORM.BUTTONS.SAVE" | translate }}
</bh-loading-button>
</div>
</form>
</form>
3 changes: 1 addition & 2 deletions client/src/partials/users/users.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
type="button"
class="btn btn-default"
ui-sref="users.create"
data-method="create"
id="user-create">
data-method="create">
<span class="fa fa-plus"></span> {{ "USERS.ADD_USER" | translate }}
</button>
</div>
Expand Down
2 changes: 1 addition & 1 deletion client/src/partials/users/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,5 @@ function UsersController($state, Users, Notify) {
.catch(Notify.handleError);
}

loadGrid();
loadGrid();
}

0 comments on commit e654052

Please sign in to comment.