Added client side validation

Closes #581.

* Basically adds the client side of node validator, that we're already using
* Validator is plonked onto `Ghost.Validator`
* Usage is identical as to https://github.com/chriso/node-validator
* Has sanitizing values et al
* `Ghost.Validator.error` is redefined, it populates Ghost.Validator._errors (Array)
* `Ghost.Validator.handleErrors` is supposed to print out the multiple error messages, if there are multiple (this is broken due to how notifications are presented `.html` instead of `.append`), and also apply class to element
* The ajax calls are wrapped in an if to prevent network traffic if something's not right on client side
* Added validation to general settings and user settings screens.
* On validation error, optionally adds `.input-error` to whatever element you reference, see below (if `el` exists on the error object). This is the only place where usage is different to the original implementation. Redeclared `error()` function in `init.js`
* Usage: `Ghost.Validate.check(valueToCheck, {message: "the error message", el: $('#the element')}).isEmail()`
* The element above will receive the `.input-error` class. `isEmail()` is one of the stuff you can check against.
This commit is contained in:
Gabor Javorszky 2013-09-12 21:03:18 +01:00
parent d00392973f
commit 6c99b67ab3
6 changed files with 1209 additions and 119 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
/*globals window, $, _, Backbone */
/*globals window, $, _, Backbone, Validator */
(function () {
"use strict";
@ -7,6 +7,7 @@
Views : {},
Collections : {},
Models : {},
Validate : new Validator(),
settings: {
apiRoot: '/api/v0.1'
@ -36,6 +37,25 @@
});
};
Ghost.Validate.error = function (object) {
this._errors.push(object);
return this;
};
Ghost.Validate.handleErrors = function () {
_.each(Ghost.Validate._errors, function (errorObj) {
Ghost.notifications.addItem({
type: 'error',
message: errorObj.message,
status: 'passive'
});
if (errorObj.hasOwnProperty('el')) {
errorObj.el.addClass('input-error');
}
});
};
window.Ghost = Ghost;
}());
}());

View File

@ -69,7 +69,7 @@
<input type="password" id="user-new-password-verification">
</div>
<div class="form-group">
<button class="button-delete">Change Password</button>
<button class="button-delete button-change-password">Change Password</button>
</div>
</fieldset>

View File

@ -23,25 +23,33 @@
password = this.$el.find('.password').val(),
redirect = Ghost.Views.Utils.getUrlVariables().r;
$.ajax({
url: '/ghost/signin/',
type: 'POST',
data: {
email: email,
password: password,
redirect: redirect
},
success: function (msg) {
window.location.href = msg.redirect;
},
error: function (xhr) {
Ghost.notifications.addItem({
type: 'error',
message: Ghost.Views.Utils.getRequestErrorMessage(xhr),
status: 'passive'
});
}
});
Ghost.Validate._errors = [];
Ghost.Validate.check(email).isEmail();
Ghost.Validate.check(password, "Password too short").len(5);
if (Ghost.Validate._errors.length > 0) {
Ghost.Validate.handleErrors();
} else {
$.ajax({
url: '/ghost/signin/',
type: 'POST',
data: {
email: email,
password: password,
redirect: redirect
},
success: function (msg) {
window.location.href = msg.redirect;
},
error: function (xhr) {
Ghost.notifications.addItem({
type: 'error',
message: Ghost.Views.Utils.getRequestErrorMessage(xhr),
status: 'passive'
});
}
});
}
}
});
@ -66,24 +74,15 @@
email = this.$el.find('.email').val(),
password = this.$el.find('.password').val();
if (!name) {
Ghost.notifications.addItem({
type: 'error',
message: "Please enter a name",
status: 'passive'
});
} else if (!email) {
Ghost.notifications.addItem({
type: 'error',
message: "Please enter an email",
status: 'passive'
});
} else if (!password) {
Ghost.notifications.addItem({
type: 'error',
message: "Please enter a password",
status: 'passive'
});
// This is needed due to how error handling is done. If this is not here, there will not be a time
// when there is no error.
Ghost.Validate._errors = [];
Ghost.Validate.check(name, "Please enter a name").len(1);
Ghost.Validate.check(email, "Please enter a correct email address").isEmail();
Ghost.Validate.check(password, "Please enter a password").len(5);
if (Ghost.Validate._errors.length > 0) {
Ghost.Validate.handleErrors();
} else {
$.ajax({
url: '/ghost/signup/',
@ -128,24 +127,31 @@
var email = this.$el.find('.email').val();
$.ajax({
url: '/ghost/forgotten/',
type: 'POST',
data: {
email: email
},
success: function (msg) {
Ghost.Validate._errors = [];
Ghost.Validate.check(email).isEmail();
window.location.href = msg.redirect;
},
error: function (xhr) {
Ghost.notifications.addItem({
type: 'error',
message: Ghost.Views.Utils.getRequestErrorMessage(xhr),
status: 'passive'
});
}
});
if (Ghost.Validate._errors.length > 0) {
Ghost.Validate.handleErrors();
} else {
$.ajax({
url: '/ghost/forgotten/',
type: 'POST',
data: {
email: email
},
success: function (msg) {
window.location.href = msg.redirect;
},
error: function (xhr) {
Ghost.notifications.addItem({
type: 'error',
message: Ghost.Views.Utils.getRequestErrorMessage(xhr),
status: 'passive'
});
}
});
}
}
});
}());

View File

@ -157,21 +157,45 @@
},
saveSettings: function () {
var themes = this.model.get('availableThemes');
this.model.unset('availableThemes');
this.model.save({
title: this.$('#blog-title').val(),
description: $('#blog-description').val(),
logo: this.$('#blog-logo').attr("src"),
cover: this.$('#blog-cover').attr("src"),
email: this.$('#email-address').val(),
postsPerPage: this.$('#postsPerPage').val(),
activeTheme: this.$('#activeTheme').val()
}, {
success: this.saveSuccess,
error: this.saveError
});
this.model.set({availableThemes: themes});
var themes = this.model.get('availableThemes'),
title = this.$('#blog-title').val(),
description = this.$('#blog-description').val(),
email = this.$('#email-address').val(),
postsPerPage = this.$('#postsPerPage').val();
Ghost.Validate._errors = [];
Ghost.Validate
.check(title, {message: "Title is too long", el: $('#blog-title')})
.len(0, 150);
Ghost.Validate
.check(description, {message: "Description is too long", el: $('#blog-description')})
.len(0, 200);
Ghost.Validate
.check(email, {message: "Please supply a valid email address", el: $('#email-address')})
.isEmail().len(0, 254);
Ghost.Validate
.check(postsPerPage, {message: "Please use a number", el: $('postsPerPage')})
.isInt();
if (Ghost.Validate._errors.length > 0) {
Ghost.Validate.handleErrors();
} else {
this.model.unset('availableThemes');
this.model.save({
title: title,
description: description,
logo: this.$('#blog-logo').attr("src"),
cover: this.$('#blog-cover').attr("src"),
email: email,
postsPerPage: postsPerPage,
activeTheme: this.$('#activeTheme').val()
}, {
success: this.saveSuccess,
error: this.saveError
});
this.model.set({availableThemes: themes});
}
},
showLogo: function () {
var settings = this.model.toJSON();
@ -259,63 +283,92 @@
saveUser: function () {
this.model.save({
'full_name': this.$('#user-name').val(),
'email_address': this.$('#user-email').val(),
'location': this.$('#user-location').val(),
'url': this.$('#user-website').val(),
'bio': this.$('#user-bio').val(),
'profile_picture': this.$('#user-profile-picture').attr('src'),
'cover_picture': this.$('#user-cover-picture').attr('src')
}, {
success: this.saveSuccess,
error: this.saveError
});
var userName = this.$('#user-name').val(),
userEmail = this.$('#user-email').val(),
userLocation = this.$('#user-location').val(),
userWebsite = this.$('#user-website').val(),
userBio = this.$('#user-bio').val();
Ghost.Validate._errors = [];
Ghost.Validate
.check(userName, {message: "Name is too long", el: $('#user-name')})
.len(0, 150);
Ghost.Validate
.check(userBio, {message: "Bio is too long", el: $('#user-bio')})
.len(0, 200);
Ghost.Validate
.check(userEmail, {message: "Please supply a valid email address", el: $('#user-email')})
.isEmail();
Ghost.Validate
.check(userLocation, {message: "Location is too long", el: $('#user-location')})
.len(0, 150);
if (userWebsite.length > 0) {
Ghost.Validate
.check(userWebsite, {message: "Please use a valid url", el: $('#user-website')})
.isUrl()
.len(0, 2000);
}
if (Ghost.Validate._errors.length > 0) {
Ghost.Validate.handleErrors();
} else {
this.model.save({
'full_name': userName,
'email_address': userEmail,
'location': userLocation,
'url': userWebsite,
'bio': userBio,
'profile_picture': this.$('#user-profile-picture').attr('src'),
'cover_picture': this.$('#user-cover-picture').attr('src')
}, {
success: this.saveSuccess,
error: this.saveError
});
}
},
changePassword: function (event) {
event.preventDefault();
var self = this,
oldPassword = this.$('#user-password-old').val(),
newPassword = this.$('#user-password-new').val(),
ne2Password = this.$('#user-new-password-verification').val();
if (newPassword !== ne2Password) {
this.validationError('Your new passwords do not match');
return;
}
Ghost.Validate._errors = [];
Ghost.Validate.check(newPassword, {message: 'Your new passwords do not match'}).equals(ne2Password);
Ghost.Validate.check(newPassword, {message: 'Your password is not long enough. It must be at least 8 chars long.'}).len(8);
if (newPassword.length < 8) {
this.validationError('Your password is not long enough. It must be at least 8 chars long.');
return;
}
if (Ghost.Validate._errors.length > 0) {
Ghost.Validate.handleErrors();
} else {
$.ajax({
url: '/ghost/changepw/',
type: 'POST',
data: {
password: oldPassword,
newpassword: newPassword,
ne2password: ne2Password
},
success: function (msg) {
Ghost.notifications.addItem({
type: 'success',
message: msg.msg,
status: 'passive',
id: 'success-98'
});
self.$('#user-password-old, #user-password-new, #user-new-password-verification').val('');
},
error: function (xhr) {
Ghost.notifications.addItem({
type: 'error',
message: Ghost.Views.Utils.getRequestErrorMessage(xhr),
status: 'passive'
});
}
});
$.ajax({
url: '/ghost/changepw/',
type: 'POST',
data: {
password: oldPassword,
newpassword: newPassword,
ne2password: ne2Password
},
success: function (msg) {
Ghost.notifications.addItem({
type: 'success',
message: msg.msg,
status: 'passive',
id: 'success-98'
});
self.$('#user-password-old, #user-password-new, #user-new-password-verification').val('');
},
error: function (xhr) {
Ghost.notifications.addItem({
type: 'error',
message: Ghost.Views.Utils.getRequestErrorMessage(xhr),
status: 'passive'
});
}
});
}
},
templateName: 'settings/user-profile',

View File

@ -58,6 +58,7 @@
<script src="/public/vendor/showdown/extensions/ghostdown.js"></script>
<script src="/shared/vendor/showdown/extensions/github.js"></script>
<script src="/public/vendor/shortcuts.js"></script>
<script src="/public/vendor/validator-client.js"></script>
<script src="/public/vendor/countable.js"></script>
<script src="/public/vendor/to-title-case.js"></script>
<script src="/public/vendor/packery.pkgd.min.js"></script>
@ -83,8 +84,8 @@
<script src="/public/models/uploadModal.js"></script>
<!-- // require '/public/views/*' -->
<script src="/public/views/base.js"></script>
<script src="/public/models/uploadModal.js"></script>
<script src="/public/views/post-settings.js"></script>
<script src="/public/models/uploadModal.js"></script>
<script src="/public/views/blog.js"></script>
<script src="/public/views/editor.js"></script>
<script src="/public/views/editor-tag-widget.js"></script>