How i can refresh model's attributes when invoke save using backbone - javascript

How i can refresh model's attributes when invoke save()
I am using backbone and backbone relational. Have the following code:
saveParams: function(event){
var self = this;
this.model.save({}, {
success: function(model, resp, xhr){
model = ...
},
error: function(model, resp){
alert(JSON.stringify(resp));
}
});
$(this.el).effect("highlight", {}, 1000);
event.preventDefault();
},
When pass callback success, the parameters "model" has ald attributes (before save), resp is holding updated attributes. How i can update attributes in a model?
model.set(resp) doesn' help me
model.set(JSON.stringify) doesn't help me
UPD1: I use Backbone RelationModel cause have nested models. Nested models doesn't refresh when success callback invokes. I guess because RelationModel using Backbone.Store.
UPD2: For me works only this:
model.clear()
model.set(resp);
model.change();
I know it's ugly, but it's working )

Normally you don't have to do this!
Backbone automatically parses the response if a save() command, as you can see here:
http://documentcloud.github.com/backbone/docs/backbone.html#section-41
If your response data differs from the default assumed backbone data structure you should take a look at the Backbone.Model#parse method and maybe overwrite it (it's a very simple method).

Related

Using Backbone.Collection fetch but value not including

I thought that initializing the collection returned a ready instance of Backbone.Collection. After the fetch, however, the collection contains some models, but in require, _this.serviceCollection.toJSON() gives me undefined object.
My Backbone.Collection:
var ServiceCollection = Backbone.Collection.extend({
model: Backbone.Model,
initialize: function(options){
this.fetch({
url: 'service/' + options + '/',
async: false,
success: function(collection, response, options){
collection.set(response.result);
}
});
}
});
return ServiceCollection;
My CollectionView showing:
OpenServices: function(category){
var _this = this;
require([
'service/js/service_collection',
'service/js/service_collection_view'
], function(ServiceCollection, ServiceCollectionView){
_this.serviceCollection = new ServiceCollection(category);
_this.serviceCollectionView = new ServiceCollectionView({
collection: _this.serviceCollection
});
_this.categoryLayout.categoryItems.show(_this.serviceCollectionView);
});
}
What's wrong this code?
Mistake was in sync/async request. My fetch was async: false, so I'm just change it to async: true and a set event on reset collection for that collectionView.
I suspect the issue you are having is related to your success callback. There should be no need to call collection.set(...) to add the models to the collection; that is done automatically for you when the fetch succeed. In fact, the documentation around Collection.set provides this nugget:
... if the collection contains any models that aren't present in the list, they'll be removed.
I suspect that response.result in your success callback doesn't contain any of the data you think it does. Because of that, your call to collection.set(...) is actually removing all items from the collection.
Try removing that success callback & see what else happens.
Somewhat unrelated but still important:
Using Collection.fetch(...) synchronously is considered bad practice; if your server takes longer than a few hundred milliseconds to return the data, the browser may lock up.
Specifying the url as a parameter to Collection.fetch(...) is not a terrible idea, but consider extending Backbone.Model and specifying the urlRoot parameter instead. If your server follows REST-ful conventions, it makes it very easy to create/update/delete data that way.

Backbone sync issue firing POST instead of PUT

I have written the following code in my model:
urlroot: '/url/sms',
setAuthId: function(value) {
var _this = this ;
if (this.get("smsauth") != value) {
this.set("smsauth",value);
this.save();
//Ideally, I want to achieve this AJAX call request with backbone.
// $.ajax({
// url: "/url/sms",
// data: value,
// type: 'PUT',
// processData: false,
// success: function(result) {
// _this.set("authId", value);
// },
// error : function(){
// console.log('Error setting authid');
// }
// });
}
},
Ideally, we should be firing a "PUT" request everytime. But the backbone is firing a POST request because "ID" is not present.
I'm quite new to backbone, I was wondering if there is anyway to sync with server without having to pass an ID? How can I solve this problem?
I basically want to fire a PUT request NOT post request for the URL. (Since my backend only supports PUT request).
The only real way to force Backbone.Model.save() to do a PUT is the way #dbf explained, you have to set your idAttribute. To properly set idAttribute your model should have an attribute that is unique. (This is not a hard requirement, since model.isNew() just checks that your model has a property named id or whatever string you supply to your model idAttribute property. It doesn't check for uniqueness).
I sense that in your case, you may not have a unique attribute in your models, so setting idAttribute may be a challenge. For that reason, I suggest you don't specify an idAttribute in your model definition. Rather, we just handle it dynamically.Just refactor your code like this:
setAuthId: function(value) {
var _this = this ;
if (this.get("smsauth") != value) {
// any model attribute is fine, we just need to return a prop
this.prototype.idAttribute = "smsauth"
this.save("smsauth",value) // Save will do a set before the server request
// model.save returns a promise. Here we'll reset the idAttribute
.then(/* success handler */ function (response) {
_this.set("authId",value);
_this.prototype.idAttribute = 'id' // You can set this to "authId" if that
// uniquely identifies this model
},/* error handler */ function (response) {
_this.prototype.idAttribute = 'id' // reset idAttribute to default
});
}
}
It's not really clear to me what you are saving. The fact you are handling with POST suggest its a new entry. Quoting from the docs this behaviour is correct. PUT is update, POST is create (under CRUD operations).
If the model isNew, the save will be a "create" (HTTP POST), if the
model already exists on the server, the save will be an "update" (HTTP
PUT).
What you might try is what #Sami suggest by overwriting the save with an update request (do realise that all solutions here are incorrect/workarounds).
If you really need PUT and cannot alter your backend for some mysterious reason to accept POST, you could change the idAttribute within the model.
var smsModel = Backbone.Model.extend({
idAttribute: "smsauth" // for example
});
Do realise that your backend, very likely, has a design flaw where you are changing and creating workarounds to work with it, which you should consider to avoid.
I have used this snippet.
this.save({}, {
type: 'PUT'
});
I found all your answers really fascinating, I am going to try everyone of them.
Thanks for your suggestions, this is why I like SO. Something new to learn.
You can override save method.
Something like
var model = Backbone.Model.extend({
save: function(options){
return Backbone.sync("update", this, options);
}
});

backbone.stickit and html-form: How to save (patch) only changed attributes?

tl;dr
How to use backbone.stickit with a html form to change an existing model fetched from the server and only PATCH the changed attributes (changed by user input within the html form) to the server?
/tl;dr
I'm using backbone.stickit in a backbone.js application to bind a model to a HTML-form which is part of a backbone view. This works fine so far, but it becomes a little bit complicated if I'm going to save the bound model. This is because I want to use the PATCH-method and only send the changed attributes to the server. I try to illustrate what I've done so far:
Fetching the model from Server
user = new User(); //instatiate a new user-model
user.fetch(); //fetching the model from the server
console.log(user.changedAttributes()); // Returns ALL attributes, because model was empty
The last line indicates my problem, because I thought I can used the changedAtrributes() method later to get the attributes which need a patch on the server. So I tried this workaround which I found here
user.fetch({
success: function (model, response, options) {
model.set({});
}
});
user.changedAtrributes(); //Returns now "false"
Do stickit-bindings
Now I render my view and call the stickit() method on the view, to do the bindings:
//Bindings specified in the view:
[...]
bindings: {
"#username" : "username"
"#age" : "age"
}
[...]
//within the render method of the view
this.stickit();
The bindings work fine and my user model gets updated, but changedAttributes() remain empty all the time.
Save the model to the server
If the user has made all required changes, the model should be saved to the server. I want to use the PATCH method and only send the changed attributes to the server.
user.save(null, {patch:true}); //PATCH method is used but ALL attributes are sent to the server
OR
user.save(user.changedAttributes(),{patch : true});
With the second approach there are different outcomes:
if I didn't use the user.set({}) woraround, all attributes get PATCHED to the server
if I use the user.set({}) woraround the return value of changedAttributes() is "false" and all attributes are PUT to the server
if I call a user.set("age","123") before calling save(), then only the age attribute is PATCHED to the server
So outcome 3 is my desired behaviour, but there are 2 problems with this: First stickit doesn't seem to use the set() method on the model to update the attributes if they are changed within the html-form. And second, if you call set() with one attribute and afterwards with another, only the second attributes is returned by changedAttributes().
Maybe I just overseen something in the backbone or backbone.stickit docs, so I didn't get the desired behaviour working. Any ideas about that?
NOTE: As found out the problem wasn't directly related to backbone.stickit, more to backbone itself.
Solved this problem on my own, maybe this helps someone who may stumble upon this question:
Backbone only keep track of unchanged attributes, but not of unsaved attributes. So with
model.changedAttributes();
you will only get the attributes of the model, which was changed since the last
model.set("some_attribute","some_value")
Finally I stumbled upon backbone.trackit which is a backbone.js plugin maintained by the creator of backbone.stickit. With this plugin you can track unsaved attributes (all attributes which have changed since the last model.save()) and then use them in the save-method of a model. Example (my usecase):
Backbone.View.extend({
bindings: {
"#name" : "name",
"#age" : "age"
},
initialize: function () {
this.model = new User();
this.model.fetch({
success: function (model, response, options) {
//this tells backbone.stickit to track unsaved attributes
model.startTracking();
}
});
},
render: function () {
this.$el.html(tmpl);
this.stickit();
return this;
},
onSaveUserToServer: function () {
//first argument: only unsaved attributes, second argument: tell backbone to PATCH
this.model.save(this.model.unsavedAttributes(), { patch: true });
});
});

backbone.js View doesn't reset

I need to work with backbone.js, i can't go to "render" part inside my view here is my code:
var Vigne = {
Models:{},
Collections: {},
Views:{},
Templates:{}
}
Vigne.Models.Raisin = Backbone.Model.extend({})
Vigne.Collections.Grape = Backbone.Collection.extend({
model: Vigne.Models.Raisin,
url: "./scripts/data/vin.json",
initialize: function (){
console.log("grape initialised");
}
});
Vigne.Views.Grape= Backbone.View.extend({
initialize: function(){
_.bindAll(this,"render");
this.collection.bind("reset",this.render);
},
render: function(){
console.log("render");
console.log(this.collection.length);
}
})
Vigne.Router = Backbone.Router.extend({
routes:{
"": "defaultRoute"
},
defaultRoute: function(){
console.log("defaultRoute");
Vigne.grape = new Vigne.Collections.Grape()
new Vigne.Views.Grape ({ collection : Vigne.grape });
Vigne.grape.fetch();
console.log(Vigne.grape.length);
}
}
);
var appRouter= new Vigne.Router();
Backbone.history.start();
I am expecting it to display my collection's length in the debugger's console, it seem's like it doesn't reset. Any ideas?
Edit:
i added this within the fetch function:
success: function(){
console.log(arguments);
},
error: function() {
console.log(arguments);
}
});
and the fetch function succeed on getting the json file, but it doesn't trigger the reset function.
i solved this problem by setting the attribute within the fetch function to true:
Vigne.grape.fetch({
reset:true,
error: function() {
console.log(arguments);
}
}
);
This book helped me : http://addyosmani.github.io/backbone-fundamentals/
Backbone calls reset() on fetch success which in turns triggers reset event. But If your fetch fails due to some reason, you won't get any event. So you have to pass an error handler in fetch method and use it to identify the error and handle it.
Vigne.grape.fetch({
error: function() {
console.log(arguments);
}
});
You can also pass success call back and you will be able to know the problem in your fetch.
You can also use Charles proxy/Chrome Debuuger Tool to identify if you are getting proper response from your backend.
Can you please paste your response what you are getting from server. You may vary the data but just keep the format right.
Edit:
One more problem I can see is that you have not defined attributes in your model So after Backbone fetch, it refreshes your collection with the new models fetched from server. Fetch method expects an array of json objects from server and each json object in response array should match with the attributes you have set in defaults in your model Otherwise it won't be able to create new models and hence won't be able to refresh your collection. Can you please rectify this and let me know.

Adapt my old work flow to Backbone

Im starting to build a new app and I would like to use Backbone as my framework. Below is a basic workflow that this (and most apps) follow.
What is the correct/best model to use with Backbone?
Old Way
User navigates to a page.
Selects "Create New widget"
User is presented with a form filled with inputs
At this point I would probably take the values entered (after passing basic validation), wrap them up and send them to the server via an ajax request
Request comes back as "OK" and the user is taken somewhere else (This step isn't entirely important)
Some basic pseudo-code
// Grab values
var userName = $('.UserName').val(),
dateOfBirth = $('.DateOfBirth').val();
...
...
...
$.ajax({
url: "/Webservices/ProcessStuff",
success: function(result){
if (result) {
// Render something or doing something else
} else {
// Error message
}
},
error: function () {
// Error message
}
});
Backbone way
Using the same example as above; I assume I'd have a model for the user information and a view to display the inputs. However, processing the actual call to the web service is one of the things I'm confused about. Where does this need to go? In the model or in the view click of some "Go" button?
Model.UserInformation = Backbone.Model.extend({ username: null, dateOfBirth: null });
Maybe also have a collection of these UserInformation models?
UserInformations = Backbone.Collection.extend({ model: Model.UserInformation' });
So bottom line what I'm asking is...
What is the best way to achieve this functionality?
What is the proper way to actually perform CRUD? Where to put the actual call to delete/update/create/etc?
You have the right idea and Backbone should make it easy for you to get things done using the same basic high level overview of your workflow. Note that you're still going to be using jQuery for this functionality - you'll just be doing it through the organizational aspects of Backbone's types.
There are a couple of key items that you'll want in place, most of which you already mentioned:
A backbone View to coordinate the HTML elements with your Javascript code
A backbone Model to store all of the data that the user input into the HTML elements
A back-end server that can handle RESTful JSON calls via AJAX requests from jQuery
I think the only thing you are missing is that the model has a save method on it, which wraps up all of the logic to call the create / update routes on your back-end server. The model also has a delete method to handle deletion from the server.
As a very simple example, here's a form that renders an HTML template to the screen, gathers the user input in to the model and then saves it to the server.
An HTML template:
<script id="myTemplate" type="text/x-jquery-tmpl">
First name: <input id="first_name"><br/>
Last Name: <input id="last_name"><br/>
<button id="save">Save!</button>
</script>
The code to run this:
MyModel = Backbone.Model.extend({
urlRoot: "/myModel"
});
MyView = Backbone.View.extend({
template: "#myTemplate",
events: {
"change #first_name": "setFirstName",
"change #last_name: "setLastName",
"click #save": "save"
},
initialize: function(){
_.bindAll(this, "saveSuccess", "saveError");
},
setFirstName: function(e){
var val = $(e.currentTarget).val();
this.model.set({first_name: val});
},
setLastName: function(e){
var val = $(e.currentTarget).val();
this.model.set({last_name: val});
},
save: function(e){
e.preventDefault(); // prevent the button click from posting back to the server
this.model.save(null, {success: this.saveSuccess, error: this.saveError);
},
saveSuccess: function(model, response){
// do things here after a successful save to the server
},
saveError: function(model, response){
// do things here after a failed save to the server
},
render: function(){
var html = $(this.template).tmpl();
$(el).html(html);
}
});
myModel = new MyModel();
myView = new MyView({model: myModel});
myView.render();
$("#someDivOnMyPage").html(myView.el);
This will give you a quick start for a form that saves a new model back to the server.
There are a couple of things your server needs to do:
Return a valid HTTP response code (200 or some other response that says everything was "ok")
Return the JSON that was sent to the server, including any data that the server assigned to the model such as an id field.
It's very important that your server do these things and include an id field in the response. Without an id field from the server, your model will never be able to update itself when you call save again. It will only try to create a new instance on the server again.
Backbone uses the id attribute of a model to determine if it should create or update a model when pushing data to the back end. The difference between creating a new model and saving one is only the id attribute. You call save on the model whether it's a new or an edited model.
A delete works the same way - you just call destroy on the model and it does a call back to the server to do the destroy. With some HTML that has a "delete" link or button, you would attach to the click event of that HTML element the same as I've shown for the "Save" button. Then in the callback method for the delete click, you would call this.model.destroy() and pass any parameters you want, such as success and error callbacks.
Note that I included a urlRoot on the model, as well. This, or a url function are needed on a model if the model is not part of a collection. If the model is part of a collection, the collection must specify the url.
I hope that helps.
If the "el" of the view is the form tag, then you could probably use the built in event object to bind a function to submit, but if the root of the view is something else, then you'll need to attach the click handler in the render function.

Categories