How can I call validate on no change in Backbone? - backbone.js

Set data that will fail validation
Model.set({phoneNumber : 'meow meow'})
Validation that applies a border color
validate: function(attr){
if(attr.phoneNumber){
var phoneNumber = attr.phoneNumber,
regex = /^\([0-9]{3}\)\s[0-9]{3}-[0-9]{4}/;
if(!regex.test(val)){
View.showError('businessNumber');
}
}
Later the user focus on the input field, I remove the error styling
The user doesn't change anything and the blur event is fired
Validation doesn't run because nothing changed.

Do not reference your view directly from your model. This is a completely deep and fundamental violation of the core tenet of the Model/View/* design. When you call set and validation fails, the model will emit an error event (as of Backbone 0.9.9) which your view should listen for and respond to by updating the view accordingly. You may also alternately pass a callback to set to handle the error, but events are the better choice in most situations. Note that your model needs to actually return an error object from validate, which will refuse to update the data, so after the user fixes the input and blur occurs, the data will actually change. With your code as is, Backbone thinks the validate call succeeded since no error is returned.
In your view, here's some pseudocode showing how to translate the model's error object into UI warnings:
initialize: function () {
_.bindAll(this);
this.model.on('error', this.showError);
},
showError: function (error) {
if (error.businessNumber) {
this.$businessNumber.addClass('error');
}
}
As a side note, don't feel bad about being confused with how to use backbone for forms. The fact that out of the box set won't accept invalid data is a huge stumbling block and non-intuitive. There are plugins to help with both forms and validation (https://github.com/documentcloud/backbone/wiki/Extensions,-Plugins,-Resources), but out of the box this is not one of the obvious/easy parts of backbone.

Related

Backbone validateAll

Question 1
I'm trying to add a form validation using the following link.
https://github.com/gfranko/Backbone.validateAll/blob/master/demos/index.html
The sample code is working perfectly. But in my case I have a form with corresponding View, when I click on the submit button how can I validate?
In my View's initialize() I have added the following code to enable validation for the fields.
var user = new User;
$('input').each(function() {
new Field({el: this, model: user});
});
this.model.set({route: formData.route}, {validate: true, validateAll: false});
But it is not validating.
My idea is validate a form with backbone and submit to PHP.
Using the code in the link how can I achieve it?
Question 2
I tried some other kind fo validations, Model save() error callback is not working for it.
this.model.save({route:formData.route}, {
error: function(){
console.log('error!!!'); // Not showing for failed validation
},
success: function(){
console.log('success!!!!!');
}
});
What could be the reason?
The main ideia is you have validators to perform when you set something on Model.
To do that, you'll need a code like this:
For example inside the model, you can check if the attribute has some value.
validation:
route : [
required: true
]
setNewValue:->
#set("route":"AnyValue")
The validation function will validate all the rules before the value be attached on model.
keep on mind that it's really important do this kind of thing inside your model. Avoiding your views to have theses rules.
To perform the validation before you save, try to add.
postData:->
validateModel = true
if #isValid(validateModel)
#save()....

Webshims - Show invalid form fields on initial load

(Follow on questions from Placeholder Hidden)
I'd like my form to validate existing data when it is loaded. I can't seem to get that to happen
I jQuery.each of my controls and call focus() and blur(), is there a better way than this? I tried to call ctrl.checkValidity(), but it wasn't always defined yet. When it was, it still didn't mark the controls.
I seem to have a timing issue too, while the focus and blur() fire, the UI does not update. It's as if the Webshims are not fully loaded yet, even though this fires in the $.webshims.ready event.
I also tried to call $('#form').submit(), but this doesn't fire the events as I expected. The only way I could make that happen was to include an input type='submit'. How can I pragmatically case a form validation like clicking a submit button would?
Here's a jsFiddle that demonstrates the problem. When the form loads, I want the invalid email to be marked as such. If you click the add button it will be marked then, but not when initially loaded. Why?
Focus and blur in the control will cause it to be marked.
BUT, clicking ADD will too (which runs the same method that ran when it was loaded). Why does it work the 2nd time, but not when initially loaded?
updateValidation : function () {
this.$el.find('[placeholder]').each(function (index, ctrl) {
var $ctrl = $(ctrl);
if( $ctrl.val() !== "" && (ctrl.checkValidity && !ctrl.checkValidity()) ) {
// alert('Do validity check!');
$ctrl.focus();
$ctrl.blur();
}
});
}
I see this in FF 17.0.5. The problem is worse in IE9, sometimes taking 2 or 3 clicks of ADD before the fields show in error. However, I get errors on some of the js files I've liked 'due to mime type mismatch'.
This has to do with the fact, that you are trying to reuse the .user-error class, which is a "shim" for the CSS4 :user-error and shouldn't be triggered from script. The user-error scripts are loaded after onload or as soon as a user seems to interact with an invalid from.
From my point of view, you shouldn't use user-error and instead create your own class. You can simply check for validity using the ':invalid' selector:
$(this)[ $(this).is(':invalid') ? 'addClass' : 'removeClass']('invalid-value');
Simply write a function with similar code and bind them to events like change, input and so on and call it on start.
In case you still want to use user-error, you could do the following, but I would not recommend:
$.webshims.polyfill('forms');
//force webshims to load form-validation module as soon as possible
$.webshims.loader.loadList(['form-validation']);
//wait until form-validation is loaded
$.webshims.ready('DOM form-validation', function(){
$('input:invalid')
.filter(function(){
return !!$(this).val();
})
.trigger('refreshvalidityui')
;
});

Stickit: How to trigger a change event after every model -> view change

I have many events bound to elements in my view, though when I use stickit js to change values in my view by altering the model it doesn't trigger an onChange event.
Is there a way that I can trigger an onchange event for the current model:element after the setting the value in the model without having to write a handler for every binding? This would be for all form elements, input/select/textarea.
I want to avoid the following for each form element on the page:
bindings: {
'#foo': {
observe: 'foo',
afterUpdate: 'forceChange'
},
'#bar': {
observe: 'bar',
afterUpdate: 'forceChange'
},
...
},
forceChange: function(el) { jQuery(el).change() }
One possible hack (with version 0.6.3 only) would be to define a global handler which matches all elements:
Backbone.Stickit.addHandler({
selector: '*',
afterUpdate: function($el) {
$el.trigger('change');
}
});
Since handlers are mixed in with other matching handlers and bindings configurations in the order that they are defined, you couldn't use afterUpdate in any of your bindings without overwriting this global, all-matching handler since the bindings configurations are the last to be mixed in. You can read more about it here.
Ahhh, that comment clarifies matters. So, in Javascript when you change an input's value "manually" (whether through jQuery or through someElement.value =) the browser won't, as you noticed, fire a change event. Change events (and most other events for that matter) are only fired in response to user actions, not to Javascript.
Luckily, just as you can "manually" change a value, you can also "manually" trigger an event. In jQuery the syntax for that is:
$(yourElement).trigger('change');
If you need to control things like e.target you can read up on the jQuery trigger documentation for the details, but that's the basic idea.
You can even chain the value-changing and event-triggering together if you want:
$(yourElement).val('newValue').trigger('change');

Backbone Validation firing multiple times

I'm wondering why the Validate method on my model runs multiple times when I add a model to a collection.
Even if I strip my model Validation right down to this...
Client.Model = Backbone.Model.extend ({
validate : function(attrs) {
if ( !attrs.first_name ){
return 'Required';
}
}
});
If I console.log() from inside the validate method I can see that it's been called 5 times. the first two validate successfully, the third fails, and then the 4th and 5th also pass (and subsequently it syncs correctly on the server)
This is creating a problem because I'm building a custom message plugin and it's being called all 5 times that the validation occurs.
I know it will correctly be called when I create a new model and retrieve models from the server. But what I cannot understand is this 'third' call to validate that always fails. (btw, I've managed to figure out that it is NOT a server issue)
I'm wondering what i'm missing here..
Thanks in advance.
:)
JSBIN - http://jsbin.com/ucowoq/2/edit
Check the console, obviously there's an error with the POST, but it shows the validate method running 5 times, on my app, it fails to validate on the 3rd every time! The server only ever returns a 500 error or the JSON for the created model.
Hope this helps anyone looking over this.
EDIT :
I've come up with this hack to get everything working correctly. I'm still not happy with the validate method being called 5 times, but because the 1 occurrence that caused the validation to fail contained an object with key & 'undefined' values, I'm just checking for that before returning anything. This allows me to implement my 'message' plugin as I can now retrieve errors at the correct time.
validate: function( attrs ){
if (attrs.first_name !== undefined){
if (!attrs.first_name)
return 'first name required';
}
}
The line that causes this confusion here is this: Backbone 0.9.9 Line 411 It clears the model's attributes before setting them again.
Why does it matter though? It will fail to validate, true, but the result of that validation is never used anywhere, so you shouldn't need that check for undefined in your edit.

Understanding Backbone Model set, validate and change callbacks

The Backbone documentation says:
Model.set will fail if validation fails - it won't set the value therefore it won't trigger any callback. We can pass { silent: true } to Model.set - then it will set the value but won't trigger any callback neither.
So,
Why does Backbone Model require a valid state to simply set an attribute value? What if we want to set attributes as the user interacts with the UI, but the model is not valid yet? It means change callbacks are unavailable unless we pass { silent: true } then manually trigger the change?!
Please say you know a better way of handling this :)
I'm not sure how to answer the Why questions but you could say that there are arguments for why it is good that set runs validations. For instance, it makes it dead simple to do client side validation in real time.
If your problem could be solved by only validating the value that is currently being changed by the user, you can do that by combining your validate method with the hasChanged method.
For example something like this:
Backbone.Model.extend({
defaults : { name : "" },
validate : function (attrs) {
var errors = {};
if(this.hasChanged("name") && attr.name.length == 0) {
errors.name = "Need a name yo!";
}
//...
if(_.keys(errors).length > 0) {
return errors;
}
}
})
In Backbone whenever you call set on model , It keeps track of what attributes of model has been changed and what attributes are newly added.Calling validate allows it be more efficient in doing it .Passing {silent:true} as options in set function causes validate and change to not execute so if doesnt fire any change events.
If you want to set attributes as the user interacts with the UI, but the model is not valid yet
In this case you can set the change in a plain object make sure object keys are sames as model's attribute and then at some point just set in your model.
var uiChanges = {name:'x'}; //just fill it with your changes
ur_model.set(uiModel); //then set it ,this way it fires change events only once
To check the diff between your plain object and model you can use the
ur_model.changedAttributes(uiChanges);
changedAttributes -
Return an object containing all the attributes that have changed, or false if there are no changed attributes.
You can further use it save only those attributes that have changed rather than saving entire model again.

Resources