I have some code:
var Person = new Backbone.Model({name: 'Jeremy'});
Person.validate = function(attrs) {
if (!attrs.name) {
return 'I need your name';
}
};
Person.on("invalid", function(model, error) {
alert(model.get("title") + " " + error);
});
Person.set({name: 'Samuel'});
console.log(Person.get('name'));
// 'Samuel'
Person.unset('name', {validate: true});
console.log(Person.get('name'));//Why can i print name here if it unsetted?
When i type unset method i see an error alert. It's correct. But why can i print in console the name if it was unsetted?
The name is still there because the validation failure stopped the unset from doing anything.
The documentation isn't very explicit about how validation works with set and unset but it is quite explicit with save:
validate model.validate(attributes, options)
[...] If validate returns an error, save will not continue, and the model attributes will not be modified on the server.
So it is reasonable to think that validation errors will prevent the current operation (set, unset, save, ...) from changing anything.
You can see how it works by examining the Backbone source code. First, you need to know that unset is just a set call in disguise:
unset: function(attr, options) {
return this.set(attr, void 0, _.extend({}, options, {unset: true}));
}
So we look at set:
set: function(key, val, options) {
// A bunch of boring bookkeeping stuff...
// Run validation.
if (!this._validate(attrs, options)) return false;
// The stuff that changes attributes and triggers events.
}
The validations happen as soon as set knows what it is working with and set returns without changing anything if the validations fail.
The Backbone documentation leaves a lot of important things out so you need to be passingly familiar with the Backbone source if you're going to use Backbone. The source is fairly straight forward, don't be afraid to jump into it to see what's going on.
Do this: To not trigger events you can use the silent:true option. I believe there might be some issue upstream in your code. Anyways, do the following - it should work. ( in my tests, it did ).
Person.unset('name',{validate: true,silent:true})
p.s.: Mu ( below ) gives great information.
Related
In my controller i have this add function:
public function add(){
$this->layout = false;
if($this->request->is('post')){
$this->Paciente->create();
$this->Paciente->set($this->request->data);
if ($this->Paciente->validates()) {
if($this->Paciente->save()){
$this->set('status', '200');
} else {
// debug($this->Paciente->validationErrors);
// ini_set('memory_limit', '-1');
// var_dump($this->Paciente);
$this->set('status', '101');
// $log = $this->Paciente->getDataSource()->getLog(false, false);
// debug($log);
// die;
}
} else {
// didn't validate logic
// $errors = $this->Paciente->validationErrors;
// debug($errors);
$this->set('status', '100');
}
} else
$this->set('status', '404');
}
I'm sending the post info and the status is always 101. As you can see I have tried a lot to find out where is the error, but no luck.
Also I don't have any callback in my Model (beforeSave, afterSave...)...
Anyone knows what is happening?
Your
if ($this->Paciente->validates()) {
if($this->Paciente->save()) {
}
is wrong.
You are validating twice.
Please - as documented - either use false (save(null, false)) to not invalidate twice or simply remove the validates() part here.
You can directly do:
if ($this->Paciente->save($this->request->data)) {}
This itself is unlikely causing your issue, though.
Or do you have some custom validation rules that on second (and faulty) trigger will make a field invalidate?
You can confirm that by checking $this->Paciente->validationErrors afterwards. It should be empty. If it does not save (and it's for sure no callback), and if it does not throw exceptions due to SQL errors then it most likely are your validation rules.
The problem was in the way i was sending the post info:
I was setting the variables as data['Model']['var'] and the correct way is data[Model][var]
Thanks #mark, #AD7six, #liyakat and #Alex for your help in the problem.
I can't imagine. When you set your array values like above it would likely break in php5.3 >
Backbone changed a few things about validation between these versions, first off, you now have to explicitly pass {validation: true} with a set call for validation to trigger. There must have been another change too, because this doesn't work no longer.
model.set(obj, {
error : function(model, error){
//Do stuff with error
}
})
I found this ticket on backbone's github, but it only answers the issue if using save, not set.
https://github.com/jashkenas/backbone/issues/2153
Here's the solution I've discovered.
1) Assign the set to a variable called success (or whatever you like)
var success = model.set(obj, {validate : true});
2) Check the status of success, and then use model.validationError
if(!success){
var error = model.validationError;
// Do stuff with error
}
The problem
I want to have a default error handler in my app that handles all unexpected errors but some time (for example when saving a model) there are many errors that can be expected so I want to handle them in a custom way rather than show a generic error page.
My previous solution
My Backbone.sync function used to have this:
if(options.error)
options.error(response)
else
app.vent.trigger('api:error', response) # This is the global event channel
However, this no longer works since backbone always wraps the error function so it can trigger the error event on models.
New solution 1
I could overwrite the fetch and save methods on models and collections to wrap options.error and have the code above there but this feels kinda ugly.
New solution 2
Listen to error on models, this won't allow me to override the default error handler though.
New solution 3
Pass in a custom option to disable the global triggering of the errors, this feels redundant though.
Have I missed anything? Is there a recommended way of doing this?
I can add that I'm using the latest version from their git repo, not the latest from their home page.
Could you do this in your overridden sync? This seems to accomplish the same thing you did before.
// error is the error callback you passed to fetch, save, etc.
var error = options.error;
options.error = function(xhr) {
if (error) error(model, xhr, options);
// added line below.
// no error callback passed into sync.
else app.vent.trigger('api:error', xhr);
model.trigger('error', model, xhr, options);
};
This code is from Backbone source, I only add the else line.
EDIT:
This is not the prettiest solution, but might work for you. Create a new Model base class to use, instead of extending Backbone.Model, extend this.
var Model = Backbone.Model.extend({
// override fetch. Do something similar for save, destroy.
fetch: function(options){
options = options ? _.clone(options) : {};
var error = options.error;
options.error = function(model, resp) {
if (error) error(model, resp);
else app.vent.trigger('api:error', resp);
};
return Backbone.Model.prototype.fetch.apply(this, [options]);
},
});
var MyModel = Model.extend({});
var model = new MyModel();
model.fetch(); // error will trigger 'api:error'
Actually, this might be better than overriding sync anyways.
Possible alternative is to use this: http://api.jquery.com/ajaxError/.
But with that, you will get the error regardless of whether you passed in an error callback to backbone fetch/save/destroy.
So when I save a model on the backend, My api send back a response telling everything went fine and giving you some other pointers in json format
My problem is that backbone think I want to use that response as attributes of my model and automatically dump them in the model attributes..
I just saved it on the front-end and do not want to save the attributs again.
That is the what Backbone.Model.parse is for. By default, it is just a pass-through, so you don't need to call "super".
Let's say you only care about two properties that come back (id and foo) and you don't care about anything else:
var myModel = Backbone.Model.extend({
parse : function(resp, xhr) {
return {
id: resp.id,
foo: resp.foo
};
}
});
Note that I included "id" in my example. It is really important that creates (POST) return an id property. Without it, the Backbone model won't know how to update/delete in the future. Even if it has a different name for id (like "objectId"), you should still set the id in this function.
Indeed that's the default behaviour, and if you want to change it, you have to overwrite some Backbone functions.
Looking at how save is implemented, you have two options - either overwrite save for your model, or overwrite parse to make it aware of the data you are sending.
http://documentcloud.github.com/backbone/docs/backbone.html#section-41
Or, you could give up sending the 'pointers' in the response, because an empty response means that the model does not change after save.
I have the exact issue you are encountering. Backbone is a pretty young framework with the additional fact that javascript is really dynamic. So the saying that there are a thousand ways to solve a problem applies really well here.
I think a more suitable way to go about this is to employ something called Mixins. Here's what I did:
define([
'underscore',
'backbone',
'jquery'
], function (_, Backbone, $) {
return {
parse: function(response, xhr){
var data = response;
if(response.response && response.response.status != 0){
return {};
}
if(response.response && response.response.data)
{
data = _.first(response.response.data);
if(typeof data == 'undefined'){
data={};
}
}
if(_.isFunction(this.postParse)){
return this.postParse.call(this, data);
}
return data;
}
}
});
As you can see, I've overridden the Backbone.Model.parse prototype method and came up with my own that takes in a response. And parse the response according to my server's spec. In you case, you would implement whatever it takes to understand your server's response.
With the ground work out of the way, specifying a model is very easy:
var group = Backbone.Model.extend(
_.extend({}, ModelMixin, {
initialize:function () {
}
})
);
return group;
Voila! All the parse methods and checks that you need to write is encapsulated in that one ModelMixin "superclass".
There should be a glitch in my syntax, or something I haven't understood, but when I am doing a save method on my model. The error callback is called whatever the outcome of the method.
#new_activity = new Activity()
#new_activity.save
know: $('input#know').val()
learn: $('input#learn').val()
success: -> console.log 'success'
error: -> console.log 'error'
In my case, since I do not really know whether the new_activity has effectively passed the validation, I have to do an ugly trick to add the activity to the collection. (By the way, I do not use the create method since I do want to have the different errors, and not a simple "false".
if #new_activity.has('know') and #new_activity.has('learn')
app.collections.activities.add #new_activity
When it is successful though; there is an alert of the created model.
Edit: Further details.
Here is my model:
initialize: ->
_.bindAll #, 'validate', 'errorHandler'
#.bind 'error', #errorHandler
validate: (attrs) ->
errors = []
# We only support a certain number of languages, we enforce that the user does not select a wrong set.
if _.isEmpty(_.intersection([attrs.know], ['en', 'fr'])) is true
errors.push 'This language is not currently supported.'
if _.isEmpty(_.intersection([attrs.learn], ['en', 'fr', 'de', 'es', 'zh', 'pt', 'ar', 'ja', 'ru'])) is true
errors.push 'You cannot learn this language yet.'
if _.isEmpty(errors) is false
errors
errorHandler: (model, error) ->
console.log error
When the validation occurs, and if the validate method returns nothing, it still triggers the error event, and the error variable contains the model (but no error message).
You should check whether the new_activity is saved properly, please verify that the server returns success response to the PUT request.
Furthermore, I have had issues with using Rails 3.1 standard format.json { head :ok } because it returns a single space as a reponse and application/json as content type. Backbone then tries to parse JSON and dies with an error.
The problem is the single space that Rails sends back with head :ok. If you send back an empty string, Backbone handles it fine.
The proper way to do that is to send back a 204 No Content. You can do that by replacing head :ok with head :no_content in the format.json block. Rails will not send back anything in the HTTP body.
I'm not sure what the cause of your error is, but I think I can help you pin it down:
Looking at the annotated source for Backbone.js, you can see that save() defers to sync(), which in turn defers to jQuery's $.ajax. So ultimately, your error function (wrapped by Backbone's wrapError) is called from that.
Unfortunately, the wrapper discards all but the jqXHR argument, making debugging a bit tricky. So you might want to try hacking your local Backbone.js source file to replace the existing wrapErrorfunction with something like this:
var wrapError = function(onError, model, options) {
return function(jqXHR, textStatus, errorThrown) {
console.log(jqXHR);
console.log(textStatus);
console.log(errorThrown);
if (onError) {
onError(model, jqXHR, options);
} else {
model.trigger('error', model, jqXHR, options);
}
};
};
Then, at least, you'll see all of the debugging data provided by jQuery on your console.