Automatically save after adding model to collection - backbone.js

I have a collection myCollection to which I add models as follows:
myCollection.add({title: Romeo and Juliette, author: Shakespear});
Have can I now save this added model to the server? Backbone Collections do not have a save() and I do not a reference to the added model to call save directly.

You can use the create function on the collection to add a model and have it automatically saved to the server.
myCollection.create({title: Romeo and Juliette, author: Shakespeare});
Here's the documentation on the create function.

You could bind the save method of your collection to the add event:
MyCollection = Backbone.Collection.extend({
initialize: function(){
this.bind('add', this.save, this)
}
save: function(){
$.post(this.url, this.toJSON())
}
})

Related

Backbone.js How to make collection only accept one class of model

I am new to backbone.js and I am trying to learn it. In the code below I want my collection called "JokesCollection" to only accept adding models of the class "Joke". How do I do achieve this? When setting "Collection" attribute "model" to a certain model, isn´t the collection supposed to only accept that model class and ensure homogeneity? Don´t seam so. When I assign attribute "model" in the "JokesCollection" class to "Joke" it still accepts adding models of class "Persson" witch is not what I want. I only want it to accept adding models of class "Joke".
Joke = Backbone.Model.extend ({
initialize: function(){
console.log("Joke was created");
},
defaults: {
joke : "",
date : "0",
}
});
JokesCollection = Backbone.Collection.extend({
initialize: function(){
console.log("JokesCollection was created");
},
model: Joke // <=== Isn´t this supposed to ensure that the collection only accepts models of class "Joke"?
});
Person = Backbone.Model.extend ({
initialize: function(){
console.log("Person was created");
},
defaults: {
username: "default",
password: "default",
email: "default"
}
});
var person1 = new Person({username:"masterMind"});
var joke1 = new Joke({joke:"Girls are cute and funny hahahaha"});
jokesCollection = new JokesCollection();
jokesCollection.add(joke1);
jokesCollection.add(person1); // This adds a model of class "Person" to the collection. Witch is not what I want. It is not supposed to work! I want "jokesCollection" to only accept models of class "Joke".
console.log(jokesCollection.length); // length gets increased by 1 after adding "person1" to "jokesCollection". Again, it is no supposed to work from my point of view. I want "jokesCollection" to only accept models of class "Joke".
console.log(jokesCollection);
From official docs:
model collection.model
Override this property to specify the model class that the collection
contains. If defined, you can pass raw attributes objects (and arrays)
to add, create, and reset, and the attributes will be converted into a
model of the proper type.
Looks like will have to re-write add method something like this :
add: function(models, options) {
var modelClass = this.model;
isProperIns = this.models.every.(function(model){
return model instanceof modelClass;
});
if (!isProperIns) {
throw new Error("Some of models has unacceptable type")
}
return this.set(models, _.extend({merge: false}, options, addOptions));
}
The purpose of a Collection's model property is not to limit which models the Collection can accept. Rather, that property defines the Model class which the Collection will use when it needs to create a new Model. For instance,when you pass an object literal of Model attributes (as opposed to an instantiated Model) to JokesCollection.add, or when you fetch models in to a JokesCollection, Backbone will use Joke as the Model to instantiate those new additions to the Collection.
There are two ways to ensure your JokesCollection is only populated with instances of Joke. The first way is to never add Model instances to the JokesCollection directly, and instead either:
A) Bring new Jokes in from the server by calling fetch on a JokesCollection
B) add only "raw" Model attributes to the JokesCollection; don't add instantiated Models
However, if you're concerned about a developer accidentally adding a non-Joke Model to the Collection, your other option (as first suggested by #Evgeniy) is to overwrite your JokesCollection's add method. Unlike #Evgeniy's answer though I would not recommend re-writing Backbone's internals. Instead, I would use a simple overwrite that just calls the base Backbone method if possible:
add: function(models, options) {
if (models instanceof Joke) {
// Use the normal Backbone.Collection add method
return Backbone.Collection.prototype.add.call(this, models, options);
}
var allModelsAreJokes = _(models).all(function(model) {
return model instanceof Joke;
));
if (allModelsAreJokes) {
// Use the normal Backbone.Collection add method
return Backbone.Collection.prototype.add.call(this, models, options);
}
// Handle the case where non-Jokes are passed in; either:
// A) convert whatever was passed in to be a Joke:
// var rawModels = _(models).isArray() ? _(models).invoke('toJSON') : model.toJSON();
// return Backbone.Collection.prototype.add.call(this, rawModels, options);
// B) just don't add anything
}

Backbone JS Models and Collection URLs

If I have a model named "Book" and a collection named "Library" defined as below:
Book
app.Book = Backbone.Model.extend({
defaults: {
title: 'No title',
author: 'Unknown'
}
});
Library
app.Library = Backbone.Collection.extend({
model: app.Book,
url: '/api/books'
});
When I call BookInstance.save() how does it come up with the URL to use? Does it derive it from the collection?
In Backbone.model there are 2 options: url and urlRoot. What is the purpose and difference between these?
In Backbone.collection, there is a single parameter url. Is this always the GET request for the RESTFUL api?
Basically, there are 3 possibilities to construct a model's url:
If the model object exists in a collection then its url method will return an address composed of the collection.url and model.id: [collection.url]/[id].
If you don't want to use a model inside the collection, then model.urlRoot's value can be used instead of the collection.url fragment, resulting in the following pattern: [urlRoot]/[id].
Finally, if you're NOT planning to persist more that one model of a given type to the server or will be defining URLs for each model upon their creation, you can directly assign a value to model.url.
Collections send only GET requests — to get an array of models' JSON data. For saving, removing, and updating, the individual model's save() (POST/PUT/PATCH) and destroy() (DELETE) methods are used.
Here's the source code of Backbone.Model.url, which should help you:
url: function() {
var base =
_.result(this, 'urlRoot') ||
_.result(this.collection, 'url') ||
urlError();
if (this.isNew()) return base;
var id = this.get(this.idAttribute);
return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
}
In model
urlRoot is used for the Model.
url is used for the instance of the Model.
So if urlRoot exists
then book.fetch() will fetch the data given id, for example
var Book = Backbone.Model.extend({urlRoot: 'books' });
var book = new Book({id: 1});
book.fetch(); // will get /books/1
var Book = Backbone.Model.extend({});
var book = new Book({url: 'books/1'});
book.fetch(); // will get /books/1
var Books = Backbone.Collection.extend({model: Book});
var books = new Books({ /*....*/ });
books.fetch(); // will get /books/
You can refer the backbone Model urlRoot source code here
I hope it makes sense to you, good luck.

Backbone.js - Build view with sub models

I have an edit view for a Backbone Model that I create each time the the element is clicked on. The problem I have is that the edit view needs two Backbone collections to create the edit form (it contains two <select> lists).
The view:
MyApp.elementView = Backbone.View.extend({
events: {
'click .edit': 'editForm',
},
editForm: function(ev) {
var editView = new TimeTrack.Views.EditJob({
model: this.model
// This view needs two more collections
// for the <select> elements
});
...
}
});
Instantiate the view:
var elementView = new MyApp.elementView({
collection: elementCollection
});
What is the best way to push the needed collections to the edit view? Do I have to pass the collections need for the edit view from the elementView form the instantiation? Or is there a better way of doing this?
I did so, passed in view 2 collections, 1 - the main and the other as follows:
to elementView - second collection and in elementView recive her.
example:
in router I'm
initialize: ->
(YourNameSpace).secondCollection = new (YourNameSpace).secondCollection
elements: =>
view = new (YourNameSpace).elementView( secondCollection: #secondCollection )
$('.l-yield').html(view.render().el)

Backbone.js error - Uncaught TypeError: Object [object Object] has no method 'set'

My Code:
I am new to Backbone.js and trying to build an app with Backbone.js and PHP. When I am trying to call add in the router, I am getting error:
Uncaught TypeError: Object [object Object] has no method 'set'.
Please help me to find my mistake.
Thanks.
// Models
window.Users = Backbone.Model.extend({
urlRoot:"./bb-api/users",
defaults:{
"id":null,
"name":"",
"email":"",
"designation":""
}
});
window.UsersCollection = Backbone.Collection.extend({
model:Users,
url:"./bb-api/users"
});
// Views
window.AddUserView = Backbone.View.extend({
template:_.template($('#new-user-tpl').html()),
initialize:function(){
this.model.bind("click", this.render, this);
},
render:function(){
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
events:{
"click .add":"saveUser"
},
saveUser:function(){ alert('saveUser');
this.model.set({
name:$("#name").val(),
email:$("#email").val(),
designation:$("#designation").val()
});
if(this.model.isNew()){
this.model.create(this.model);
}
return false;
}
});
// Router
var AppRouter = Backbone.Router.extend({
routes:{
"":"welcome",
"users":"list",
"users/:id":"userDetails",
"add":"addUser"
},
addUser:function(){
this.addUserModel = new UsersCollection();
this.addUserView = new AddUserView({model:this.addUserModel});
$('#content').html(this.addUserView.render().el);
}
});
var app = new AppRouter();
Backbone.history.start();
As suggested in the comments, the problem starts here here:
this.addUserModel = new UsersCollection();
this.addUserView = new AddUserView({model:this.addUserModel});
and finishes here:
saveUser:function(){ alert('saveUser');
this.model.set({
By passing a collection in place of a model you create confusion, and as a result later in the saveUser function you try to call a Backbone.Model method (set) on a Backbone.Collection instance.
Note: As of version 1.0.0 Backbone.Collection now has a set method. In previous versions, such as the one used by the question's author, that method was instead called update.
There are several steps you can take to clarify this code. For starters, I would rename your model and collection classes so that it's clear that the model is the singular form and the collection is the plural form:
window.Users => window.User
window.UsersCollection => window.Users
Next, I would create a new User model, instead of a Users collection, and pass that to your view:
this.addUserModel = new User();
this.addUserView = new AddUserView({model:this.addUserModel});
Finally, I'd remove these lines:
if(this.model.isNew()){
this.model.create(this.model);
}
For one thing, the model will always be new (as you just created it before passing it in), but more importantly you don't need to call the Collection's create method because that method creates a new model, when you already have one created. Perhaps what you should add instead is :
this.model.save();
if your intent is to save the model to your server.
Since you already specified a urlRoot for the model, that should be all you need to create a new model, pass it to your view, have your view fill in its attributes based on DOM elements, and finally save that model's attributes to your server.
I think you are facing problem with object scope. When event fired it send to event object to that function. Just try this it may work
Declare global variable with the current view inside the initialize
initialize : function(){ self = this; }
then change this to self,
saveUser:function(){ alert('saveUser');
self.model.set({
name:$("#name").val(),
email:$("#email").val(),
designation:$("#designation").val()
});
if(self.model.isNew()){
self.model.create(this.model);
}
return false;
}

Calling invoke on Backbone Collection

I've a Backbone Collection initialized but calling invoke on collection doesn't work. For some reason I'm getting JS error:
var vw = new SomeView(); // A view with function 'refresh'
var col = new Backbone.Collection();
col.add(vw);
...
setTimeout(function(){ col.invoke('refresh'); }, 1000); // Error: Cannot call method 'apply' of undefined
However, invoking method like isEmpty works fine
console.log("Is empty? ", col.isEmpty()); // prints: 'Is Empty? false'
It seems I'm missing something very obvious.
N.B: I'm not interested in calling each function and then invoking refresh on view object because that's just clunky.
A collection in backbone is a list of models. So when you add a view to a collection internally it will call something like this col.add(Backbone.Model.extend(vw)). So it will create a new model with your view as constructor params. I f you wanna store your view in a list just use a JavaScript array or a smarter underscore collection
you try to create a collection by instantiating the collection itself,
you first have to extend from it and tell it what models it's holding
and of what i see above you are trying to put your views in a collection?
that is not possible directly, as a collection holds a list of models, not views.
you can however create a model defining your view.
var myView = Backbone.View.extend({});
var myModel = Backbone.Model.extend({});
var myCollection = Backbone.Collection.extend({ model: myModel });
$(function(){
// creating your view
var vw = new SomeView();
// creating a model for the view
var viewModel = new myModel({ linkedview : vw });
// creating a collection
var modelList = new myCollection();
modelList.add(viewModel);
});
the gist of it is, that you create a model, containing a reference to your view, and not add the view directly into the collection (which will not work)

Resources