Backbone: Batch save models in single request? - javascript

I have a single page application using backbone to manage a collection of models.
Sometimes this collection gets quite large and if the user performs operations that change a lot of them this can result in .save getting called lots of times. In some cases resulting in hundreds of ajax requests firing simultaneously.
Does backbone provide any way to batch operations like these into a single request? Or is there a preferred pattern to use?
Thanks for any advice.

There is no built-in way to batch operations for a Backbone.Collection. A common pattern to use is to wrap the collection in a Backbone.Model and simply overwrite the toJSON method. You can then treat this like any other Backbone.Model and simply call save().
var Post = Backbone.Model.extend({
...
});
var Posts = Backbone.Collection.extend({
model: Post,
...
});
var PostsList = Backbone.Model.extend({
url: '/path/for/bulk/operations',
toJSON: function() {
// the model in this case is the Posts collection
return this.model.toJSON();
}
});
Another option is to simply add a save() method to your collection and delegate to Backbone.Sync
var Posts = Backbone.Collection.extend({
...
save: function( options ) {
return Backbone.sync( 'create', this, options );
}
});

Related

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.js removing the model

I have been trying for hours now, without luck. If you could see on the image, I have mildly complex model. Image is taken from Chrome in debug.
I need to delete a model from collection, also I need to be able to change the URL where the backbone will shoot its ajax for delete. So in essence, this is my model structure:
attributes:
favorites {
bookmarkedArticles: [{id: 123123},{id: ...}],
bookedmarkedSearches: [{}],
dispatchesMailds: []
}
How can I delete model in bookmarkedArticles with id of 123123?
I have tried this:
var model = new metaModel(
{
favourites: {
bookmarkedArticles: {
id: "123123"
}
}
}
);
model.destroy();
also this
aamodel.headerData.collection.remove(model);
No success at all.
The information provided is not giving a lot of details, but I will try to answer considering two scenarios:
Option A:
You are trying to delete a model in the collection that has bookmarkedArticle.id="123123". if that is the case and considering the bookmarkedArticles it is just an Array of objects, I would suggest to filter the Collection using the underscore method filter and then delete the models returned by the filter.
var id = 123123;
var modelsToDelete = aamodel.headerData.collection.filter(function(model){
// find in the bookmarked articles
return _.find(model.get('bookmarkedArticles'), function(ba){
return (ba.id === id);
});
});
_.each(modelsToDelete, function(model){
model.destroy();
});
Option 2: If you want to remove the bookmarked article '123123' associated to your main model using just the 'destroy' method, firstable you have to convert 'bookmarkedArticles' to a Backbone.Collection as it is just an Array of Objects, there are some utilities for Backbone that allows you to do this easily:
https://github.com/blittle/backbone-nested-models
But by default this is not possible, then, If you want to remove the 'bookmarkedArticle' you can create the Backbone.Model and then use the method destroy. Example:
var BookmarkedArticle = Backbone.Model.extend({
url: function(){
return '/bookmarkArticle/' + this.id;
}
});
new BookmarkedArticle({"id": "123123","master": "5",...}).destroy();
Hope this information is useful and provide some guidance to solve your problem.

How to update the whole Backbone.js collection that in database?

I have a collection (an object list) in database. I can fetch it like: collectionModel.fetch()
But then user changes something on that collection. When user clickes on save button, the whole collection list must be update in database. I thought maybe i can delete() the old one first and then create() it with new one but i could'n achive it. I can't use the update() method because in this case i should find which collection elements has changed but i want to update whole list. How can i do that? Thanks for help.
Do you have a REST api in front of that database? That's how Backbone is made to work with. When your JavaScript code runs model.save(); a PUT request is made to your api for that model.
You question is about saving the whole collection, for that if you want to remain within the default implementation of Backbone you will have to go over all the models in the collection and call save for each of them.
If you want to make one single request to your server you will have to implement a custom method inside your collection. Something like:
MyCollection = Backbone.Collection.extend({
saveAll: function() {
var data = this.toJSON();
return Backbone.$.ajax({
data: { objects: data },
url: '/url/in/your/server/to/update/db'
});
}
});
That's going to send the array of all models in your collection converted to JSON to your server.
Again, you want to have a RESTful API on the server side if you want to make your life with Backbone easy.
If you want to reset collection you have to specify "reset" attribute.
collectionList.fetch({
reset: true,
...
});
But I think it's better to just update it:
collectionList.fetch({
remove: false,
update: true,
merge: true,
...
});
This is a very old question, but I had another approach so I thought I'd post it.
Sometimes my collections have a lot of data and the server doesn't get it all. I solved this by using one of the underscore methods that backbone collections have, invoke (also relies on jquery):
MyCollection = Backbone.Collection.extend({
update: function(callback) {
// Invoke the update method on all models
$.when.apply($, this.invoke('update')).then(() => {
// After complete call the callback method (if passsed)
if(callback) {
callback();
}
});
}
});
You can use it by calling collection.update() when the collection has models in it. A similar method can be used for creating or deleting collections, and this should be modifiable to catch errors but I didn't account for that.

Backbone one-off ajax request

I have a simple List-style Backbone app that I'm making with a Rails backend.
I have a collection:
var ItemList = Backbone.Collection.extend({
model: Item,
initialize: function(id) {
this.id = id;
},
url: function(){
return '/lists/' + this.id + '/items';
},
});
All the standard CRUD operations work fine from the model. But I have an "extra" route - "clear" that will clear all the items in a list at one show. The route would be:
/lists/[:id]/clear
Because this is outside the normal CRUD operations, is there way to hook it into the normal Collection, or do i do something separate?
You can make a method on your collection called destroy and inside there you can take one of several approaches to making the AJAX request (in order of harmony with Backbone). Note you probably don't want to call your collection method clear because Backbone Models already have a clear method with different semantics.
create a throw-away Backbone.Model instance with the correct URL and ID and then call 'destroy' on it
Call Backbone.sync with method "delete" and a throw-away model object with just an 'url' property and empty 'toJSON' function with the right ID
Make a direct jQuery $.ajax call.
You could add your own method that executes /lists/:id/clear and then does a reset on the collection when it is done:
clear: function() {
var _this = this;
$.ajax({
url: '/lists/' + this.id + '/clear',
//...
success: function() {
_this.reset();
}
});
}
When you call reset without any arguments, it removes all the models from the collection.

How should I create a backbone collection scoped to another model?

I have a post which has many comments, and I want to display the comments on the post page. This is a Rails 3 application and I have my comments' URLs nested in the post's URL, so it basically looks like /post/1/comments.
The problem is, I'm not really sure how should I create a backbone collection for this situation. Should I just pass the post_id to the javascript form the server and then do something like
var Comments = Backbone.Collection.extend({
model: Comment,
url: '/post/' + idFromSomewhereElse + '/comments'
});
or is there a better way to handle this? How should I handle this in case of multiple nesting, where I could have something like /forums/1/topics/3/replies.
One idea could be to delegate the root of the Comments.url to the Post parent model:
// code simplified and not tested
App.Post = Backbone.Model.extend({
urlRoot: "/posts"
});
App.Comments = Backbone.Collection.extend({
model: Comment,
urlRoot: function(){
return this.post.url + "/comments";
},
initialize: function( opts ){
this.post = opts.post;
}
});
I think this idea can scale also for more complicate relations like /forums/1/topics/3/replies. You just have to take care of mantain the relation from every Collection to its parent.

Categories