I’m confused – I thought I had model binding working correctly, but it was only as a jsFiddle with faked ajax request. I’ve bound a model to a view, and if I override .fetch() and fake the response, everything works (I can update the model and the view updates on the page). However, when I don’t override .fetch() and use the urlRoot param and wait for the response, I get errors.
Here is the working jsFiddle where a model is rendered after calling .fetch() with a faked response, than changed:
http://jsfiddle.net/franklovecchio/FkNwG/182/
So if I have an API call on the server side:
/thing/:id
With an example response for /thing/1:
{"id":"1","latitude":"lat1","longitude":"lon1"}
And I comment out .fetch(), I get the console errors:
load js core functions core.js:2
init model timeout app.js:114
initializer callback for history, routes app.js:95
App.Layouts.MyLayout onShow app.js:41
App.Regions.MyRegion onShow app.js:25
App.Models.Thing init app.js:55
App.ItemViews.Thing init app.js:87
Uncaught TypeError: Object #<Object> has no method 'toJSON' backbone.marionette-0.8.1.min.js:9
Parsing App.Models.Thing.fetch() response: {"id":"1","latitude":"lat1","longitude":"lon1"} app.js:62
Thing: {"id":"1","latitude":"lat1","longitude":"lon1"} app.js:66
a Thing has changed, update ItemView! app.js:57
Uncaught TypeError: Cannot call method 'render' of undefined app.js:58
update model app.js:108
Uncaught TypeError: Object #<Object> has no method 'set'
The code with .fetch() commented out:
window.App = { }
window.App.Regions = { }
window.App.Layouts = { }
window.App.Models = { }
window.App.ItemViews = { }
window.App.Rendered = { }
window.App.Data = { }
# ----------------------------------------------------------------
# App.Regions.MyRegion
# ----------------------------------------------------------------
class MyRegion extends Backbone.Marionette.Region
el: '#myregion'
onShow: (view) ->
console.log 'App.Regions.MyRegion onShow'
App.Regions.MyRegion = MyRegion
# ----------------------------------------------------------------
# App.Layouts.MyLayout
# ----------------------------------------------------------------
class MyLayout extends Backbone.Marionette.Layout
template: '#template-mylayout'
regions:
contentRegion: '#content'
anotherRegion: '#another'
onShow: (view) ->
console.log 'App.Layouts.MyLayout onShow'
App.Layouts.MyLayout = MyLayout
# ----------------------------------------------------------------
# App.Models.Thing
# ----------------------------------------------------------------
class Thing extends Backbone.Model
urlRoot: () ->
'/thing'
initialize: (item) ->
console.log 'App.Models.Thing init'
@bind 'change', ->
console.log 'a Thing has changed, update ItemView!'
@view.render()
parse: (resp) ->
console.log 'Parsing App.Models.Thing.fetch() response: ' + JSON.stringify resp
@attributes.id = resp.id
@attributes.latitude = resp.latitude
@attributes.longitude = resp.longitude
console.log 'Thing: ' + JSON.stringify @
@
# If I don't override, I get an error.
###fetch: () ->
console.log 'override ajax for test - App.Models.Thing.fetch()'
resp =
id: 1
latitude: 'lat1'
longitude: 'lon1'
console.log 'Faked Thing response: ' + JSON.stringify resp
@parse resp###
App.Models.Thing = Thing
# ----------------------------------------------------------------
# App.ItemViews.Thing
# ----------------------------------------------------------------
class Thing extends Backbone.Marionette.ItemView
template: '#template-thing'
initialize: (options) ->
console.log 'App.ItemViews.Thing init'
# Bind
@options.model.view = @
App.ItemViews.Thing = Thing
# ----------------------------------------------------------------
# App.MyApp ...the Marionette application
# ----------------------------------------------------------------
App.MyApp = new Backbone.Marionette.Application()
# ----------------------------------------------------------------
# App.MyApp before init
# ----------------------------------------------------------------
App.MyApp.addInitializer (data) ->
console.log 'initializer callback for history, routes'
App.Rendered.myRegion = new App.Regions.MyRegion
App.Rendered.myLayout = new App.Layouts.MyLayout
App.Rendered.myRegion.show App.Rendered.myLayout
# GET thing
App.Data.thing = new App.Models.Thing(id: 1)
.fetch()
App.Rendered.thingView = new App.ItemViews.Thing(model: App.Data.thing)
App.Rendered.myLayout.contentRegion.show App.Rendered.thingView
# ----------------------------------------------------------------
# Test
# ----------------------------------------------------------------
App.updateModel = ->
console.log 'update model'
# Update the Thing with id = 1
App.Data.thing.set
latitude: 'somenewlat'
App.updateModelTimeout = ->
console.log 'init model timeout'
setTimeout 'App.updateModel()', 2000
App.updateModelTimeout()
$ ->
data = { }
App.MyApp.start data
There are a lot of strange and confused things going on here. Fear not, all is not yet lost and the confusion can be sorted out.
Backbone’s
fetchis supposed to return ajqXHR, not the model itself. Yourfetchimplementation incorrectly returns@parse respand yourparsereturns@(which is also incorrect and sometimes two wrongs do make a right). The result is that this:gives you a useful
App.Data.thingwhen you use yourfetchbut it won’t be right with Backbone’sfetch. So yourfetchis broken and you’re not using thefetchcorrectly; then you try to give thejqXHRto your view as the model and your view sets@viewon thejqXHRrather than the model:So you end up with a
viewproperty on thejqXHRbut the model has no@view(becauseApp.Data.thingis not the model) and you get a “Cannot call method ‘render’ of undefined” error in your model’s change handler.You should be doing this:
Now on to your
parse:From the fine manual:
So
parseis just supposed to massage the server’s response into something that can beseton the model. Yourparseis setting the attributes and returning@. The result is that you’ll end up doing the equivalent ofm.set(m). So get rid of yourparseimplementation, yours is incorrect and you don’t even need one.Your model/view connection is backwards: views reference models, models don’t reference views. You have this in your model:
and this in your view:
You should be passing the model to the view when you create it (which you are so this is okay):
and then the view should bind to the model:
and you can drop the
initializeimplementation in your model.Also, you can (and should) declare classes directly in a namespace, don’t do this:
do this:
Furthermore, the string/eval form of
setTimeoutis evil should almost never be used. Don’t do this:do this:
There may be more but hopefully this will get you started and solve your immediate problems.