Backbone Cannot read property 'property' of undefined error in backbone view - javascript

I've just decided to learn backbone. I'm following a video tutorial. Everything works fine there, but at my end I get this error "Uncaught TypeError: Cannot read property 'name' of undefined".
Here's my code:
var MenuItemDetails = Backbone.View.extend({
render: function() {
var markup = this.options.name + this.options.category + this.options.imagepath;
// I also had some html markup in the string above, of course, but I've striped it because stackoverflow didn't show it in the preview of my post.
this.$el.html(markup);
return this;
}
});
var AppRouter = Backbone.Router.extend({
routes: {
"" : "list",
"menu-items/new" : "itemForm",
"menu-items/:item" : "itemDetails"
},
list: function() {
$('#app').html('List screen');
},
itemDetails: function(item) {
var view = new MenuItemDetails({ name: item, category: 'Some category', imagepath: 'no-image.jpg' });
$('#app').html(view.render().el);
},
itemForm: function() {
$('#app').html('New item form');
}
});
var app = new AppRouter();
$(function() {
Backbone.history.start();
});
The "itemDetails" function gives "Uncaught TypeError: Cannot read property 'name' of undefined" error. Of course, if I don't use the 'name' property in the view, I get "Uncaught TypeError: Cannot read property 'category' of undefined". In the video tutorial that I'm following, everything works fine (it uses version 0.9.1 of backbonejs). I use the latest (1.1.0).
Does anybody know why do I get this error?
There isn't anything misspelled, everything is in the right order (exactly as in the video tutorial, where it works). Why does backbone throws me this error?

Backbone views used to automatically copy the constructor options to this.options but no longer:
Change Log
1.1.0 — Oct. 10, 2013
Backbone Views no longer automatically attach options passed to the constructor as this.options, but you can do it yourself if you prefer.
So if you're depending on this.options being set in your views then you'll have to do it yourself:
var MenuItemDetails = Backbone.View.extend({
initialize: function(options) {
this.options = options;
},
//...
});
Or better, unpack the options so that you know what your view's interface is:
initialize: function(options) {
this.options = _(options).pick('name', 'category', 'imagepath');
}
This way you at least have a list of what you're expecting to see in options.

Related

Not fetching correct url issue

I have a backboneJS app that has a router that looks
var StoreRouter = Backbone.Router.extend({
routes: {
'stores/add/' : 'add',
'stores/edit/:id': 'edit'
},
add: function(){
var addStoresView = new AddStoresView({
el: ".wrapper"
});
},
edit: function(id){
var editStoresView = new EditStoresView({
el: ".wrapper",
model: new Store({ id: id })
});
}
});
var storeRouter = new StoreRouter();
Backbone.history.start({ pushState: true, hashChange: false });
and a model that looks like:
var Store = Backbone.Model.extend({
urlRoot: "/stores/"
});
and then my view looks like:
var EditStoresView = Backbone.View.extend({
...
render: function() {
this.model.fetch({
success : function(model, response, options) {
this.$el.append ( JST['tmpl/' + "edit"] (model.toJSON()) );
}
});
}
I thought that urlRoot when fetched would call /stores/ID_HERE, but right now it doesn't call that, it just calls /stores/, but I'm not sure why and how to fix this?
In devTools, here is the url it's going for:
GET http://localhost/stores/
This might not be the answer since it depends on your real production code.
Normally the code you entered is supposed to work, and I even saw a comment saying that it works in a jsfiddle. A couple of reasons might affect the outcome:
In your code you changed the Backbone.Model.url() function. By default the url function is
url: function() {
var base =
_.result(this, 'urlRoot') ||
_.result(this.collection, 'url') ||
urlError();
if (this.isNew()) return base;
return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
},
This is the function to be used by Backbone to generate the URL for model.fetch();.
You added a custom idAttribute when you declared your Store Model to be like the one in your DB. For example your database has a different id than id itself, but in your code you still use new Model({ id: id }); when you really should use new Model({ customId: id });. What happens behind the scenes is that you see in the url() function it checks if the model isNew(). This function actually checks if the id is set, but if it is custom it checks for that:
isNew: function() {
return !this.has(this.idAttribute);
},
You messed up with Backbone.sync ... lots of things can be done with this I will not even start unless I want to make a paper on it. Maybe you followed a tutorial without knowing that it might affect some other code.
You called model.fetch() "a la" $.ajax style:
model.fetch({
data: objectHere,
url: yourUrlHere,
success: function () {},
error: function () {}
});
This overrides the awesomeness of the Backbone automation. (I think sync takes over from here, don't quote me on that).
Reference: Backbone annotated sourcecode

Ember Data - TypeError: Object has no method 'eachRelationship'

So, I'm trying to build routes in my Ember application dynamically with data from an API endpoint, /categories, with Ember Data. In order to do this, I'm adding a didLoad method to my model, which is called by the controller and set to a property of that controller. I map the route to my router, and all that works fine. The real trouble starts when I try to set up a controller with a content property set by data from the server retrieved by findQuery.
This is the error:
TypeError {} "Object /categories/548/feeds has no method 'eachRelationship'"
This is the code:
window.categoryRoutes = [];
App.Categories = DS.Model.extend({
CATEGORYAFFINITY: DS.attr('boolean'),
CATEGORYID: DS.attr('number'),
CATEGORYNAME: DS.attr('string'),
CATEGORYLINK: function () {
var safeUrl = urlsafe(this.get('CATEGORYNAME'));
categoryRoutes.push(safeUrl);
return safeUrl;
}.property('CATEGORYNAME'),
didLoad: function () {
var categoryLink = this.get('CATEGORYLINK');
var categoryId = this.get('CATEGORYID');
App.Router.map(function () {
this.resource(categoryLink, function () {
// some routes
});
});
App[Ember.String.classify(categoryLink) + 'Route'] = Ember.Route.extend({
setupController: function(controller, model) {
// source of error
this.controllerFor(categoryLink).set(
'content',
this.store.findQuery('/categories/' + categoryId + '/feeds', {
appid: 'abc123def456',
lat: 39.75,
long: -105
})
);
}
});
}
});
Any 'halp' is appreciated!
Also, if I'm doing this completely wrong, and there's a more Ember-like way to do this, I'd like to know.
I figured this out. I got this error because I was passing in a string instead of a real 'type' from the App.Helpers object to an extract method in some custom RESTAdapter code I had overridden.
The solution is to pass in the corresponding model helper in App.Helpers using my custom type name.
Something like this in the overridden RESTAdapter.serializer.extractMany method:
var reference = this.extractRecordRepresentation(loader, App.Helpers[root], objects[i]);

Rendering with Backbone

I am trying to learn backbone and I was following along the code school backbone.js course to build my own backbone app. So far I have this code but I am having problems with rendering anything.
var PostsApp = new (Backbone.View.extend({
Collections: {},
Models: {},
Views: {},
start: function(bootstrap){
var posts = new PostsApp.Collections.Posts(bootstrap.posts);
var postsView = new PostsApp.Views.Posts({collection: posts});
this.$el.append(postsView.render().el);
}
}))({el : document.body});
PostsApp.Models.Post = Backbone.Model.extend({});
PostsApp.Collections.Posts = Backbone.Collection.extend({});
PostsApp.Views.Post = Backbone.View.extend({
template: _.template("<%= name %>"),
render: function(){
this.$el.html(this.template(this.model.toJSON()));
}
});
PostsApp.Views.Posts = Backbone.View.extend({
render: function(){
this.collection.forEach(this.addOne, this);
},
addOne: function(post){
var postView = new PostsApp.Views.Post({model:post});
this.$el.append(postView.render().el);
}
});
var bootstrap = {
posts: [
{name:"gorkem"},
{name: "janish"}
]
}
$(function(){
PostsApp.start(bootstrap);
});
I am just trying to create a very simple backbone app, CodeSchool is great but it not good at combining the pieces together and when I try to do that myself I am having problems.
So far the error I am getting is "Uncaught TypeError: Cannot read property 'el' of undefined" in the addOne function of the Posts View. Any help would be much appreciated.
edit: The answer below solved my initial problem, but I also set up an express server to send data to the front end with the code :
app.get('/tweet', function(req,res){
res.send([{ name: 'random_name' }, {name: 'diren_gezi'}] );
});
and then I am trying to fetch this data to my collection like this :
var PostsApp = new (Backbone.View.extend({
Collections: {},
Models: {},
Views: {},
start: function(bootstrap){
var posts = new PostsApp.Collections.Posts(bootstrap.posts);
posts.url = '/tweet';
posts.fetch();
var postsView = new PostsApp.Views.Posts({collection: posts});
postsView.render();
this.$el.append(postsView.el);
}
}))({el : document.body});
But in the page the initial data (name: gorkem and name: janish) is displayed instead of the recently fetched data..
This is the problem line (I see it in a few spots).
this.$el.append(postsView.render().el);
Try changing it to
postsView.render();
this.$el.append(postsView.el);
Render function doesn't return a function to self (the object with a reference to el).

how to extend a backbone view

I am new in SPA's with backbone and I am trying to develop a small app by using backbone and requireJs.
The problem I faced is that I can't extend a view by passing a collection.
Well, this is the view with name MenuView.js
define([
'Backbone'
], function (Backbone) {
var MenuView = Backbone.View.extend({
tagName: 'ul',
render: function () {
_(this.collection).each(function (item) {
this.$el.append(new MenuListView({ model: item }).render().el);
}, this);
return this;
}
});
return new MenuView;
});
and this is the router.js in which the error is appeared
define([
'Underscore',
'Backbone',
'views/menu/menuView',
'views/createNew/createNew',
'collections/menu/menuCollection',
], function (_, Backbone, MenuView, CreateNewView,Menucollection) {
var AppRouter = Backbone.Router.extend({
routes: {
'index': 'index',
'action/:Create': 'Create'
},
index: function () {
CreateNewView.clear();
//----------- HERE IS THE PROBLEM ------------
$('#menu').html(MenuView({ collection: Menucollection.models }).render().el);
},
Create: function () {
CreateNewView.render();
}
});
var initialize = function () {
var appRouter = new AppRouter();
Backbone.history.start();
appRouter.navigate('index', { trigger: true });
};
return {
initialize: initialize
};
});
The error message is "object is not a function". I agreed with this since the MenuView is not a function. I tried to extend the MenuView (MenuView.extend({collection:Menucollection.models})) and the error message was "objet[object,object] has no method extend".
I suppose that the way I am trying to do this, is far away from the correct one.
Could anyone suggest how to do this?
Thanks
#Matti John's solution will work, but it's more of a workaround than a best practice IMHO.
As it is, you initializing your view just by requiring it, which:
Limits you to never accept arguments
Hits performance
Makes it really hard to unit-test if you relay on assigning properties ater constructing an instance.
A module should be returning a 'class' view and not an instance on that view.
In MenuView.js I would replace return new MenuView with return MenuView; and intitalzie it when required in router.js.
Your MenuView.js returns an initialized MenuView, so you could just do:
MenuView.collection = Menucollection
Note I haven't selected the models - I think it's better if you don't use the models as a replacement for your view's collection, since it would be confusing to read the code and not have a Backbone collection as the view's collection. You would also lose the method's contained within the collection (e.g. fetch/update).
If you do this, then you would need to update your loop (each is available as a method for the collection):
this.collection.each(function (item) {
this.$el.append(new MenuListView({ model: item }).render().el);
}, this);

backbonejs: Cannot call method 'bind' of undefined

I am currently learning backbone.js via a screencast tutorial, somewhere along the way, my code seems to stop working, throwing the error Cannot call method 'bind' of undefined in Chrome's javascript console. The erroneous line is contained in the initialize function:
window.PlaylistView = Backbone.View.extend({
tag: 'section',
className: 'playlist',
initialize: function() {
_.bindAll(this, 'render');
this.template = _.template($('#playlist-template').html());
this.collection.bind('reset', this.render); //<<<<<< THIS LINE
this.player = this.options.player;
this.library = this.options.library;
},
render: function() {
$(this.el).html(this.template(this.player.toJSON()));
this.$('button.play').toggle(this.player.isStopped());
this.$('button.pause').toggle(this.player.isPlaying());
return this;
}
});
I don't know what this.collection means, why does the view have a collection, isn't collections for models? this.collection.bind() used in other views did not seem to throw any errors. In window.LibraryAlbumView which calls this.collection.trigger('select', this.model); and extends window.AlbumView, I dont see any collection defined anywhere in window.AlbumView, yet no error is thrown. This seems to be confusing me.
JSFIDDLE
EDIT:
The error has been fixed because instead of
window.Player = Backbone.Model.extend({
defaults: {
'currentAlbumIndex': 0,
'currentTrackIndex': 0,
'state': 'stop'
},
initialize: function() {
this.playlist = new Playlist();
},
I had
window.Player = Backbone.Model.extend({
defaults: {
'currentAlbumIndex': 0,
'currentTrackIndex': 0,
'state': 'stop'
},
initialize: function() {
playlist = new Playlist(); // <<< this line changed!
},
Also previously this.collection refered to the collection here,
window.BackboneTunes = Backbone.Router.extend({
routes: {
'': 'home',
'blank': 'blank'
},
initialize: function() {
this.playlistView = new PlaylistView({
collection: window.player.playlist, // <<<< THIS ONE!
player: window.player,
library: window.library
});
this.libraryView = new LibraryView({
collection: window.library
});
},
Backbone Views contain a collection or a model, because views are meant for presenting the data contained within a model or a collections of models.
This example throws an error because this.collection has not been defined yet. To do that you need to initialize some collection and then pass it on to your view.
new PlayListView({collection: someCollection});

Categories