0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

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 () { (function () {
"use strict"; "use strict";
@ -7,6 +7,7 @@
Views : {}, Views : {},
Collections : {}, Collections : {},
Models : {}, Models : {},
Validate : new Validator(),
settings: { settings: {
apiRoot: '/api/v0.1' 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; window.Ghost = Ghost;
}()); }());

View file

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

View file

@ -23,25 +23,33 @@
password = this.$el.find('.password').val(), password = this.$el.find('.password').val(),
redirect = Ghost.Views.Utils.getUrlVariables().r; redirect = Ghost.Views.Utils.getUrlVariables().r;
$.ajax({ Ghost.Validate._errors = [];
url: '/ghost/signin/', Ghost.Validate.check(email).isEmail();
type: 'POST', Ghost.Validate.check(password, "Password too short").len(5);
data: {
email: email, if (Ghost.Validate._errors.length > 0) {
password: password, Ghost.Validate.handleErrors();
redirect: redirect } else {
}, $.ajax({
success: function (msg) { url: '/ghost/signin/',
window.location.href = msg.redirect; type: 'POST',
}, data: {
error: function (xhr) { email: email,
Ghost.notifications.addItem({ password: password,
type: 'error', redirect: redirect
message: Ghost.Views.Utils.getRequestErrorMessage(xhr), },
status: 'passive' 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(), email = this.$el.find('.email').val(),
password = this.$el.find('.password').val(); password = this.$el.find('.password').val();
if (!name) { // This is needed due to how error handling is done. If this is not here, there will not be a time
Ghost.notifications.addItem({ // when there is no error.
type: 'error', Ghost.Validate._errors = [];
message: "Please enter a name", Ghost.Validate.check(name, "Please enter a name").len(1);
status: 'passive' Ghost.Validate.check(email, "Please enter a correct email address").isEmail();
}); Ghost.Validate.check(password, "Please enter a password").len(5);
} else if (!email) {
Ghost.notifications.addItem({ if (Ghost.Validate._errors.length > 0) {
type: 'error', Ghost.Validate.handleErrors();
message: "Please enter an email",
status: 'passive'
});
} else if (!password) {
Ghost.notifications.addItem({
type: 'error',
message: "Please enter a password",
status: 'passive'
});
} else { } else {
$.ajax({ $.ajax({
url: '/ghost/signup/', url: '/ghost/signup/',
@ -128,24 +127,31 @@
var email = this.$el.find('.email').val(); var email = this.$el.find('.email').val();
$.ajax({ Ghost.Validate._errors = [];
url: '/ghost/forgotten/', Ghost.Validate.check(email).isEmail();
type: 'POST',
data: {
email: email
},
success: function (msg) {
window.location.href = msg.redirect; if (Ghost.Validate._errors.length > 0) {
}, Ghost.Validate.handleErrors();
error: function (xhr) { } else {
Ghost.notifications.addItem({ $.ajax({
type: 'error', url: '/ghost/forgotten/',
message: Ghost.Views.Utils.getRequestErrorMessage(xhr), type: 'POST',
status: 'passive' 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 () { saveSettings: function () {
var themes = this.model.get('availableThemes'); var themes = this.model.get('availableThemes'),
this.model.unset('availableThemes'); title = this.$('#blog-title').val(),
this.model.save({ description = this.$('#blog-description').val(),
title: this.$('#blog-title').val(), email = this.$('#email-address').val(),
description: $('#blog-description').val(), postsPerPage = this.$('#postsPerPage').val();
logo: this.$('#blog-logo').attr("src"),
cover: this.$('#blog-cover').attr("src"), Ghost.Validate._errors = [];
email: this.$('#email-address').val(), Ghost.Validate
postsPerPage: this.$('#postsPerPage').val(), .check(title, {message: "Title is too long", el: $('#blog-title')})
activeTheme: this.$('#activeTheme').val() .len(0, 150);
}, { Ghost.Validate
success: this.saveSuccess, .check(description, {message: "Description is too long", el: $('#blog-description')})
error: this.saveError .len(0, 200);
}); Ghost.Validate
this.model.set({availableThemes: themes}); .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 () { showLogo: function () {
var settings = this.model.toJSON(); var settings = this.model.toJSON();
@ -259,63 +283,92 @@
saveUser: function () { saveUser: function () {
this.model.save({ var userName = this.$('#user-name').val(),
'full_name': this.$('#user-name').val(), userEmail = this.$('#user-email').val(),
'email_address': this.$('#user-email').val(), userLocation = this.$('#user-location').val(),
'location': this.$('#user-location').val(), userWebsite = this.$('#user-website').val(),
'url': this.$('#user-website').val(), userBio = this.$('#user-bio').val();
'bio': this.$('#user-bio').val(),
'profile_picture': this.$('#user-profile-picture').attr('src'), Ghost.Validate._errors = [];
'cover_picture': this.$('#user-cover-picture').attr('src') Ghost.Validate
}, { .check(userName, {message: "Name is too long", el: $('#user-name')})
success: this.saveSuccess, .len(0, 150);
error: this.saveError 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) { changePassword: function (event) {
event.preventDefault(); event.preventDefault();
var self = this, var self = this,
oldPassword = this.$('#user-password-old').val(), oldPassword = this.$('#user-password-old').val(),
newPassword = this.$('#user-password-new').val(), newPassword = this.$('#user-password-new').val(),
ne2Password = this.$('#user-new-password-verification').val(); ne2Password = this.$('#user-new-password-verification').val();
if (newPassword !== ne2Password) { Ghost.Validate._errors = [];
this.validationError('Your new passwords do not match'); Ghost.Validate.check(newPassword, {message: 'Your new passwords do not match'}).equals(ne2Password);
return; 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) { if (Ghost.Validate._errors.length > 0) {
this.validationError('Your password is not long enough. It must be at least 8 chars long.'); Ghost.Validate.handleErrors();
return; } else {
}
$.ajax({ $.ajax({
url: '/ghost/changepw/', url: '/ghost/changepw/',
type: 'POST', type: 'POST',
data: { data: {
password: oldPassword, password: oldPassword,
newpassword: newPassword, newpassword: newPassword,
ne2password: ne2Password ne2password: ne2Password
}, },
success: function (msg) { success: function (msg) {
Ghost.notifications.addItem({ Ghost.notifications.addItem({
type: 'success', type: 'success',
message: msg.msg, message: msg.msg,
status: 'passive', status: 'passive',
id: 'success-98' id: 'success-98'
}); });
self.$('#user-password-old, #user-password-new, #user-new-password-verification').val(''); self.$('#user-password-old, #user-password-new, #user-new-password-verification').val('');
}, },
error: function (xhr) { error: function (xhr) {
Ghost.notifications.addItem({ Ghost.notifications.addItem({
type: 'error', type: 'error',
message: Ghost.Views.Utils.getRequestErrorMessage(xhr), message: Ghost.Views.Utils.getRequestErrorMessage(xhr),
status: 'passive' status: 'passive'
}); });
} }
}); });
}
}, },
templateName: 'settings/user-profile', templateName: 'settings/user-profile',

View file

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