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

Commit

Permalink
client-side form validation with ng-messages.
Browse files Browse the repository at this point in the history
remove data prefix from attributes.

fix tests
  • Loading branch information
rhutchison committed Aug 25, 2015
1 parent 979c4e5 commit 8015476
Show file tree
Hide file tree
Showing 20 changed files with 274 additions and 106 deletions.
3 changes: 2 additions & 1 deletion bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"angular-bootstrap": "~0.13",
"angular-ui-utils": "bower",
"angular-ui-router": "~0.2",
"angular-file-upload": "1.1.5"
"angular-file-upload": "1.1.5",
"angular-messages": "1.3.17"

This comment has been minimized.

Copy link
@ilanbiala

ilanbiala Aug 27, 2015

Member

Why is this static?

},
"resolutions": {
"angular": "~1.3"
Expand Down
1 change: 1 addition & 0 deletions config/assets/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = {
'public/lib/angular/angular.js',
'public/lib/angular-resource/angular-resource.js',
'public/lib/angular-animate/angular-animate.js',
'public/lib/angular-messages/angular-messages.js',
'public/lib/angular-ui-router/release/angular-ui-router.js',
'public/lib/angular-ui-utils/ui-utils.js',
'public/lib/angular-bootstrap/ui-bootstrap-tpls.js',
Expand Down
1 change: 1 addition & 0 deletions config/assets/production.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = {
'public/lib/angular/angular.min.js',
'public/lib/angular-resource/angular-resource.min.js',
'public/lib/angular-animate/angular-animate.min.js',
'public/lib/angular-messages/angular-messages.min.js',
'public/lib/angular-ui-router/release/angular-ui-router.min.js',
'public/lib/angular-ui-utils/ui-utils.min.js',
'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js',
Expand Down
20 changes: 18 additions & 2 deletions modules/articles/client/controllers/articles.client.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ angular.module('articles').controller('ArticlesController', ['$scope', '$statePa
$scope.authentication = Authentication;

// Create new Article
$scope.create = function () {
$scope.create = function (isValid) {
$scope.error = null;

if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'articleForm');

return false;
}

// Create new Article object
var article = new Articles({
title: this.title,
Expand Down Expand Up @@ -43,7 +51,15 @@ angular.module('articles').controller('ArticlesController', ['$scope', '$statePa
};

// Update existing Article
$scope.update = function () {
$scope.update = function (isValid) {
$scope.error = null;

if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'articleForm');

return false;
}

var article = $scope.article;

article.$update(function () {
Expand Down
17 changes: 8 additions & 9 deletions modules/articles/client/views/create-article.client.view.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@
<h1>New Article</h1>
</div>
<div class="col-md-12">
<form name="articleForm" class="form-horizontal" data-ng-submit="create()" novalidate>
<form name="articleForm" class="form-horizontal" ng-submit="create(articleForm.$valid)" novalidate>
<fieldset>
<div class="form-group">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input name="title" type="text" data-ng-model="title" id="title" class="form-control" placeholder="Title">
<div class="form-group" show-errors>
<label for="title">Title</label>
<input name="title" type="text" ng-model="title" id="title" class="form-control" placeholder="Title" required>
<div ng-messages="articleForm.title.$error" role="alert">
<p class="help-block error-text" ng-message="required">Article title is required.</p>
</div>
</div>
<div class="form-group">
<label class="control-label" for="content">Content</label>
<div class="controls">
<textarea name="content" data-ng-model="content" id="content" class="form-control" cols="30" rows="10" placeholder="Content"></textarea>
</div>
<label for="content">Content</label>
<textarea name="content" ng-model="content" id="content" class="form-control" cols="30" rows="10" placeholder="Content"></textarea>
</div>
<div class="form-group">
<input type="submit" class="btn btn-default">
Expand Down
17 changes: 8 additions & 9 deletions modules/articles/client/views/edit-article.client.view.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@
<h1>Edit Article</h1>
</div>
<div class="col-md-12">
<form name="articleForm" class="form-horizontal" data-ng-submit="update()" novalidate>
<form name="articleForm" class="form-horizontal" ng-submit="update(articleForm.$valid)" novalidate>
<fieldset>
<div class="form-group">
<label class="control-label" for="title">Title</label>
<div class="controls">
<input name="title" type="text" data-ng-model="article.title" id="title" class="form-control" placeholder="Title" required>
<div class="form-group" show-errors>
<label for="title">Title</label>
<input name="title" type="text" ng-model="article.title" id="title" class="form-control" placeholder="Title" required>
<div ng-messages="articleForm.title.$error" role="alert">
<p class="help-block error-text" ng-message="required">Article title is required.</p>
</div>
</div>
<div class="form-group">
<label class="control-label" for="content">Content</label>
<div class="controls">
<textarea name="content" data-ng-model="article.content" id="content" class="form-control" cols="30" rows="10" placeholder="Content"></textarea>
</div>
<label for="content">Content</label>
<textarea name="content" ng-model="article.content" id="content" class="form-control" cols="30" rows="10" placeholder="Content"></textarea>
</div>
<div class="form-group">
<input type="submit" value="Update" class="btn btn-default">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
expect(scope.article).toEqualData(mockArticle);
}));

describe('$scope.craete()', function () {
describe('$scope.create()', function () {
var sampleArticlePostData;

beforeEach(function () {
Expand All @@ -119,7 +119,7 @@
$httpBackend.expectPOST('api/articles', sampleArticlePostData).respond(mockArticle);

// Run controller functionality
scope.create();
scope.create(true);

This comment has been minimized.

Copy link
@ilanbiala

ilanbiala Aug 27, 2015

Member

What does passing true do?

$httpBackend.flush();

// Test form inputs are reset
Expand All @@ -136,7 +136,7 @@
message: errorMessage
});

scope.create();
scope.create(true);
$httpBackend.flush();

expect(scope.error).toBe(errorMessage);
Expand All @@ -154,7 +154,7 @@
$httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond();

// Run controller functionality
scope.update();
scope.update(true);
$httpBackend.flush();

// Test URL location to new object
Expand All @@ -167,7 +167,7 @@
message: errorMessage
});

scope.update();
scope.update(true);
$httpBackend.flush();

expect(scope.error).toBe(errorMessage);
Expand Down
2 changes: 1 addition & 1 deletion modules/core/client/app/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
var ApplicationConfiguration = (function () {
// Init module configuration options
var applicationModuleName = 'mean';
var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils', 'angularFileUpload'];
var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ngMessages', 'ui.router', 'ui.bootstrap', 'ui.utils', 'angularFileUpload'];

// Add a new vertical module
var registerModule = function (moduleName, dependencies) {
Expand Down
15 changes: 9 additions & 6 deletions modules/core/client/css/core.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@
.x-ng-cloak {
display: none !important;
}
.ng-invalid.ng-dirty {
border-color: #FA787E;
}
.ng-valid.ng-dirty {
border-color: #78FA89;
}
.header-profile-image {
opacity: 0.8;
height: 28px;
Expand All @@ -33,3 +27,12 @@ a:hover .header-profile-image {
padding-top: 11px !important;
padding-bottom: 11px !important;
}
.error-text {
display: none;
}
.has-error .help-block.error-text {
display: block;
}
.has-error .help-inline.error-text {
display: inline;
}
74 changes: 74 additions & 0 deletions modules/core/client/directives/show-errors.client.directives.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict';

/**
* Edits by Ryan Hutchison
* Credit: https://github.com/paulyoder/angular-bootstrap-show-errors */

angular.module('core')
.directive('showErrors', ['$timeout', '$interpolate', function ($timeout, $interpolate) {
var linkFn = function (scope, el, attrs, formCtrl) {
var inputEl, inputName, inputNgEl, options, showSuccess, toggleClasses,
initCheck = false,
showValidationMessages = false,
blurred = false;

options = scope.$eval(attrs.showErrors) || {};
showSuccess = options.showSuccess || false;
inputEl = el[0].querySelector('.form-control[name]') || el[0].querySelector('[name]');
inputNgEl = angular.element(inputEl);
inputName = $interpolate(inputNgEl.attr('name') || '')(scope);

if (!inputName) {
throw 'show-errors element has no child input elements with a \'name\' attribute class';
}

var reset = function () {
return $timeout(function () {
el.removeClass('has-error');
el.removeClass('has-success');
showValidationMessages = false;
}, 0, false);
};

scope.$watch(function () {
return formCtrl[inputName] && formCtrl[inputName].$invalid;
}, function (invalid) {
return toggleClasses(invalid);
});

scope.$on('show-errors-check-validity', function (event, name) {
if (angular.isUndefined(name) || formCtrl.$name === name) {
initCheck = true;
showValidationMessages = true;

return toggleClasses(formCtrl[inputName].$invalid);
}
});

scope.$on('show-errors-reset', function (event, name) {
if (angular.isUndefined(name) || formCtrl.$name === name) {
return reset();
}
});

toggleClasses = function (invalid) {
el.toggleClass('has-error', showValidationMessages && invalid);
if (showSuccess) {
return el.toggleClass('has-success', showValidationMessages && !invalid);
}
};
};

return {
restrict: 'A',
require: '^form',
compile: function (elem, attrs) {
if (attrs.showErrors.indexOf('skipFormGroupCheck') === -1) {
if (!(elem.hasClass('form-group') || elem.hasClass('input-group'))) {
throw 'show-errors element does not have the \'form-group\' or \'input-group\' class';
}
}
return linkFn;
}
};
}]);
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ angular.module('users.admin').controller('UserController', ['$scope', '$state',
}
};

$scope.update = function () {
$scope.update = function (isValid) {
if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'userForm');

return false;
}

var user = $scope.user;

user.$update(function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$stat
$location.path('/');
}

$scope.signup = function () {
$scope.signup = function (isValid) {
$scope.error = null;

if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'userForm');

return false;
}

$http.post('/api/auth/signup', $scope.credentials).success(function (response) {
// If successful we assign the response to the global user model
$scope.authentication.user = response;
Expand All @@ -24,7 +32,15 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$stat
});
};

$scope.signin = function () {
$scope.signin = function (isValid) {
$scope.error = null;

if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'userForm');

return false;
}

$http.post('/api/auth/signin', $scope.credentials).success(function (response) {
// If successful we assign the response to the global user model
$scope.authentication.user = response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ angular.module('users').controller('ChangePasswordController', ['$scope', '$http
$scope.user = Authentication.user;

// Change user password
$scope.changeUserPassword = function () {
$scope.changeUserPassword = function (isValid) {
$scope.success = $scope.error = null;

if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'passwordForm');

return false;
}

$http.post('/api/users/password', $scope.passwordDetails).success(function (response) {
// If successful show success message and clear form
$scope.$broadcast('show-errors-reset', 'passwordForm');
$scope.success = true;
$scope.passwordDetails = null;
}).error(function (response) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@ angular.module('users').controller('EditProfileController', ['$scope', '$http',

// Update a user profile
$scope.updateUserProfile = function (isValid) {
if (isValid) {
$scope.success = $scope.error = null;
var user = new Users($scope.user);
$scope.success = $scope.error = null;

user.$update(function (response) {
$scope.success = true;
Authentication.user = response;
}, function (response) {
$scope.error = response.data.message;
});
} else {
$scope.submitted = true;
if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'userForm');

return false;
}

var user = new Users($scope.user);

user.$update(function (response) {
$scope.$broadcast('show-errors-reset', 'userForm');

$scope.success = true;
Authentication.user = response;
}, function (response) {
$scope.error = response.data.message;
});
};
}
]);
Loading

1 comment on commit 8015476

@bastianwegge
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every time so much code is written without a single line of test a programmer dies.

Please sign in to comment.