Update id after collection.create in Backbone.js - javascript

On a collection, I am using the create function to save a new instance of a model to the server. This POST request is successful and I return the new model.
{id:135, type:tweet, start:08:00:00, end:14:00:00, text:foo, date:2013-04-01}
However, I need to update the collection with the new model.id that has been returned from the server and it appears that the sync method in create does not do this. Should I add a callback to the create function to update the model with it's changed attributes?
var AddScheduleBlock = Backbone.View.extend({
saveScheduleBlock: function (ev){
var text = "foo"
this.model.set({
text: text
});
var block = blockCollection.create(this.model, {
silent: true,
wait: true
});
});

Backbone's Collection create method calls Model save method underneath (code reference). You don't need to set the id yourself, Backbone does that for you.
The Model save method sets the response attributes into the model (code reference)
If the create is not doing the job, you should try debugging it.

Related

How to re fetch backbone collection after collection create?

Can someone tell me how to re fetch a Backbone collection after calling collection's create function when I create a new model?
When I call fetch on my collection after creating new model, sometimes I'm getting that model and sometimes not.
My problem is when I create a new model in my collection, I'm not getting the id back of my model and then I can't update it immediately, I need to refresh the page and then I got the id of the created model.
I tried with listenTo but I can't use it because I need to send more collections to one function.
And that my view for my bootstrap modal, on save I'm creating my model it persists to database and I'm getting all attributes in my console when I create it except models id.
Backbone view:
app.types.EditView = Backbone.View.extend({
tagName: "div",
$container: $('#containerEdit'),
template: _.template($('#itemEdit-template').html()),
events:
{
"click .save": "save",
},
initialize: function(options)
{
this.options = options;
this.$container.html(this.render());
this.start();
this.end();
},
render: function()
{
this.$el.html(this.template());
return this.$el;
},
save: function()
{
console.log("save");
$('#openModal').modal('hide');
var dan = this.model.dan_u_tjednu_usera.datum;
var mjesec = this.model.dan_u_tjednu_usera.mjesecBrojevi;
var godina = this.model.dan_u_tjednu_usera.godina;
var start = $("#start").val();
var end = $("#end").val();
var user_id = this.model.user.id;
this.model.shifts.create({day: dan, month: mjesec, year: godina, time_from: start, time_to: end, user_id: user_id});
this.options.model.el.html($("<td href='#openModal' width='25%' align='center' class='list-group test' scope='row'>" + start + " - " + end + " " + "Admin" + "</td>"));
this.model.shifts.fetch({sync: true});
console.log("test", this.model.shifts);
}
Here you can see that in my response im not getting the id attribute, on create.
And here you can see when i click on my cell i log my collection and i have not the id attribute of the created model here. And im not getting the id attribute it too when i log this.model
This is because the request sent to the server when you call Collection.create is asynchronous, the Javascript code will continue to execute before the server receives and responds to the request.
If you want to have the Model updated with the ID coming back from the server, you can specify {wait: true} in the Collection.create call. This will mean that the Collection will not have the Model added straight away, but instead only when the server responds (successfully).
In this case you should not run the fetch immediately afterwards, as it will also need to wait for the create operation to complete. You should setup any following actions to trigger when the create operation has completed. Here is an example:
var model = collection.create({field: 'abc'}, {wait: true});
model.once('sync', function() { window.alert(model.id); });
Backbone's create
Convenience to create a new instance of a model within a collection.
Equivalent to instantiating a model with a hash of attributes, saving
the model to the server, and adding the model to the set after being
successfully created.
There's no need to fetch a collection after a create, the model id and any other field are automatically merged within its attributes hash.
Fetch after model creation
While mikeapr4 is not wrong, his example could be improved.
The { wait: true } is unnecessary if the only problem comes from the fetch, not from the model already being inside the collection.
Also, once should be avoided as it's the "old" way, and instead listenToOnce should be used. See Difference between ListenTo and on.
If you really want to fetch once a model is created, using events is overkill here, and instead, using the success callback is best:
save: function() {
// ..snip...
this.model.shifts.create({ /* ...snip... */ }, {
context: this,
success: this.onModelCreated
});
},
onModelCreated: function() {
// the model is now created and its attributes are up-to-date
this.model.shifts.fetch();
}
Other notes on your code
There are no sync option in Backbone. Only a "sync" event and a sync function.
Avoid using the global jQuery selector (like $('.class-name')) and instead, whenever the element is within the view's element, use this.$('.class-name').
Also, cache the jQuery element to avoid the costly search of the find method.
Like $("#start") could be cache and reused. Only reset the cached elements when re-rendering.
The Backbone .render function should return this by convention.
Then, your rendering call should look like:
this.$container.html(this.render().el); // el is enough

consuming RESTful api with backbone

I have a backbone application and a RESTful api. I used the sample created by Coenraets to understand the architecture of a backbone app, but I decided to setup my own structure and just use the data for testing.
I want to know the best way to return data from the RESTful api. I currently have my app folder structure setup with model, collection, view and service folders. I have a node server running with express that handles the backend and is working fine.
What I want to know is what is the best practice for accessing the restful data api? Should I do that in my service class or in my view class? How do I make this work dynamically using the returned data from my restful api: http://localhost:3000/employees
It seems like there are many ways to do this and for now I just want something that works, but eventually I do want to know what is the best way to do it. Ultimately I want to have a CRUD setup. But I'm not sure where that should be setup. Similar to what is detailed here: http://www.codeproject.com/Articles/797899/BackBone-Tutorial-Part-CRUD-Operations-on-Backbone
My files are as follows:
employeecolletion.js
var Backbone = require('backbone');
var Employee = require('../models/employeemodel.js');
module.exports = Backbone.Collection.extend({
model: Employee,
url:"http://localhost:3000/employees"
});
employeemodel.js
var Backbone = require('backbone');
var EmployeeCollection = require('../collections/employeecollection.js');
module.exports = Backbone.Model.extend({
urlRoot:"http://localhost:3000/employees"
// initialize:function () {
// this.reports = new EmployeeCollection();
// //this.reports.url = this.urlRoot + "/" + 1 + "/reports";
// }
});
employee.js (employee view that binds to my template)
var fs = require('fs');
var base = require('./base.js');
var EmployeeList = require('../collections/employeecollection.js');
var employeeService = require('../services/employeeService.js');
var template = fs.readFileSync('app/templates/employee.mu', { encoding: 'utf8' });
module.exports = base.extend({
el: '.view',
template:template,
collection: employeeService.collection,
initialize: function () {
this.viewModel = {
employee_list: this.collection.toJSON()
//employee_list: this.collection.fetch() --HERE I EXPERIMENTED WITH FETCHING THE DATA
};
this.render();
}
});
employeeservice.js (file in service folder that would ideally return the collection which I would just bind to my template in they employees view file)
var EmployeeCollection = require('../collections/employeecollection.js');
//if wanting to pass in data manually
var employee_list = [
{
id:1,
firstName:"James",
lastName:"King",
fullName:"James King",
managerId:0,
managerName:"",
title:"President and CEO",
department:"Corporate",
cellPhone:"617-000-0001",
officePhone:"781-000-0001",
email:"jking#fakemail.com",
city:"Boston, MA",
pic:"james_king.jpg",
twitterId:"#fakejking",
blog:"http://coenraets.org"
}
];
//HERE I WAS EXPERIMENTING WITH A DIFFERENT SYNTAX TO DO THE FILTERING BY ID
//IN MY SERVICE AND SIMPLY RETURNING THE FINAL DATA I WANT TO MY VIEW CLASS
// var employees = new EmployeeCollection({id: id});
// employees.fetch({
// success: function (data) {
// console.log(data);
// }
// });
module.exports = {
collection: new EmployeeCollection(employee_list)
};
Backbone is meant for RESTful services.
I'll try to explain the basics using some easy to understand terms.
So backbone is based on models and views.
The model is responsible to the data.
That means, that the model is the one who fetches the data from the server and stores it.
In an interactive application, the model should have a url or urlRoot properties which indicate what is the url of the specific resource this model refers to.
For example, if we had a Person resource, and assuming we are consuming a standard RESTfult service, I would expect something similiar to this:
var Person = Backbone.Model.extend({
url : 'http://localhost:3000/api/Person'
});
That actually lets us create new instances of this model and manipulate it.
This url will be used by the model for all CRUD operations related to it.
For example, if we now create a new instance:
var person = new Person();
We now have the following basic CRUD operations:
fetch: this method is executing an async AJAX GET request behind the scenes, and injects the data into the model.
Now, after we fetched the data, we can use it by simply calling get:
person.get('name'); * assuming there's a name property.
save this method is exectuing an async AJAX POST or PUT request behind the scene.
If the model's idAttribute is undefined, it will executed POST, otherwise PUT. The idAttribute is a model property which indicates what is the model's unique id.
A sample usage:
person.set({name : 'Mor'});
person.save();
The abvoe will execute a post request with the name: 'Mor' in the request body.
If for example I fetched the model, and already have an idAttribute assigned, calling the same save method will use the PUT request.
destroy this method will execute a DELETE request behind the scene.
Sample usage: person.destroy();.
Obviously I have just shown you the basic usages, there's a lot more options out there.
A collection is simply a list of models so there's not much to explain, you can read more here: http://backbonejs.org/#Collection
A view is all you see. It is the visual part of the application.
What Backbone lets us do, is to bind views to models and collections.
By that, we can create some dynamic content and visuals.
A basic view would like something like that:
var PersonView = Backbone.View.extend({
el: '.person',
initialize: function(){
this.listenTo(this.model, "change", this.render);
},
render: function(){
this.$el.html("hello :"+this.model.get("name"));
}
});
As you can see, I used listenTo. It is an event listener that calls render each time the model changes.
When I refer to this.model I refer to a model I will pass to the view when I initiate it:
var view = new View({ model : person});
By that, and since I used listenTo, my view is now binded with the person model.
This is basically it.
Obviously, there's a lot more to learn and understand, but this pretty much covers the basics.
Please refer to http://backbonejs.org/ and read some more information.

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: How to update a model that isNew without fetching entire collection?

I have a form that create a model for a collection. That fires an add event to the collection. I have that binded to a method:
this.collection.bind('add', this.addOne, this)
fires...
addOne: function(tenant) {
var self = this
var collection = this.collection
var view = new TenantView({model: tenant,collection:collection});
self.$el.append(view.render().el);
}
The create syncs it to the database but, the new appended view still isNew to backbone since it hasn't fetched the collection and grabbed the id for the new model.
My question, how can I grab the synced model from the server (that has the id and isn't isNew) without fetching the entire collection then append it?
Use the sync event instead of add...
this.collection.bind('sync', this.addOne, this);
The add event gets fired immediately when calling create; but sync gets fired once the server has responded to the create method, so it should include the correct model id.
You can also wait for the server's response, before adding the model to the collection -- to do that use wait: true in create's options hash: collection.create({ ... }, { wait: true }).
Creating a model will cause an immediate "add" event to be triggered on the collection, as well as a "sync" event, once the model has been successfully created on the server. Pass {wait: true} if you'd like to wait for the server before adding the new model to the collection.

Backbone: Adding a model to a collection from a collection view?

I have some code where I want a NoteCollectionView to add a new Note to the NoteCollection. This is triggered by a function newNote in the NoteCollectionView:
newNote: function(data) {
var note = new Note(data);
this.collection.add(note);
},
I'm still very new to backbone, and I want to make sure this syncs with the server. The concerns I have are:
1) Will simply adding this note to the collection trigger a save() from the server, and update the model with the ID that the server gives it? Or,
2) If the server does not update my model and give me an actual ID, how do I save the model with note.save() and get back an ID from the server?
To address your first question, no, .add will not trigger any kind of call to the server; it will only add a model to a collection.
However, you do have a couple options. One would be to create the new note model, save it to the database, and then add it to the collection:
newNote: function(data) {
var note = new Note(data);
note.save();
this.collection.add(note);
}
The second option is to simply use Backbone's collection.create method. Give it a hash of attributes and it will
Create the model
Save it to the database
Add it to the collection
All in one fell swoop, like so:
newNote: function(data) {
return this.collection.create(data);
}
collection.create also returns the newly created model, illustrated by my return statement above.

Categories