Model rendering inside view: toJSON is not a function - javascript

I have added a model attribut inside a view like so:
app.ActorView = Backbone.View.extend({
modelImg:'', ....
I'm skipping to the rendering part as everything else is ok:
render: function () {this.$el.html(this.template({
modImg: this.modelImg.toJSON(),
mod: this.model.toJSON(),
movies: this.collection.toJSON()}
Every model in the view (model, collection and modelimg) is correctly fetched in the rooter part of my project:
modActor.fetch().done(function () {
modActorMovies.fetch().done(function () {
modImgActor.fetch().done(function () {
var actorView = new app.ActorView({
modelImg: modImgActor,<--problematic model
model: modActor,
collection: modActorMovies
});
My modImgActor definition is the following:
app.ActorImg= Backbone.Model.extend({
url: "http://umovie.herokuapp.com/unsecure/actors/272994458/movies",
parse: function (response) {
return response.results[0];
}
});
The problem is when I use the toJson() function on the modelImg. There is the following error: this.modelImg.toJSON is not a function
Can it be how the model is defined with its url?

modImg is not a standard option for Backbone.View. So backbone will just ignore it.
You have to manually handle the custom properties that you pass along with the options.
So you're view definition should be
app.ActorView = Backbone.View.extend({
initialize: function(options){
this.modelImg = options.modelImg;
}
}):

Related

The sort-button does not work in Backbone.js

I have a JSON file, that I need to parse it into collection and render it to HTML pageand then I need to add a button, that will sort this collection and redraw it on page.
That the code, that I made:
That's the part about model, collection and sorting:
var Profile = Backbone.Model.extend();
var ProfileList = Backbone.Collection.extend({
model: Profile,
url: 'profiles.json',
selectedStrategy: "count",
comparator: function (property){
return selectedStrategy.apply(model.get(property));
},
strategies: {
count: function (model) {return model.get("count");},
name: function (model) {return model.get("name");}
},
changeSort: function (sortProperty) {
this.comparator = this.strategies[sortProperty];
},
initialize: function () {
this.changeSort("count");
},
});
It's the View and the Button:
var ProfileView = Backbone.View.extend({
el: "body",
template: _.template($('#profileTemplate').html()),
Sort: null,
initialize: function() {
this.Sort = new ReSortView();
this.bind('all', this.render());
},
render: function() {
_.each(this.model.models, function(profile){
var profileTemplate = this.template(profile.toJSON());
$(this.el).append(profileTemplate);
}, this);
return this;
},
ReSort: function (){
console.log("111");
this.model.changeSort("name");
},
events: {
"click .Sort": "ReSort",
//"click.NSort": "NSort"
},
});
var ReSortView = Backbone.View.extend({
el: $("#Sort")
});
var AppView = Backbone.View.extend({
el: "body",
initialize: function() {
var profiles = new ProfileList();
var profilesView = new ProfileView({
model: profiles
});
profiles.bind('all', function () {
profilesView.render();
});
profiles.fetch({success: function (model,resp) { console.log(resp);}});
}
});
var App = new AppView();
});
The question is why when I run it, everything seems to be ok, but the sorting does't work, and FireBug saying nothing and Button just writing into the consol.
P.S. I'm new in WEB developing and exactly in JS\Backbone.js
Just changing the comparator:
changeSort: function (sortProperty) {
this.comparator = this.strategies[sortProperty];
}
won't re-sort the collection, the collection has no way of know that the comparator has changed unless you tell it. You need to force the issue by calling sort:
changeSort: function (sortProperty) {
this.comparator = this.strategies[sortProperty];
this.sort();
}
And a few other things while I'm here:
Your initial comparator:
comparator: function (property){
return selectedStrategy.apply(model.get(property));
}
is invalid (unless you have a global selectedStrategy defined somewhere), you should probably just leave it out and let initialize set it up by calling changeSort.
this.bind('all', this.render()); does nothing useful, bind wants a function as the second argument but this.render() calls the render method. You probably don't want a this.bind call there at all and if you do, you'd want to say this.bind('all', this.render).
Views handle the collection option similarly to how the handle the model option in their constructor:
There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, tagName and attributes.
so, if your view is collection-based, you'd want to say new View({ collection: ... }) and use this.collection instead of this.model to avoid confusion.
Collections have various Underscore functions built-in so don't say:
_.each(this.model.models, ...
when you can say this instead:
this.collection.each(...
View's have a jQuery wrapped version of el built in so you can use this.$el instead of $(this.el) (which rebuilds the jQuery wrapper each time you call it).
You are calling the changeSort method on the model but that method is on your collection (as it should be)

Backbone js not populating a model with data using fetch()

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.

backbone.js, handlebars error : this._input.match is not a function

I'm new to backbone.js and handlebars and I'm having a problem getting my template to render out the data.
Here is my collection and model data from tagfeed.js module:
// Create a new module.
var Tagfeed = app.module();
// Default model.
Tagfeed.Model = Backbone.Model.extend({
defaults : {
name : '',
image : ''
}
});
// Default collection.
Tagfeed.Collection = Backbone.Collection.extend({
model : Tagfeed.Model,
url : Api_get('api/call')
});
Tagfeed.TagView = Backbone.LayoutView.extend({
template: "tagfeed/feed",
initialize: function() {
this.model.bind("change", this.render, this);
},
render: function(template, context) {
return Handlebars.compile(template)(context);
}
});
Then in my router I have:
define([
// Application.
"app",
// Attach some modules
"modules/tagfeed"
],
function(app, Tagfeed) {
// Defining the application router, you can attach sub routers here.
var Router = Backbone.Router.extend({
routes: {
"index.html": "index"
},
index: function() {
var collection = new Tagfeed.Collection();
app.useLayout('main', {
views: {
".feed": new Tagfeed.TagView({
collection: collection,
model: Tagfeed.Model,
render: function(template, context) {
return Handlebars.compile(template)(context);
}
})
}
});
}
});
return Router;
});
THis successfully makes a call to the api, makes a call to get my main template, and makes the call to get the feed template HTML. If I don't include that render(template, context) function, then it renders on the page as the straight up HTML that I have in the feed template with the {{ name }} still included. however when its included, I get the error
TypeError: this._input.match is not a function
[Break On This Error]
match = this._input.match(this.rules[rules[i]]);
and if I examine the variables that get passed into the appLayout views render function for feed, I see that the template var is a function, and the context var is undefined, then it throws that error.
Any ideas what I'm doing wrong? I know I have at least one problem here, probably more.
Since you're using requirejs, you can use the text module to externalise your templates or better still pre-compile them and include them in your view. Check out http://berzniz.com/post/24743062344/handling-handlebars-js-like-a-pro
E.g. using pre-compiled templates
// router.js
define(['views/tag_feed', 'templates/feed'], function(TagFeedView) {
var AppRouter = Backbone.Router.extend({
// ...
});
})
// tag_feed.js
define(['collections/tag_feed'], function() {
return Backbone.View.extend({
// ...
render: function() {
this.$el.html(
Handlebars.templates.feed({
name: '...'
})
);
}
});
})
For reference I've created simple boilerplate for a backbone/require/handlebars setup https://github.com/nec286/backbone-requirejs-handlebars

this.collection.each does not fire this way (backbone.js)

I am using this.collection.each() to iterate through the collection fetched from the backend.
Problem: I notice that when I bind the reset event of the collection to the render method of the view in the initialize method and place a console.log() within this.collection.each, I see the console output as expected.
However, If I dont do the binding above, and simply use this.render() within initialize, the console.log() does not output anything. This seems really strange to me, can anyone provide an explaination?
I also placed a console.log(this.collection); just before the loop, and this always outputs the collection correctly! I was guessing that the collection has not been populated on initialization of the View, but that will cause console.log(this.collection); to not show anything.
This Works
SimilarPhotoListView = Backbone.View.extend({
el: '#modal_similar_items',
initialize: function() {
this.collection.on('reset', this.render, this);
},
render: function() {
console.log(this.collection);
this.collection.each(function(photo, index) {
console.log('hello');
}, this);
return this;
}
});
This does not output from within this.collection.each()
SimilarPhotoListView = Backbone.View.extend({
el: '#modal_similar_items',
initialize: function() {
this.render();
},
render: function() {
console.log(this.collection);
this.collection.each(function(photo, index) {
console.log('hello');
}, this);
return this;
}
});
Both classes are instantiated via:
renderSimilarPosts: function() {
this.similarPhotoList = new SimilarPhotoCollection();
this.similarPhotoListView = new SimilarPhotoListView({ collection: this.similarPhotoList });
this.similarPhotoList.fetch({
data: {post_id: this.model.id},
processData: true
});
}
When you initialize your view, this.similarPhotoList is an empty collection. Therefore, when you create your similarPhotoListView, you're passing it an empty collection. similarPhotoListView.initialize calls render thus with an empty collection, all before the collection is populated by fetch.
The reason the first method works is because reset is triggered in collection.fetch. From the backbone source:
fetch:
...
options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp);
};
...
initialize runs on instantiation, therefore you run render before you even pass in your collection. Additionally, render should not be called directly from initialize

Scope error using Backbone.js?

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) {
// ...

Categories

Resources