Backbone JS Models and Collection URLs - backbone.js

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.

Related

backbone.js set in model initialize not effecting models in collection

While performing a fetch() on my backbone collection, and instantiating models as children of that collection, I want to add one more piece of information to each model.
I thought that I could do this using set in the model initialize. (My assumption is that fetch() is instantiating a new model for each object passed into it. And therefore as each initialize occurs the extra piece of data would be set.
To illustrate my problem I've pasted in four snippets, first from my collection class. Second the initialize function in my model class. Third, two functions that I use in the initialize function to get the needed information from the flickr api. Fourth, and finally, the app.js which performs the fetch().
First the collection class:
var ArmorApp = ArmorApp || {};
ArmorApp.ArmorCollection = Backbone.Collection.extend({
model: ArmorApp.singleArmor,
url: "https://spreadsheets.google.com/feeds/list/1SjHIBLTFb1XrlrpHxZ4SLE9lEJf4NyDVnKnbVejlL4w/1/public/values?alt=json",
//comparator: "Century",
parse: function(data){
var armorarray = [];
var entryarray = data.feed.entry;
for (var x in entryarray){
armorarray.push({"id": entryarray[x].gsx$id.$t,
"Filename": entryarray[x].gsx$filename.$t,
"Century": entryarray[x].gsx$century.$t,
"Date": entryarray[x].gsx$date.$t,
"Country": entryarray[x].gsx$country.$t,
"City": entryarray[x].gsx$city.$t,
"Type": entryarray[x].gsx$type.$t,
"Maker": entryarray[x].gsx$maker.$t,
"Recepient": entryarray[x].gsx$recipient.$t,
"Flickrid": entryarray[x].gsx$flickrid.$t,
"FlickrUrl": "", //entryarray[x].gsx$flickrurl.$t,
"FlickrUrlBig": ""//entryarray[x].gsx$flickrurlbig.$t,
});
}
return armorarray;
}
});
Second, the initialization in my model.
initialize: function(){
//console.log("A model instance named " + this.get("Filename"));
item = this;
var flickrapi = "https://api.flickr.com/services/rest/?&method=flickr.photos.getSizes&api_key=<my_apikey>&photo_id=" + this.get("Flickrid") + "&format=json&jsoncallback=?";
sources = getFlickrSources(flickrapi);
sources.then(function(data){
sourceArray = parseFlickrResponse(data);
FlickrSmall = sourceArray[0].FlickrSmall;
console.log (FlickrSmall);
item.set("FlickrUrl", FlickrSmall);
console.log(item);
});
Notice here how I'm getting the "Flickrid" and using to get one more piece of information and then trying to add it back into the model with item.set("FlickrUrl", FlickerSmall);
console.log confirms that the property "FlickrUrl" has been set to the desired value.
Third, these are the functions my model uses to get the information it needs for the flicker api.
var getFlickrSources = function(flickrapi){
flickrResponse = $.ajax({
url: flickrapi,
// The name of the callback parameter, as specified by the YQL service
jsonp: "callback",
// Tell jQuery we're expecting JSONP
dataType: "jsonp"})
return flickrResponse;
}
var parseFlickrResponse = function(data){
flickrSourceArray = []
if (data.stat == "ok"){
sizeArray = data.sizes.size;
for (var y in sizeArray){
if (sizeArray[y].label == "Small"){
flickrSourceArray.push({"FlickrSmall": sizeArray[y].source});
}
else if (sizeArray[y].label == "Large"){
flickrSourceArray.push({"FlickrLarge": sizeArray[y].source});
}
}
}
return flickrSourceArray
}
But, fourth, when I try to perform the fetch and render the collection, I only get objects in my collection without the FlickrUrl property set.
//create an array of models and then pass them in collection creation method
var armorGroup = new ArmorApp.ArmorCollection();
armorGroup.fetch().then(function(){
console.log(armorGroup.toJSON());
var armorGroupView = new ArmorApp.allArmorView({collection: armorGroup});
$("#allArmor").html(armorGroupView.render().el);
});
var armorRouter = new ArmorApp.Router();
Backbone.history.start();
The console.log in this last snippet prints out all the objects/models supposedly instantiated through the fetch. But none of them include the extra property that should have been set during the initialization.
Any ideas what is happening?
What is this function ? getFlickrSources(flickrapi)
Why are you using this.get in the initialize function. Honestly it looks over-complicated for what you are trying to do.
If you want to set some parameter when you instantiate your model then do this var model = new Model({ param:"someparam", url:"someurl",wtv:"somewtv"});
If the point is to update your model just write an update function in your model something like update: function (newparam) { this.set;... etc and call it when you need it.
If I read you well you just want to set some params when your model is instantiated, so just use what I specified above. Here is some more doc : http://backbonejs.org/#Model-constructor
I hope it helps.
edit:
Put your call outside your model, you shouldn't (imo) make call inside your model this way it seems kinda dirty.
Sources.then(function(flickrdata) {
var mymodel = new Model({flicker:flickrdata.wtv});
});
It would be cleaner in my opinion.

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
}

How to call fetch method of Backbone Collection passing Id

I want to fire fetch method on Backbone Collection which would pass an Id parameter similar to what happens in Model.fetch(id)
E.g.
var someFoo= new Foo({id: '1234'});// Where Foo is a Backbone Model
someFoo.fetch();
My Backbone collection:-
var tasks = backbone.Collection.extend({
model: taskModel,
url: '/MyController/GetTasks',
initialize: function () {
return this;
}
});
In my View when I try to fetch data:-
var _dummyId = 10; //
// Tried approach 1 && It calls an api without any `id` parameter, so I get 500 (Internal Server Error).
this.collection.fetch(_dummyId);
// Tried approach 2 && which fires API call passing Id, but just after that
// I am getting error as below:- Uncaught TypeError: object is not a function
this.collection.fetch({
data: {
id: _dummyId
}
});
Found it very late : To cut short the above story I want something like Get /collection/id in backbone.
Thank you for your answers, finally I got the solution from Backbone.js collection options.
Apologies that I couldn't explain the question properly while for same requirement others have done brilliantly and smartly.
Solution : I can have something like :-
var Messages = Backbone.Collection.extend({
initialize: function(models, options) {
this.id = options.id;
},
url: function() {
return '/messages/' + this.id;
},
model: Message,
});
var collection = new Messages([], { id: 2 });
collection.fetch();
Thanks to nrabinowitz. Link to the Answer
As mentioned by Matt Ball, the question doesn't make sense: either you call fetch() on a Collection to retrieve all the Models from the Server, or you call fetch() on a Model with an ID to retrieve only this one.
Now, if for some reason you'd need to pass extra parameters to a Collection.fetch() (such as paging information), you could always add a 'data' key in your options object, and it may happen that one of this key be an id (+add option to add this fetched model rather than replace the collection with just one model)... but that would be a very round-about way of fetching a model. The expected way is to create a new Model with the id and fetch it:
this.collection = new taskCollection();
newTask = this.collection.add({id: 15002});
newTask.fetch();
In your code however, I don't see where the ID is coming from, so I am wondering what did you expect to be in the 'ID' parameter that you wanted the collection.fetch() to send?

how to set backbone model idAttribute using nested property

My model is structured like this:
model = {
distance: 12.05,
widget: {
id: 1,
creationDate: '12/01/2012'
}
}
How do I set the idAttribute of the model to be the id on the widget property? Is there a syntax to do this?
How about rearranging/flattening your model to make id a top-level property? Override parse and you won't need to set the idAttribute:
var YourModel = Backbone.Model.extend({
parse: function (response) {
var distance = response.distance;
response = response.widget;
response.distance = distance;
return response;
}
});
Now id will be automatically picked up by Backbone as the id. If you need to persist your data back to your datastore, you'll need to overwrite methods necessary to transform the data back. If possible, it would be a better solution if your model came structured with id already at a top level.

How to initialise nested Backbone.js models

In my Application, I have the following JSON data format:
{
Item: {
property1: '',
...
}
}
Following the solution of this stackoverflow.com answer, I modeled my Backbond.js models the following way:
App.Models.Item = Backbone.Model.extend({
});
App.Models.ItemData = Backbone.Model.extend({
defaults: {
'Item': new App.Models.Item
}
});
I now want to bootstap the data to my App from the Backend system on the page load the following way:
var item = App.Models.ItemData({
{Item:
{property1: 'data'}
}
});
The problem I have now is that item.get('Item') returns a plain JavaScrip object and not a Backbone.Model object, because the defaults are overwritten. How can I create the Backbone.js object while ensuring that item.get('Item') is an App.Models.Item object?
I also have read that if you nest Backbone.Models, you should wirite custom getter methods, so the rest of your app dose not have to know about the internal data structure. If so, what is the right way to implement those setters and getters?
You can override the parse method on your ItemData model. No defaults required. The parse method will initialize an empty model, if one is not passed:
App.Models.ItemData = Backbone.Model.extend({
parse: function(attrs) {
attrs = attrs || {};
if(!(attrs.Item instanceof App.Models.Item))
attrs.Item = new App.Models.Item(attrs.Item);
return attrs;
}
});
And then initialize your ItemData model with the option parse:true:
var item = new App.Models.ItemData({Item:{property1: 'data'}}, {parse:true});

Resources