Skip to content
This repository has been archived by the owner on Aug 30, 2021. It is now read-only.

Commit

Permalink
feat(core): add notification feedback with angular-ui-notification (#…
Browse files Browse the repository at this point in the history
…1532)

Added visual notification for user/article updates
angular-ui-notification config added to core client config
Notification idea from #369
  • Loading branch information
sujeethk authored and mleanos committed Oct 10, 2016
1 parent 5725c44 commit 607ed06
Show file tree
Hide file tree
Showing 26 changed files with 122 additions and 137 deletions.
1 change: 1 addition & 0 deletions bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"angular-messages": "~1.5.0",
"angular-mocks": "~1.5.0",
"angular-resource": "~1.5.0",
"angular-ui-notification": "~0.2.0",
"angular-ui-router": "~0.2.18",
"bootstrap": "~3.3.6",
"ng-file-upload": "^12.1.0",
Expand Down
4 changes: 3 additions & 1 deletion config/assets/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ module.exports = {
// bower:css
'public/lib/bootstrap/dist/css/bootstrap.css',
'public/lib/bootstrap/dist/css/bootstrap-theme.css',
'public/lib/ng-img-crop/compile/unminified/ng-img-crop.css'
'public/lib/ng-img-crop/compile/unminified/ng-img-crop.css',
'public/lib/angular-ui-notification/dist/angular-ui-notification.css'
// endbower
],
js: [
Expand All @@ -22,6 +23,7 @@ module.exports = {
'public/lib/angular-messages/angular-messages.js',
'public/lib/angular-mocks/angular-mocks.js',
'public/lib/angular-resource/angular-resource.js',
'public/lib/angular-ui-notification/dist/angular-ui-notification.js',
'public/lib/angular-ui-router/release/angular-ui-router.js',
'public/lib/owasp-password-strength-test/owasp-password-strength-test.js',
// endbower
Expand Down
2 changes: 2 additions & 0 deletions config/assets/production.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
'public/lib/bootstrap/dist/css/bootstrap.min.css',
'public/lib/bootstrap/dist/css/bootstrap-theme.min.css',
'public/lib/ng-img-crop/compile/minified/ng-img-crop.css',
'public/lib/angular-ui-notification/dist/angular-ui-notification.min.css'
// endbower
],
js: [
Expand All @@ -20,6 +21,7 @@ module.exports = {
'public/lib/angular-messages/angular-messages.min.js',
'public/lib/angular-mocks/angular-mocks.js',
'public/lib/angular-resource/angular-resource.min.js',
'public/lib/angular-ui-notification/dist/angular-ui-notification.min.js',
'public/lib/angular-ui-router/release/angular-ui-router.min.js',
'public/lib/ng-file-upload/ng-file-upload.min.js',
'public/lib/ng-img-crop/compile/minified/ng-img-crop.js',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
.module('articles.admin')
.controller('ArticlesAdminController', ArticlesAdminController);

ArticlesAdminController.$inject = ['$scope', '$state', '$window', 'articleResolve', 'Authentication'];
ArticlesAdminController.$inject = ['$scope', '$state', '$window', 'articleResolve', 'Authentication', 'Notification'];

function ArticlesAdminController($scope, $state, $window, article, Authentication) {
function ArticlesAdminController($scope, $state, $window, article, Authentication, Notification) {
var vm = this;

vm.article = article;
vm.authentication = Authentication;
vm.error = null;
vm.form = {};
vm.remove = remove;
vm.save = save;
Expand All @@ -22,6 +21,7 @@
if ($window.confirm('Are you sure you want to delete?')) {
vm.article.$remove(function() {
$state.go('admin.articles.list');
Notification.success({ message: '<i class="glyphicon glyphicon-ok"></i> Article deleted successfully!' });
});
}
}
Expand All @@ -40,10 +40,11 @@

function successCallback(res) {
$state.go('admin.articles.list'); // should we send the User to the list or the updated Article's view?
Notification.success({ message: '<i class="glyphicon glyphicon-ok"></i> Article saved successfully!' });
}

function errorCallback(res) {
vm.error = res.data.message;
Notification.error({ message: res.data.message, title: '<i class="glyphicon glyphicon-remove"></i> Article save error!' });
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

vm.article = article;
vm.authentication = Authentication;
vm.error = null;

}
}());
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ <h1>{{vm.article._id ? 'Edit Article' : 'New Article'}}</h1>
<div class="form-group">
<button type="submit" class="btn btn-default">{{vm.article._id ? 'Update' : 'Create'}}</button>
</div>
<div ng-show="vm.error" class="text-danger">
<strong ng-bind="vm.error"></strong>
</div>
</fieldset>
</form>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
$state,
Authentication,
ArticlesService,
mockArticle;
mockArticle,
Notification;

// The $resource service augments the response object with methods for updating and deleting the resource.
// If we were to use the standard toEqual matcher, our tests would fail because the test values would not match
Expand All @@ -36,7 +37,7 @@
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable
// with the same name as the service.
beforeEach(inject(function ($controller, $rootScope, _$state_, _$httpBackend_, _Authentication_, _ArticlesService_) {
beforeEach(inject(function ($controller, $rootScope, _$state_, _$httpBackend_, _Authentication_, _ArticlesService_, _Notification_) {
// Set a new global scope
$scope = $rootScope.$new();

Expand All @@ -45,6 +46,7 @@
$state = _$state_;
Authentication = _Authentication_;
ArticlesService = _ArticlesService_;
Notification = _Notification_;

// create mock article
mockArticle = new ArticlesService({
Expand All @@ -66,6 +68,8 @@

// Spy on state go
spyOn($state, 'go');
spyOn(Notification, 'error');
spyOn(Notification, 'success');
}));

describe('vm.save() as create', function () {
Expand All @@ -89,11 +93,13 @@
$scope.vm.save(true);
$httpBackend.flush();

// Test Notification success was called
expect(Notification.success).toHaveBeenCalledWith({ message: '<i class="glyphicon glyphicon-ok"></i> Article saved successfully!' });
// Test URL redirection after the article was created
expect($state.go).toHaveBeenCalledWith('admin.articles.list');
}));

it('should set $scope.vm.error if error', function () {
it('should call Notification.error if error', function () {
var errorMessage = 'this is an error message';
$httpBackend.expectPOST('api/articles', sampleArticlePostData).respond(400, {
message: errorMessage
Expand All @@ -102,7 +108,7 @@
$scope.vm.save(true);
$httpBackend.flush();

expect($scope.vm.error).toBe(errorMessage);
expect(Notification.error).toHaveBeenCalledWith({ message: errorMessage, title: '<i class="glyphicon glyphicon-remove"></i> Article save error!' });
});
});

Expand All @@ -120,11 +126,13 @@
$scope.vm.save(true);
$httpBackend.flush();

// Test Notification success was called
expect(Notification.success).toHaveBeenCalledWith({ message: '<i class="glyphicon glyphicon-ok"></i> Article saved successfully!' });
// Test URL location to new object
expect($state.go).toHaveBeenCalledWith('admin.articles.list');
}));

it('should set $scope.vm.error if error', inject(function (ArticlesService) {
it('should call Notification.error if error', inject(function (ArticlesService) {
var errorMessage = 'error';
$httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond(400, {
message: errorMessage
Expand All @@ -133,7 +141,7 @@
$scope.vm.save(true);
$httpBackend.flush();

expect($scope.vm.error).toBe(errorMessage);
expect(Notification.error).toHaveBeenCalledWith({ message: errorMessage, title: '<i class="glyphicon glyphicon-remove"></i> Article save error!' });
}));
});

Expand All @@ -152,6 +160,7 @@
$scope.vm.remove();
$httpBackend.flush();

expect(Notification.success).toHaveBeenCalledWith({ message: '<i class="glyphicon glyphicon-ok"></i> Article deleted successfully!' });
expect($state.go).toHaveBeenCalledWith('admin.articles.list');
});

Expand Down
15 changes: 14 additions & 1 deletion modules/core/client/app/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
var service = {
applicationEnvironment: window.env,
applicationModuleName: applicationModuleName,
applicationModuleVendorDependencies: ['ngResource', 'ngAnimate', 'ngMessages', 'ui.router', 'ui.bootstrap', 'ngFileUpload', 'ngImgCrop'],
applicationModuleVendorDependencies: ['ngResource', 'ngAnimate', 'ngMessages', 'ui.router', 'ui.bootstrap', 'ngFileUpload', 'ngImgCrop', 'ui-notification'],
registerModule: registerModule
};

Expand All @@ -20,4 +20,17 @@
// Add the module to the AngularJS configuration file
angular.module(applicationModuleName).requires.push(moduleName);
}

// Angular-ui-notification configuration
angular.module('ui-notification').config(function(NotificationProvider) {
NotificationProvider.setOptions({
delay: 2000,
startTop: 20,
startRight: 10,
verticalSpacing: 20,
horizontalSpacing: 20,
positionX: 'right',
positionY: 'bottom'
});
});
}(window));
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
.module('users.admin')
.controller('UserController', UserController);

UserController.$inject = ['$scope', '$state', '$window', 'Authentication', 'userResolve'];
UserController.$inject = ['$scope', '$state', '$window', 'Authentication', 'userResolve', 'Notification'];

function UserController($scope, $state, $window, Authentication, user) {
function UserController($scope, $state, $window, Authentication, user, Notification) {
var vm = this;

vm.authentication = Authentication;
Expand All @@ -22,9 +22,11 @@
user.$remove();

vm.users.splice(vm.users.indexOf(user), 1);
Notification.success('User deleted successfully!');
} else {
vm.user.$remove(function () {
$state.go('admin.users');
Notification.success({ message: '<i class="glyphicon glyphicon-ok"></i> User deleted successfully!' });
});
}
}
Expand All @@ -43,8 +45,9 @@
$state.go('admin.user', {
userId: user._id
});
Notification.success({ message: '<i class="glyphicon glyphicon-ok"></i> User saved successfully!' });
}, function (errorResponse) {
vm.error = errorResponse.data.message;
Notification.error({ message: errorResponse.data.message, title: '<i class="glyphicon glyphicon-remove"></i> User update error!' });
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
.module('users')
.controller('AuthenticationController', AuthenticationController);

AuthenticationController.$inject = ['$scope', '$state', 'UsersService', '$location', '$window', 'Authentication', 'PasswordValidator'];
AuthenticationController.$inject = ['$scope', '$state', 'UsersService', '$location', '$window', 'Authentication', 'PasswordValidator', 'Notification'];

function AuthenticationController($scope, $state, UsersService, $location, $window, Authentication, PasswordValidator) {
function AuthenticationController($scope, $state, UsersService, $location, $window, Authentication, PasswordValidator, Notification) {
var vm = this;

vm.authentication = Authentication;
Expand All @@ -17,15 +17,16 @@
vm.callOauthProvider = callOauthProvider;

// Get an eventual error defined in the URL query string:
vm.error = $location.search().err;
if ($location.search().err) {
Notification.error({ message: $location.search().err });
}

// If user is signed in then redirect back home
if (vm.authentication.user) {
$location.path('/');
}

function signup(isValid) {
vm.error = null;

if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'vm.userForm');
Expand All @@ -39,7 +40,6 @@
}

function signin(isValid) {
vm.error = null;

if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'vm.userForm');
Expand Down Expand Up @@ -67,25 +67,25 @@
function onUserSignupSuccess(response) {
// If successful we assign the response to the global user model
vm.authentication.user = response;

Notification.success({ message: '<i class="glyphicon glyphicon-ok"></i> Signup successful!' });
// And redirect to the previous or home page
$state.go($state.previous.state.name || 'home', $state.previous.params);
}

function onUserSignupError(response) {
vm.error = response.data.message;
Notification.error({ message: response.data.message, title: '<i class="glyphicon glyphicon-remove"></i> Signup Error!', delay: 6000 });
}

function onUserSigninSuccess(response) {
// If successful we assign the response to the global user model
vm.authentication.user = response;

Notification.info({ message: 'Welcome ' + response.firstName });
// And redirect to the previous or home page
$state.go($state.previous.state.name || 'home', $state.previous.params);
}

function onUserSigninError(response) {
vm.error = response.data.message;
Notification.error({ message: response.data.message, title: '<i class="glyphicon glyphicon-remove"></i> Signin Error!', delay: 6000 });
}
}
}());
13 changes: 6 additions & 7 deletions modules/users/client/controllers/password.client.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
.module('users')
.controller('PasswordController', PasswordController);

PasswordController.$inject = ['$scope', '$stateParams', 'UsersService', '$location', 'Authentication', 'PasswordValidator'];
PasswordController.$inject = ['$scope', '$stateParams', 'UsersService', '$location', 'Authentication', 'PasswordValidator', 'Notification'];

function PasswordController($scope, $stateParams, UsersService, $location, Authentication, PasswordValidator) {
function PasswordController($scope, $stateParams, UsersService, $location, Authentication, PasswordValidator, Notification) {
var vm = this;

vm.resetUserPassword = resetUserPassword;
Expand All @@ -22,7 +22,6 @@

// Submit forgotten password account id
function askForPasswordReset(isValid) {
vm.success = vm.error = null;

if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'vm.forgotPasswordForm');
Expand All @@ -37,7 +36,6 @@

// Change user password
function resetUserPassword(isValid) {
vm.success = vm.error = null;

if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'vm.resetPasswordForm');
Expand All @@ -55,13 +53,13 @@
function onRequestPasswordResetSuccess(response) {
// Show user success message and clear form
vm.credentials = null;
vm.success = response.message;
Notification.success({ message: response.message, title: '<i class="glyphicon glyphicon-ok"></i> Password reset email sent successfully!' });
}

function onRequestPasswordResetError(response) {
// Show user error message and clear form
vm.credentials = null;
vm.error = response.data.message;
Notification.error({ message: response.data.message, title: '<i class="glyphicon glyphicon-remove"></i> Failed to send password reset email!', delay: 4000 });
}

function onResetPasswordSuccess(response) {
Expand All @@ -70,12 +68,13 @@

// Attach user profile
Authentication.user = response;
Notification.success({ message: '<i class="glyphicon glyphicon-ok"></i> Password reset successful!' });
// And redirect to the index page
$location.path('/password/reset/success');
}

function onResetPasswordError(response) {
vm.error = response.data.message;
Notification.error({ message: response.data.message, title: '<i class="glyphicon glyphicon-remove"></i> Password reset failed!', delay: 4000 });
}
}
}());
Loading

0 comments on commit 607ed06

Please sign in to comment.