I have a model for which I would like to save only the attribute title. This is what I have tried:
myBook.model.save(['title']);
The problem is that request.body is the whole myBook.toJSON() object, instead of just the relevant attribute title. Is that by design, or am I doing something stupid?
It's by design.
save calls Backbone.sync to persist changes to your backend, which in turn does, among other things:
if (!params.data && model && (method == 'create' || method == 'update')) {
params.contentType = 'application/json';
params.data = JSON.stringify(model.toJSON()); // <-- jsonifies the entire model
}
There are plenty of ways to override this behavior. You can give your model a sync method, in which case it will be called instead of Backbone's default sync. Or you could just override Backbone.sync to do what you want.
However, most server-side frameworks will be able to handle receiving the full JSON object and only updating the changed content. Why do you need to only send the changed attribute to the server?
Side note: the first parameter to save should be a hash of attributes: so {title: newBookTitle} as opposed to ['title']. But I'm guessing that was probably just a quick example typo.
Related
I have a Model where some of the data returned from the server is not editable by the client but is used for display purposes. However, there is some data that is editable and should be sent to the server. How should I separate these? I feel like this involves some sort of combination of overriding parse and toJSON or maybe save but I'm not sure how to go about this the right way. How would you do this?
My first reaction would be "don't mix the view state with the model", it would be much better to create a specific view-model, pass your data-model into initialize and set the fields you need on the view-model from the data-model there.
Overriding parse won't do anything since it's only used when parsing responses from the server.
Overriding toJSON and have it return only the attributes you're interrested in sending to the server should work. If you look at the Backbone source
// Ensure that we have the appropriate request data.
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
params.contentType = 'application/json';
params.data = JSON.stringify(options.attrs || model.toJSON(options));
}
It should be using your model's toJSON function as long as you didn't pass any attributes to save.
I'm trying to PUT the data and my model doesn't have an id.
Is it possible to explicitly tell the Save() method to PUT the data irrespective of ID.
The save method has an options parameter that can override anything on the XHR:
model.save(newVals, { type: 'PUT' })
You can also override the isNew method. PUT vs POST is determined by the result of that method. You'll also want to make sure the URL is being created correctly for new and non-new objects.
Also consider setting the idAttribute correctly so that your model does have an id field that can be used to generate a correct url. Using POST and PUT correctly (POST new items, PUT updates to items) makes your api more intuitive.
I want to call save on a Backbone model and have it write data to the server, but not update the client. How do I do this?
To clarify: when Backbone saves a model, it sends all the data to the server, and then retrieves the data from the server and updates it on the client. This second step is what I want not to happen.
To clarify further: The model ('parent' model) has an attribute which is itself a model ('child' model); when it's saved to the server, this child model is converted to JSON. When the parent model updates after the save, the attribute that previously contained a reference to the child model is replaced with the parsed JSON object of the child model that was saved. This is what I need not to happen.
When the data is initially pulled from the server, the parent model "reconstitutes" that object into an appropriate child model, but this is an expensive process and there is no reason to re-do it every time save fires on the parent model, since the child model will never change.
It sounds like you do not want to parse your model when you receive the response from the server on a model.save
You can try something such as:
model.save(attributes,{
success: function() { ... },
parse : false // This will be passed to your parse function as an option
});
You would have to set-up your parse function in your corresponding model as follows:
parse: function(resp,options) {
// don't update model with the server response if {parse:false} is passed to save
if (options && !options.parse) return;
/ ... rest of your parse code ... /
Backbone currently defaults options.parse to true. Here is a short-discussion on the topic.
As discussed in that thread, perhaps you want to consider why you do not want want to update the server response to the client. There may be a cleaner way to achieve the results you desire.
Depending on how/what your server setup is, all you really have to do is issue a regular AJAX request. This is exactly what backbone does in the background so you'll just bypass the client side logic.
You could do this with native JavaScript, but I'm fairly sure you have some other library in use that can make things much easier.
For the completeness of this answer, I'll give an example with jQuery:
$.ajax({
type: "POST",
url: "http://same.as.your.model",
data: { "the" : "model" },
dataType: "JSON",
success: function(){
// once the request has returned
}
});
The $.ajax function also has some additional functionality, and you can read about it in the jQuery docs.
On client you mean Views? If you want to save your model but not render your views which happens since save will trigger a change event, you should call save with option silent:true, or set a custom option like dontchange:true when calling save and check it in when handling change. I prefer the custom option, because silent has side effects (at least in my version of backbone 1.0.0)
a little code:
when you save:
model.save({},{dontchange: true});
you install your event listeners in the view:
this.listenTo(model, 'change', function(model, options){
if (options.dontchange)
return;
this.render();
});
I ran into same problem. model.save(attrs,{patch:true, parse:false}) really did not invoke parse method but model was still merged with server response.
It is not elegant, but this worked for me:
model.clone().save(attrs,{patch:true})
I believe it's best to avoid this situation by clean REST api design.
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.
When a save, or create is tossed towards the server, the server responds with a new randomly created object. The object can be one of many different Classes, and Backbone responds to these differentiating objects and loads a relative view.
I can only seem to figure this logic out on bootstrap, as no view has been loaded yet, so I can based on what information I am randomly receiving from the server, bootstrap and navigate to that specific route.
However, I am stuck on trying to figure out how to do this when I save an object, and receive my return data.
Here's my code broken down.
The information is saved.
#model.save(#model.toJSON(),
I have a listenener waiting for this save :
constructor: (options) ->
super(options)
#model.bind 'change:verb', _.chooser, options
_.maestra_chooser is a mixin I have in a utility belt :
_.mixin
_chooser : (item) =>
console.log item
Something to note here. The variable item is unfortunately, the same #model that was just saved. No new data there.
What I'm hoping for item to be is the new variable data from the server, so that I can take that data, see what kind of data it is, and then route to the relevant view.
This is where I believe I'm also making an architecturally unsound idea. But for reasons I don't understand enough to explain.
Does anyone know where I can access the return data from the server and appropriately navigate my app to that respective route?
Additional Information
This is how I bootstrap it appropriately :
window.router = new Project.Routers.QuestionsRouter(
{
words: #{ #words.to_json.html_safe }
});
Backbone.history.start();
router.navigate("#{#words.kind_of?(Array) ? "bar" : "foo"}", {trigger: true, replace: true})
The change event is only ever going to give you the model and the value that changed...
You can pass a success callback to your save:
#model.save(#model.toJSON(), success: (model, resp) ->
# do whatever with resp
)
where resp will contain the raw response from the server and model will contain the server side state of your model.
You can also bind to your model's sync event as mentioned in the comments:
#model.bind 'sync', _.masetra_chooser, options
the sync callback is called with arguments: model, resp and options where options is the set of options passed to save.
https://github.com/documentcloud/backbone/blob/9a12b7640f07839134e979b66df658b70e6e4fe9/backbone.js#L383
Not really sure why you are expecting to get data back from a save that'll change your page though. Seems a bit odd.
What type of data are you expecting to receive after a save that wouldn't be in your model?