I'm using coffeescript. My code is pretty simple:
class SomeCollection extends Backbone.Collection
constructor: (#options) ->
url: ->
"#{$SCRIPT_ROOT}/some/data/#{#options.someId}"
model: SomeModel
class SomeView extends Backbone.View
initialize: ->
myCollection = new SomeCollection()
myCollection.fetch
success: (coll, resp) ->
console.log coll
The JSON that's being returned from my collection's url is exactly:
[{"id": 1, "comments": "", "name": "images/exceptions/59.png"}]
However, before anything is printed to the console, I receive a backbone.js error on line 768: Cannot read property 1 of undefined. The undefined object is this._byId within the collection's get function. How can I solve this problem?
You are extending Backbone.Collection and providing your own constructor, so you need to make sure to call the parent constructor.
constructor: (#options) ->
super null, #options
Also, the standard arguments for a Collection are (models, options), so I would stick with that.
constructor: (models, #options) ->
super models, #options
Or better yet, use initialize instead of constructor to avoid that entirely
initialize: (models, #options) ->
Related
I've got a "FormView" class in my Marionette application that sets up a lot of things for form submissions around my app. I use it every time there is a form. I've also got some helpers that I want to pass into every form template via the templateHelpers method, but I also want to be able to add additional templateHelpers in the children. Like so:
class Views.FormView extends Marionette.ItemView
templateHelpers: ->
helpers: Marionette.Concerns.Helpers
class Views.NewUser extends Views.FormView
templateHelpers: ->
variable: #something
I want to be able to access both #variable and #helpers from the template for NewUser. Additionally, I know how to handle this (via _.extends) if templateHelpers is an object, but I need it to be a function.
Is it possible? And if so, how?
In Backbone, when you inherit from another class, Backbone will give the subclass a __super__ property (double underscore at both ends) which is the prototype of the parent class. So from within your Views.NewUser class, you can get the prototype via View.NewUser.__super__. Then, you can call templateHelpers on that object. Here's how I did it (live example at JSbin):
Views = {}
class Views.FormView extends Marionette.ItemView
templateHelpers: ->
helpers: "Parent Value"
logHelpers: ->
console.log #templateHelpers()
class Views.NewUser extends Views.FormView
templateHelpers: ->
variable: "Child Value"
logHelpers: ->
console.log #templateHelpers()
console.log Views.NewUser.__super__.templateHelpers()
formView = new Views.FormView()
formView.logHelpers() // { helpers: "Parent Value" }
newUser = new Views.NewUser()
newUser.logHelpers()
// { variable: "Child Value" }, { helpers: "Parent Value" }
You could then use _.extend to extend one with the other; maybe like this:
initialize: ->
#helpers = _.extend #templateHelpers(),
Views.NewUser.__super__.templateHelpers()
Assuming that when you say "parent" you mean "parent view" rather than OO ancestor.
What you want to achieve can be done by doing the following:
1. Pass the data object from parent to child view using the itemViewOptions (or childViewOptions in newer Marionette). It can contain functions.
2. In you child view you can do the following:
templateHelpers: ->
_.extend super(), #options.someOptionsFromParent
I've got an event in my view which on keyup does a fetch request to the TMDB api,
class Movieseat.Views.Moviesearch extends Backbone.View
template: JST['movieseats/moviesearch']
el: '#moviesearch'
initialize: (opts) ->
#collection.on('reset', #render, this)
{#collection} = opts
#render()
return
render: ->
$(#el).html(#template(collection: #collection))
return
events:
"keyup input": "doSearch"
doSearch: (e) ->
#collection.setQuery $(e.currentTarget).val()
#collection.fetch()
view = new Movieseat.Views.Movie()
$('#movies').append(view.render().el)
This is my collection,
class Movieseat.Collections.Moviesearch extends Backbone.Collection
url: -> "http://api.themoviedb.org/3/search/movie?api_key=a8f7039633f2065942cd8a28d7cadad4&query=#{#query}"
setQuery: (q) ->
#query = q
return
So if my input is inception this is the fetch request,
http://api.themoviedb.org/3/search/movie?api_key=a8f7039633f2065942cd8a28d7cadad4&query=inception
As you can see in my view I'm append a template called movies. I would like to show all of the original_title from the fetch request in that template. And update the template when it changes.
From what I understood, your collection holds elements of type movie or something like that. After you fetched the collection, it's models should contain the original_title and poster_path. Now you can use pluck method to read all those properties. The code should look like that (javascript):
collection.on("sync", function() {
myView.displayTitles(collection.pluck("original_title"));
})
Update
With this code update you posted, I'm wondering why don't you use those properties inside a collection's template. Given that your collection doesn't declare the type of models it contains, you cannot use the #model.get('prop') syntax, as #model is not a Backbone model. Try using
#model.prop
syntax instead.
Here's the entirety of my simple Backbone app. When I instantiate a new view, I'm passing it a model, but when the view calls attributes on the model, it says model is undefined:
class App.Recipe extends Backbone.Model
class App.RecipeList extends Backbone.Collection
url: '/users/4/recipes'
model: App.Recipe
#recipeList = new App.RecipeList
#recipeList.fetch()
class App.RecipeView extends Backbone.View
template: _.template(#model.attributes)
render: ->
#$el.html(#template)
#
#recipeView = new App.RecipeView(model: recipeList.models[0])
$ ->
$('#featured_recipes').html(window.recipeView.render())
In the console, recipeList.models[0] returns a JSON object that does indeed have an attributes property. So when I instantiate the view and pass it a model, why do I get Uncaught TypeError: Cannot read property 'attributes' of undefined?
My guess is that #model is getting evaluated at runtime, before I pass in the model. Is there a way to defer that? Or am I wrong?
I've also tried the _.bindAll # in the initialize function of the view. No dice.
PS — I'm obviously a noob, so if you see other antipatterns or things I'm going to run into here, feel free to mention them.
Edit
I've tried the answer outlined here of adding the template inline in my initialize method like this:
class #App.RecipeView extends Backbone.View
tagName: 'li'
initialize: ->
_.bindAll #, 'render'
template = _.template $('#featured_recipes').html()
render: ->
#$el.html #model.toJSON()
#
But then I get Cannot call method 'replace' of undefined , which I know is from underscore calling template on something that isn't in the DOM yet, and that's happening because this gets called before my DOM renders. Any thoughts?
Edit 2
Now I've moved the entire app into the footer of my haml layout, so that the $(#featured-_recipes') div is in the DOM. Then I changed the view to this:
class #App.RecipeView extends Backbone.View
tagName: 'li'
initialize: ->
_.bindAll #, 'render'
#template: _.template $('#featured_recipes').html()
render: ->
#$el.html #template(#model.toJSON())
#
But I'm still getting Cannot call method 'replace' of undefined .
Try the following. I added an initialize method to your View, and I used Backbone's Collection get method to get your recipe.
class App.RecipeView extends Backbone.View
initialize: (options) ->
#model = options.recipe
template: _.template(#model.attributes)
render: ->
#$el.html(#template)
#
#view = new App.RecipeView
recipe: recipeList.get(0)
I'm writing a small app using Backbone. I start creating a SongsView which creates a SongsCollection. I fetch this collection to retrieve the data from an external API I wrote. The next step is to render the fetched data using the toJSON method, however calling toJSON returns [undefined], despite the fact that the collection is an instance of Bakcbone.Collection.
Here is my code (in coffeescript):
App:
songs = new SongsView
songs.render()
SongsView:
SongsCollection = require 'scripts/collections/songs'
class SongsView extends Backbone.View
el: '#songs'
render: ->
songs = new SongsCollection
songs.fetch
success: ( res ) =>
console.log (res instanceof Backbone.Collection) # true
console.log (res instanceof SongsCollection) # true
console.log (res.toJSON()) # [undefined]
SongsCollection:
Song = require 'scripts/models/song'
class SongsCollection extends Backbone.Collection
model: Song
url: 'http://localhost:1337/songs'
Song:
class Song extends Backbone.Model
constructor: ({#name, #path}) ->
console.log 'new'
EDIT: If I look at the prototypes chain, I can find a toJSON() method though :
EDIT²: Same behavior for a single model :
console.log (res.models[0].toJSON()) # undefined
Which is actually interesting. It means that the toJSON method from the SongsCollection works but the toJSON from Song does not. I'll dig deeper there.
Problem solved. I was using constructor instead of initialize which leads to create a model without any attributes, thus, calling toJSON returned undefined as the attributes property was not defined.
class Song extends Backbone.Model
initialize: ({#name, #path}) ->
console.log 'new'
Update
It was a stupid typo. I was using Backbone.Model.extend for the collection. facepalm
Trying to iterate through a collection but I think I've populated it incorrectly or something:
RecentContent = Backbone.View.extend
initialize: ->
#collection = new ContentAPI.ContentCollection()
#collection.fetch
success: (collection, response, options) =>
console.log #collection
# d {attributes: Object, _escapedAttributes: Object, cid: "c4", changed: Object, _silent: Object…}
# property `attributes` contains Objects from server
console.log #collection.models # undefined
#render()
#---------------------
render: ->
# ERROR: Object has no method 'each'
#collection.each (model) ->
console.log model
I also noticed that if I tried to bind the reset event to #collection (instead of render from within the success callback), it never seems to get fired.
The collection is very simple:
class ContentAPI
#Content: Backbone.Model.extend {}
#ContentCollection: Backbone.Model.extend
url: "/api/content/"
model: #Content
I'm a little new to Backbone so thank you for helping. :)
The problem is that your collection is inheriting from the wrong base class.
#ContentCollection: Backbone.Model.extend
should be
#ContentCollection: Backbone.Collection.extend
I am no coffeescript expert, but I think your problem is
#ContentCollection: Backbone.Model.extend
It should be
#ContentCollection: Backbone.Collection.extend
Also when iterating over your collection's models, use
_.each(collection.models, function(model) { console.log(model); });