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.
Related
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
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.
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);
}
});
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 );
}
});
I am using Backbone js to create an app like service. Every application has a user_id and an application_id that has been set. Each time that backbone calls a fetch(), save(), or any other RESTful/ajax function, I want the user_id and the application_id to automatically be passed with the models data.
I know I am going to have to extend the backbone model, and then make sure all my models extend from this model, but what do I modify and how do I call the parent model?
Psuedo Example (not quite sure)
MyModel = Backbone.Model.extend({
save : function(data) {
data.application_id = 3;
data.user_id = 5;
parent.save(data);
}
});
Scores = MyModel.extend({
default : {
score : 0
}
});
//This should automatically grab the application id and user id
scores = new Scores();
scores.set('score', 5);
score.save()
How do I correctly accomplish this? And by accomplish I mean an single point in the code that will work for save(), fetch() and destroy() ?
How about modifying your Backbone sync? If you're sure you want to pass the user_id and application_id with EVERY model on save() fetch() and destroy() then you could do something like this I believe...
/* alias away the sync method */
Backbone._sync = Backbone.sync;
/* new Backbone.sync method */
Backbone.sync = function(method, model, options) {
// For example purpose, this will only run on POST, PUT, and DELETE requests
// If you want you can also set it for method == 'read' for your fetch()
if (method == 'create' || method == 'update' || method == 'delete') {
model.set('user_id', userID);
model.set('application_id', appID);
}
/* proxy the call to the old sync method */
return Backbone._sync(method, model, options);
};
I do something like this for my CSRF token check to prevent cross site registration forgeries. Except instead of manipulating the model, I make sure all my POST, PUT, and DELETE requests have a special X-CSRF header with my unique token.
If your values (appid and userid) are pre-determined, you can do this in the initialize method of your model
MyModel = Backbone.Model.extend({
this.set('application_id', appID);
this.set('user_id', userID);
});
These values are now part of your model and therefore part of every CRUD operation.