Is there a way to have a “global” error handler in a Model / superMap?
I guess an option would be to sub-class and use that; or is there an easier option?
Is there a way to have a “global” error handler in a Model / superMap?
I guess an option would be to sub-class and use that; or is there an easier option?
Depends on the error. If you just want to know when some ajax request went wrong, you can use a jQuery ajax prefilter.
Cool beans! Thanks… I guess that makes sense
Globally handling for these sort of things are ineffective. They’re almost exclusively server side validation issues, or otherwise could be handled contextually within your markup.
The approach I take is to make my API validation errors symmetrical in format with my client-side validation errors. If you’re using StealJS, you can use something like the validator to do your validation both on your can.Map, and within your models via mongoose-validator. You can even go a step further use express-validator within your middleware.
I wrote express-error-funnel expressly for combining errors from express-validator / mongoose-validator / mongoose all into the same format.
Once you have uniform error handling, it’s straight forward to merge the two:
errors: {
value: Object,
get: function( map ) {
var validationErrors = this.attr('validationErrors'),
ajaxErrors = this.attr('ajaxErrors'),
params = {};
can.extend(params, validationErrors.attr());
can.extend(params, ajaxErrors.attr());
map.attr(params, true);
return map;
}
},
ajaxErrors: {
value: Object
},
validationErrors: {
value: Object
}
Here I’m deconstructing my ajax errors:
submit: function( promise ) {
var scope = this;
scope.attr({
requestTime: new Date(),
isPending: true,
code: undefined,
statusCode: undefined,
message: undefined
});
return promise.then(function() {
scope.attr('isPending', false);
}, function( xhr ) {
var res = xhr.responseJSON || {},
errors = res.errors || [],
ajaxErrors = {},
params = {};
params.statusCode = params.code = xhr.status;
params.message = xhr.statusText;
for (var i = 0; i < errors.length; i++) {
var error = errors[i];
if (error && error.hasOwnProperty('param')) {
var key = error.param,
msg = error.msg;
ajaxErrors[key] = ajaxErrors[key] || [];
ajaxErrors[key].push(msg);
}
}
[
'code',
'statusCode',
'message'
].forEach(function( key ) {
if (res.hasOwnProperty(key)) {
params[key] = res[key];
}
});
params.statusCode = res.statusCode;
scope.ajaxErrors.attr(ajaxErrors, true);
scope.attr(params);
scope.attr('isPending', false);
});
}
Toss in some helpers to iterate through these errors:
helpers: {
hasNotice: function( options ) {
var errors = this.attr('errors'),
statusCode = errors.attr('statusCode'),
message = errors.attr('message');
if (message && statusCode !== 400) {
return options.fn(message);
}
return options.inverse();
},
isSubmittable: function( options ) {
var validationErrors = this.attr('validationErrors');
if (can.Map.keys(validationErrors).length) {
return options.inverse();
}
return options.fn();
},
isValid: function( key, options ) {
if (this.attr('isDirty')) {
if (typeof key === 'function') {
key = key();
}
var errors = this.errors.attr(key);
if (errors) {
return options.inverse({
errors: errors
});
}
}
return options.fn();
},
isInvalid: function( key, options ) {
if (this.attr('isDirty')) {
if (typeof key === 'function') {
key = key();
}
var errors = this.attr('errors').attr(key);
if (errors) {
return options.fn({
errors: errors
});
}
}
return options.inverse();
}
}
The rest can be accomplished in the markup:
// handle specific cases
{{#is statusCode 404}}
<resource-not-found/>
{{/is}}
// You can build components to essentially trigger some sort of behavior as well
{{# is statusCode 401}}
// VM init -> can.route.attr(this.serialize());
<route-url screen="profile" action="authenticate"/> // redirect back to login.
{{/is}}
//
{{#hasNotice}}
<div class="u-1 has-error box-2 align-center">
<span class="notice">{{.}}</span>
</div>
{{/hasNotice}}
//
<div class="fieldset">
<input type="text" placeholder="Username" {($value)}="user.username"/>
{{#isInvalid 'username'}}
<span class="form-icon -incorrect"><i class="icon-times-circle-o"></i></span>
{{#errors}}
<p class="error">{{.}}</p>
{{/errors}}
{{else}}
{{#if user.username}}
<span class="form-icon -correct"><i class="icon-check-circle-o"></i></span>
{{/if}}
{{/isInvalid}}
</div>
// Use call expressions to pass in a tracking promise from save
<button class="button u-1" ($click)="submit(page.save())">Save</button>
Part of the beauty of this sort of methodology is that you seamlessly capture errors that couldn’t be handled before saving to the database. Something like a unique field for instance, really shouldn’t ever be verified before saving (exception: gamification), and is easiest handled by a unique/compound unique index on the DB level. Something previously tedious like “this slug is already used” is suddenly trivial.
Anyway: TLDR;
Global error handling is insane
Treating validation & error handling similarly makes life good
Love the DOM - use components to trigger business behavior when applicable
Hi Bajix,
Thanks for your input. I’ll give it a better look once I get time. I agree with you i.r.o. global error handling but I use that more as a catch-all than anything specific. Perhaps I’ll have some indicator to bypass global handling when required, etc. Haven’t gotten there yet.
I use an ASP.NET Web-Api so I don;t have middleware running on node.
Regards,
Eben