Backbone, Marionette — simple form validation with Backbone Model, Marionette’s regions and child views
Form validation for Backbone/Marionette can be tricky for person from outside this world, but with regions and child views it can be done from scratch without too much effort
Own solution instead of plugin
It’s easy to find that Backbone and Marionette have plugins for validation: thedersen/backbone.validation, powmedia/backbone-forms or not-only-code/marionette-forms, to name a few. But form validation, especially when form is simple (like mine, only 2 fields; login
and password
) is rather easy thing to do. Also, for beginner like me, this is a great opportunity to dive a little deeper into libraries ecosystem.
Idea for validation
Validation is pretty simple — when form is submitted, check if login has at least 5 characters and check if password has at least 8 characters. When this conditions are met, send request — otherwise, display information below field. Also, when user corrects data, hide error messages.
Validation on Backbone model
Backbone model provides validate method, which looks weird at first glance (especially that it does not have default implementation and is undefined if you don’t override it). You are absolutely free in how you implement your validation, to indicate that model is valid you should not return anything (or return undefined
, it means exactly the same for JavaScript). But, when you want to mark model as invalid, you should return… Whatever you want. Except undefined
, of course. And result of return is bound to validationError
property on model.
Why is it done like this?
Cause you can do whatever you want with validationError
, and depending on your needs, you can return “Something went wrong” and display an alert or return complex object and display very detailed information about what went wrong, like people used to with Angular validation. I choose the latter — but in very simplified version (validate
method from Login model, simplified for brevity):
validate: function (attrs, options) {
const validationErrors = {};
if (/*login is too short*/) {
validationErrors.login = {
error: `min`,
message: `Login should contain at least ${this.loginMinLength} characters`,
};
}
if (/*password is too short*/) {
validationErrors.password = {
error: `min`,
message: `Password should contain at least ${this.passwordMinLength} characters`,
};
}
return (validationErrors.login || validationErrors.password) ? validationErrors : undefined;
},
Returned object will be used to display messages below input. If you look closer you will find ${(…)}
fragments — it is Template String from ES 6. It significantly simplifies creating strings based on dynamic content.
Unfortunately, I had to inject minimum lengths to model itself:
model: new LoginModel({
loginMinLength: 5,
passwordMinLength: 8,
}),
Despite it’s done with great ES 6 destructuring feature, I don’t think is a good idea, but I can’t figure out how to do it better. If you know cleaner solution, do not hesitate to help me and leave a comment!
Displaying validation messages in Marionette Region with Child View
Marionette’s regions are places where you can display child views, they can be added at runtime — and also they can be removed. I created 2 regions, placed below input in HTML:
<div>
<label for="login"></label>
<input type="text" id="login">
<p id="login-invalid"></p>
</div>
<div>
<label for="password"></label>
<input type="password" id="password">
<p id="password-invalid"></p>
</div>
Declaration in View:
regions: {
loginInvalid: "#login-invalid",
passwordInvalid: "#password-invalid",
},
But when they should be shown or removed?
Backbone model events in Marionette view
Marionette provides modelEvents property, where as key you should provide event type with colon and property name (like keyup:name
, where keyup is keyup input event and name is property name on model), and as value you should provide function name, called when event occurs. Function has to be present on View, otherwise you will get an error.
Clearing error message when model changes and change event is emitted
On 2 Backbone model change
event:
modelEvents: {
"change:login": "onLoginModelChange",
"change:password": "onPasswordModelChange",
"invalid": "onValidationFailed",
},
Respective regions are cleared:
// Same for password model property change
onLoginModelChange: function () {
this.detachChildView("loginInvalid");
},
Cause I made an assumption that when user see an error and start typing, it could be annoying to see error message.
But what about “invalid”: “onValidationFailed”
? When invalid
event is emitted?
Showing error messages when model validation fails
As documentation says, validation can be triggered when you try to persist your model. But there is a caveat — save()
returned false
for invalid model which broke promise chain:
this.model.save().then...
And error was thrown with information that false
des not have then method
. So I did a workaround — I call isValid method manually, and it triggers invalid
event:
if (this.model.isValid()) {
this.model.save().then(_onLoginRequest.bind(this));
}
When model is not valid, invalid
event is emitted and validationError
property is set on model, with value returned from validate Backbone model method:
onValidationFailed: function () {
const errors = this.model.validationError;
if (errors.login) {
this.showChildView("loginInvalid", new ErrorView({
template: _.template(errors.login.message),
}));
}
if (errors.password) {
this.showChildView("passwordInvalid", new ErrorView({
template: _.template(errors.password.message),
}));
}
},
And child views are shown in respective regions. ErrorView
is view without template:
const ErrorView = Mn.View.extend({
})
Because its template
is just a message from error. I found something called Behaviors in Marionette, it could be better way to solve it — and it is at least worth to dive into.
Conclusion
With every touch, every single post I feel more excited about adventure with Backbone / Marionette. It’s not easy, cause in my 9–5 I use Angular, so for me it’s more low-level, but any time spent with mentioned libraries is definitely worth it!