I'm creating a collection in backbone js, after this fetch data from the backend, I print in the console inspector in chrome, but something caught my attention in the attributes.
The collection has models, and inside of each model has an attribute called "collections" and inside of this has a attribute call "models" and so on
I'm not interacting yet with the views, only create a collection and fetch de data.
This is what happens in the console:
This is the code that I'm using for the parse:
var TablesCollections = Backbone.Collection.extend({
model: Table,
url: '/api/tables',
parse: function(response) {
if(response.procced == 7) {
return response.data;
} else {
return "An error has trigger";
}
},
initialize: function() {
this.fetch();
}
});
And this is the models:
var Table = Backbone.Model.extend({
defaults: {
'title': '',
'titlestring' : '',
'schema': {},
'manageschema': {},
},
parse: function(response){
if(response.proceed){
if(response.proceed == 4){
response.data.schema = JSON.parse(response.data.schema);
response.data.manageschema = JSON.parse(response.data.manageschema);
response = response.data;
}
} else {
if(response.schema != 'Na'){
response.schema = JSON.parse(response.schema);
}
if(response.manageschema != 'Na'){
response.manageschema = JSON.parse(response.manageschema);
}
}
return response;
},
});
Why is there multiple copies of the collection nested inside the models?
This is normal. Each Backbone Model that is added to a collection has a reference to the collection that it belongs to. (accessed via this.collection where this is the model)
What you see in the console is a circular reference. The collection has models. Each model has a reference to the collection. That same collection has the same models which have the same reference to the collection, etc...
Model's constructor documentation:
The model.collection property is normally created automatically when you first add a model to a collection.
In your screenshot, you can see that cid: "c8". This is the client id which Backbone adds to models and collections. This shows you that it's the same model reference each time.
Related
I have a collection below
var Collection = Backbone.Collection.extend({
model: Model,
url: 'messages',
defaults :{
subject: 'this is subject',
message: 'This is message'
}
});
Now what I want is when the ajax request to message is failed the I want to my app to use defaults data but my current app does not use that.
Below is the way I am using it.
var collection = new Collection();
collection.fetch().then(function () {
new SomeView({ collection: collection });
}, function (status, error) {
new SomeView({ collection: collection });
});
What I am doing in above code is if fetch is successful or failed still I am calling the same view and passing the same collection. Now when it is failed then collection should have defaults contents.
But it does not have them unfortunately.
The main purpose of doing this way is I want my application to work even if there is not server available should be able to work with static data present in default attribute in collection.
Is it possible if yes then how can I make it work?
The defaults are set in the model instead of the collection. What you can do in case the fetch fail is to add a new model to the collection with the default values:
var Model = Backbone.Model.extend({
defaults :{
subject: 'this is subject',
message: 'This is message'
}
});
var Collection = Backbone.Collection.extend({
model: Model,
url: 'FakeUrl/messages'
});
var collection = new Collection();
collection.fetch().then(function () {
new SomeView({ collection: collection });
}, function (status, error) {
//adds new model with default values, same as collection.add(new Model());
collection.add({});
console.log(collection.toJSON());
new SomeView({ collection: collection });
});
Another option is to initially create the collection containing a single model with the default values. Then pass {reset:true} when fetching the data from the server. If fetch succeeds the default model will be removed, but if it fails the collection will still contain it:
var collection2 = new Collection(new Model());
collection2.fetch({reset: true}).then(function () {
new SomeView({ collection: collection });
}, function (status, error) {
console.log(collection2.toJSON());
new SomeView({ collection: collection });
});
You can try both options in this fiddle
I am using Backbone.js and trying to populate my model using fetch(). The problem I am having is that the returned data is not populating my model. I have found a similar question here. The difference is that inside of my success function I am not seeing any data changes nor is a 'change' event being fired.
The code:
Model
window.Company = Backbone.Model.extend({
urlRoot: "/api/company",
defaults:{
"id":null,
"name":"",
"address":"",
"city":"",
"state":"",
"phone":""
},
events: {
'change': 'doChange'
},
doChange: function(event) {
alert('company changed');
}
})
The Router
var AppRouter = Backbone.Router.extend({
routes:{
"":"home",
"company/:id":"companyDetails"
},
initialize:function () {
var user = new User();
this.headerView = new HeaderView({
model: user
});
$('.header').html(this.headerView.el);
console.log("router initialized.");
},
companyDetails: function (id) {
var company = new Company({
id: id
});
company.fetch({
success: function(){
console.log('company.id is ' + company.id);
console.log('company.name is ' + company.name);
console.log('company.address is ' + company.address);
$("#content").html(new CompanyView({
model: company
}).el);
}
});
}
});
JSON
{"address":"555 Main St","name":"Confused Technologies","id":"8dc206cc-1524-4623-a6cd-97c185a76392","state":"CO","city":"Denver","zip":"80206","phone":"5551212"}
The name and address are always undefined. I have to be overlooking something simple???
Edit
Including the view that erroneously left out passing the model to the template.
View
window.CompanyView = Backbone.View.extend({
initialize:function () {
this.render();
console.log('CompanyView initialized');
},
render:function (eventName) {
$(this.el).html(this.template());
return this;
}
})
The attributes are not stored directly on the model. They are stored in an attributes hash, so you would access them through company.attributes, though company.get(attribute) is the way it's usually done. Along the same lines, you would pass company.toJSON() to your template function, as that returns a cloned hash of the model's attributes.
As for your change event not firing, I assume you mean the change: doChange in the model's events hash. Backbone Models do not actually do anything with an events hash. That's for delegating DOM events on Backbone Views. I bet if you put company.on("change", function (model) { console.log(model.toJSON()); }) before your fetch call and removed the success callback, you'd see your model in the console.
Also, I don't think your $("#content").html... line is going to work like you expect. I'd rewrite your router callback like this:
companyDetails: function (id) {
var company = new CompanyView({
el: "#content",
model: new Company({ id: id })
});
// This line would be better in your view's initialize, replacing company with this.
company.listenTo(company.model, "change", company.render);
company.model.fetch();
}
CompanyView#render would typically pass this.model.toJSON() to a template function that returns html, and pass that to this.$el.html(). So something like this.$el.html(this.template(this.model.toJSON()));
OK. The problem with not updating my model was as far as I can tell an async issue. I updated the success callback to include the data parameter like so:
success: function (data) {
$('#content').html(new CompanyView({
model: data
}).el);
}
Note that I am not passing the company object as the model rather the raw returned data. This solved my model problem.
I mentioned in a comment that this started with my underscore template variables `<%= name %>' etc... being empty. I changed my view to this:
window.CompanyView = Backbone.View.extend({
initialize:function () {
this.render();
console.log('CompanyView initialized');
},
render:function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
})
Those to things got both my model updated and variables propagating to the template.
I'm trying to sync my Backbone.js app to the server.
I'll note that I overrided my sync in the collection to use jsonp:
window.Project = Backbone.Model.extend({
initialize:function () {
},
urlRoot:"http://cshosting.webfactional.com/api/v1/projects",
defaults:{
"id":null,
"completion_state":"0",
"last_update_datetime":"0"
}
});
window.ProjectList = Backbone.Collection.extend({
model: Project,
url:"http://cshosting.webfactional.com/api/v1/projects/",
sync: function(method, model, options) {
options.dataType = 'jsonp';
options.url="http://cshosting.webfactional.com/api/v1/projects/? format=jsonp&callback=moshe";
//options.contentType='application/json-p';
options.error=this.errorr;
return Backbone.sync(method, model, options);
},
parse: function(response) {
return response.objects;
}
});
The problem is, while "fetch" is working just fine, when I try to "create" a new model to the collection - I get this network error:
Your application is not using your jsonp configuration in the Collection.create() This is because the Collection delegates in the Model.save() to create elements.
Check the Collection.create() code.
You should override the Project.sync() as well.
I want to map JSON having hierarchical structure onto Model. I can map the data at a top hierarchy onto Model. However, I can't map it onto Model which nested the element which I nested.
JSON
{
"attr1":"data1",
"chi1": {
"attr1":"chi1_data"
},
"list1":[
{"name":"name1"},
{"name":"name2"}
]
}
JavaScript
var Child2 = Backbone.Model.extend({
fun1:function() {
alert("this is Child2");
}
});
var List1 = Backbone.Collection.extend({
url: "list1",
model: Child2,
fun1:function() {
alert("this is List1");
}
});
var Child1 = Backbone.Model.extend({
});
var Root1 = Backbone.Model.extend({
url: "sample.json",
defaults : {
list1 : new List1,
chi1 : new Child1,
}
});
var View1 = Backbone.View.extend({
el: "#friends",
events: {
"click button": "sample"
},
initialize: function() {
this.root1 = new Root1();
},
sample: function() {
this.root1.fetch({
success: function(model) {
// this is success
alert(model.get("attr1"));
// this is error
alert(model.get("list1").fun1());
// this is error too.
model.get("list1").each(function(attr) {
alert(attr.fun1());
});
},
error: function(model, res) {
alert("error: " + res.status);
}
});
},
});
You might want to take a look at this plugin.
http://documentup.com/afeld/backbone-nested/
Might not be exactly what you want, but it could at least point you in the right direction.
The other thing you can do is override the parse method on your model...
parse: function(resp){
// And setup the model using the raw resp
// The resp data is your json from the server and will
// be used to setup the model. So overriding parse, you can
// setup the model exactly they way you want.
return resp;
}
thank you jcreamer.
backbone-nested plugin seems to be different from what I want to do.
I can realize the nest of the model. In using parse function.
// it is able to get "chi1_data"
new Child2(JSON.parse(JSON.stringify(resp["chi1"]))).get("attr1")
// it is able to get "name2"
new Child2(JSON.parse(JSON.stringify(new List1(JSON.parse(JSON.stringify(resp["list1"]))).get(2)))).get("name")
I found Backbone-relational plug in. I will try this
https://github.com/PaulUithol/Backbone-relational
I believe my problem relates to scope somehow, as I'm a js newbie. I have a tiny backbone.js example where all I am trying to do is print out a list of items fetched from the server.
$(function(){
// = Models =
// Video
window.Video = Backbone.Model.extend({
defaults: function() {
return {
title: 'No title',
description: 'No description'
};
},
urlRoot: 'api/v1/video/'
});
// VideoList Collection
// To be extended for Asset Manager and Search later...
window.VideoList = Backbone.Collection.extend({
model: Video,
url: 'api/v1/video/'
});
// = Views =
window.VideoListView = Backbone.View.extend({
tagName: 'ul',
render: function(eventName) {
$(this.el).html("");
_.each(this.model.models, function(video) {
$(this.el).append(new VideoListRowView({model:video}).render().el);
}, this);
return this;
}
});
// VideoRow
window.VideoListRowView = Backbone.View.extend({
tagName: "li",
template: _.template("id: <%= id %>; title: <%= title %>"),
className: "asset-video-row",
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
// Router
var AppRouter = Backbone.Router.extend({
routes:{
"":"assetManager"
},
assetManager:function() {
this.assetList = new VideoList();
this.assetListView = new VideoListView({model:this.assetList});
this.assetList.fetch();
$('#content').html(this.assetListView.render().el);
}
});
var app = new AppRouter();
Backbone.history.start();
// The following works fine:
window.mylist = new VideoList();
window.mylistview = new VideoListView({model:window.mylist});
});
If I access mylist.fetch(); mylist.toJSON() from the console, mylist populates fine. I can tell that this.assetList.fetch() is accurately fetching the data from the backend, but it doesn't appear to be adding the objects to this.assetList.
The fetch method on Backbone collections is asynchronous:
Fetch the default set of models for this collection from the server, resetting the collection when they arrive. [...] Delegates to Backbone.sync under the covers, for custom persistence strategies.
And Backbone.sync says:
Backbone.sync is the function that Backbone calls every time it attempts to read or save a model to the server. By default, it uses (jQuery/Zepto).ajax to make a RESTful JSON request.
So fetch involves an (asynchronous) AJAX call and that means that you're trying to use the collection before fetch has retrieved the data from the server. Note that fetch supports success and error callbacks so you can do this instead:
var self = this;
this.assetList.fetch({
success: function(collection, response) {
$('#content').html(self.assetListView.render().el);
}
});
Or you could bind a callback to the collection's reset event as fetch will reset the collection. Then render your assetListView when the collection's reset event is triggered.
Also, your assetList is a collection so you should be doing:
this.assetListView = new VideoListView({collection: this.assetList});
and:
window.VideoListView = Backbone.View.extend({
tagName: 'ul',
render: function(eventName) {
$(this.el).html("");
_.each(this.collection.models, function(video) {
// ...