Nested ItemViews, events not working properly - backbone.js

I have 2 models, implemented via backbone.relational and backbone.localstorage, they're working good.
and I have 2 views, first is a "single item" viewer and the second one is an item view with a render function to view my single item view in the way I want, the problem is events not working, neither in parent view nor in single item view.
I've reimplemented that code in the similar way to show you how it's not working ( code is in coffeescript ) :
log = console.log
class $.Girl extends Backbone.RelationalModel
localStorage: new Backbone.LocalStorage 'gals'
initialize: -> if typeof #get('id') is 'undefined' then #save() else #fetch()
class $.Girls extends Backbone.RelationalModel
localStorage: new Backbone.LocalStorage 'gals'
relations:[{
type: Backbone.HasMany
key: 'gals'
relatedModel: $.Girl
includeInJson: 'id'
}]
initialize: ->
if typeof #get('id') is 'undefined' then #save() else #fetch()
#fetchRelated()
class $.GirlView extends Marionette.ItemView
tagName: 'tr'
template: (data)-> '<td>'+data.name+' -- '+data.age+'<button>Love</button></td>'
initialize: ->
#listenTo #model,'change',#render
events:
'click button': 'sayLove'
sayLove : -> log 'I Love YOU!'
class $.GirlsView extends Marionette.ItemView
template: (data)->
'<table>
<thead><tr><th>My Gals</th></tr></thead>
<tbody></tbody>
<tfoot><tr><td>I Love Them!</td></tr></tfoot>
</table>'
initialize: (options)->
#models = #model.get('gals').models
#list = []
self = #
_.each #models,(girl)-> self.list.push new $.GirlView {model:girl}
events:
'click th': 'hello'
render: ->
#$el.html(#template {})
self = #
_.each #list,(girl)->
girl.delegateEvents()
self.$('tbody').append girl.render().$el
hello: -> log 'hello'
gal1 = new $.Girl {name:'gal1',age:'22',id:'gal-1'}
gal2 = new $.Girl {name:'gal2',age:'19',id:'gal-2'}
gals = new $.Girls {title:'maGals',id:'gals-1',gals:['gal-1','gal-2']}
gv = new $.GirlsView {model:gals}
gv.render()
$('body').append gv.$el.html()
It's kinda hello world for me.
Any idea how can I implement nested itemViews with events working or any other idea for this snippet is appreciated.

Try calling delegateEvents after appending your child views:
self.$('tbody').append girl.render().el
girl.delegateEvents()
// or maybe #delegateEvents()? the context should be the child here
Edit:
Change $('body').append gv.$el.html() to $('body').append #$el.

Related

Marionette CompositeView behavior

I'm trying to follow a Backbone Rails tutorial and I'm getting stuck trying to render my collection of links in a CompositeView, no nesting involved. I suspect that tutorial is drastically outdated, but since I yet lack Backbone skills I can't pin point the problem. Please, take a look at the following code:
Creating a collection of navigational links.
#TestApp.module "Entities", (Entities, App, Backbone, Marionette, $, _) ->
class Entities.Navigation extends Backbone.Model
class Entities.NavigationCollection extends Backbone.Collection
model: Entities.Navigation
API =
getLinks: ->
new Entities.NavigationCollection [
{ name: "one" }
{ name: "two" }
{ name: "three" }
{ name: "four" }
{ name: "five" }
]
App.reqres.setHandler "navigation:entities", ->
API.getLinks()
Core navigation file.
#TestApp.module "NavigationUnit", (NavigationUnit, App, Backbone, Marionette, $, _) ->
#startWithParent = false
API =
listNavigation: ->
NavigationUnit.List.Controller.listNavigation()
NavigationUnit.on "start", ->
API.listNavigation()
Controller, where I'm passing collection to the view.
#TestApp.module "NavigationUnit.List", (List, App, Backbone, Marionette, $, _) ->
List.Controller =
listNavigation: ->
links = App.request "navigation:entities"
navigationView = #getNavigationView links
App.navRegion.show navigationView
getNavigationView: (links) ->
new List.Navigation
collection: links
And the view.
#TestApp.module "NavigationUnit.List", (List, App, Backbone, Marionette, $, _) ->
class List.NavigationLinks extends Marionette.ItemView
template: "navigation/list/templates/_links"
tagName: "li"
class List.Navigation extends Marionette.CompositeView
template: "navigation/list/templates/list_navigation"
itemView: List.NavigationLinks
itemViewContainer: "ul"
Content of the ItemView template is %a{:href => "#"}= #name. And in the CompositeView is a basic wrapper structure with the %ul tag. Now what happens is that the CompositeView renders the template as expected, but it does not populate the %ul with the itemView. Instead it creates amount of divs equals to the amount of models in the collection (five in this case) and inserts there entire wrapper templates, so it looks like this:
#navRegion
.div
.navigation-wrapper
.navigation-content
%ul
.div
.navigation-wrapper
// entire template
.div
.navigation-wrapper
// entire template
//etc +3 divs
What am I doing wrong here?
Your tutorial is probably out of date. Marionette renamed the property from itemView to childView in version 2.0.0.
From the docs
Each childView will be rendered using the childView's template. The CompositeView's
template is rendered and the childView's templates are added to this.
var ChildView = Marionette.ItemView.extend({});
var CompView = Marionette.CompositeView.extend({
childView: ChildView
});

Is it possible to remove a model by cid?

I have a view called Entry.
class Movieseat.Views.Entry extends Backbone.View
template: JST['movieseats/entry']
className: 'movie-frame'
initialize: ->
#collection.on('change', #render, this)
#collection.on('remove', #render, this)
render: ->
$(#el).html(#template(entry: #collection))
this
events: ->
"click .remove": "removeEntry"
removeEntry: (e) ->
console.log #collection
This view creates a Entries template.
<div data-id="<%= #entry.get('id') %>">
<p><%= #entry.get('title') %></p>
<p><%= #entry.get('id') %></p>
<p class="remove">Remove</p>
</div>
What I want to do is to remove a model from the collection (movieseats) and then rerender the template. If I click on a entry I fire the console.log #collection event. This logs the following,
Backbone.Model {cid: "c4", attributes: Object, collection: Movieseats, _changing: false, _previousAttributes: Object…}
How would I target the model's cid and then remove it from the collection?
Update
If I use this code,
removeEntry: (e) ->
thisid = $(e.currentTarget).closest('div').data('id')
console.log #collection
modelToRemove = #collection.findWhere({cid: thisid });
#collection.remove(modelToRemove);
I get the following result in the console log.
Backbone.Model {cid: "c4", attributes: Object, collection: Movieseats, _changing: false, _previousAttributes: Object…}_changing: false_events: Object_pending: false_previousAttributes: Objectattributes: Objectchanged: Objectcid: "c4"collection: Movieseatsid: 531__proto__: Object
Uncaught TypeError: undefined is not a function
The problem seems to be this part,
modelToRemove = this.collection
You can do it using this code (see collection#remove method):
var modelToRemove = collection.findWhere({cid: "SOME_ID_HERE"});
collection.remove(modelToRemove);
See also findwhere docs.
Edit
From the output of the console.log method it appears that the #collection variable in your above code is not a true Backbone.Collection, but rather a model pointing to the collection. Thus, you should modify the above code to be:
trueCollection = #collection.get("collection") // or #collection.collection
modelToRemove = trueCollection.findWhere( cid: "SOME_ID_HERE" )
trueCollection.remove(modelToRemove)

How to handle belongs to and has many relationship in Backbone js

I have two models, User and Picture. User has many Pictures. I want to make a suspend call to backend server query on Picture. Currently when I hit the suspend button I get the User model but I want picture model. In my User view I have following code.
USER VIEW
class MyApp.Views.User extends Backbone.View
initialize: ->
#listenTo(#model, 'change', #render)
#listenTo(#model, 'destroy', #remove)
render: ->
$(#el).html(#template(user: #model))
#fetchPictures()
this
fetchPictures: ->
#picture_collection = new MyApp.Collections.Pictures()
#picture_collection.fetch({
reset: true,
data: { "user_id": #model.get("objectId") }#,
success: (e) ->
for picture in e.models
view = new MyApp.Views.Picture(model: picture)
$("#objects-info").html(view.render().el)
})
PICTURE VIEW
class MyApp.Views.Picture extends Backbone.View
template: JST['flagged_objects/picture']
el: 'td'
events: ->
"click #Picure": "deletePicture"
initialize: ->
#model.set('id', this.model.get('objectId'))
#listenTo(#model, 'change', #render)
#listenTo(#model, 'destroy', #remove)
render: ->
$("#object-info").append(#template(entry: #model))
this
deletePicture: (e) ->
e.preventDefault()
console.log #
PICTURE COLLECTION
class MyApp.Collections.Pictures extends Backbone.Collection
model: MyApp.Models.Picture
url: "/api/pictures"
PICUTRE MODEL
class MyAdmin.Models.Picture extends Backbone.Model
urlRoot: 'api/picture'
idAttribute: 'objectId'
In USER VIEW in #model variable I get User model. Is there any way to get Picture model here so that I can send call to suspend the picture.
In summary I just want to suspend picture model obejct from collection when I press suspend button. Suspend essentially is an update call.
Thanks,
So as we have spoken in the comments, the deletePicture function should be in the Picture sub-view as what you want to suspend is a Picture model.
I think the strange behaviour you are having it's related to the way you are rendering your views.
In the user view you should append the Picture sub-views.
fetchPictures: ->
#picture_collection = new MyApp.Collections.Pictures()
#picture_collection.fetch({
reset: true,
data: { "user_id": #model.get("objectId") }#,
success: (e) ->
for picture in e.models
view = new MyApp.Views.Picture(model: picture)
$("#objects-info").append(view.render().el)
})
And it's in the render of the sub-view where you get access to the html function.
render: ->
this.$el.html(#template(entry: #model))
this
Let me know how it goes!

Having trouble rendering my collection object in backbone marionette with coffeescript

I can't get ItemView to render my values defined in the testMap. All I want is the 1, 2, 3 to be dynamically inserted in the <ol class='test-list'> tag as <li> so my html would look like
<ol class="test-list">
<li>1</li>
<li>2</li>
<li>3</li>
</ol>
What am I missing?
testMap =
a: '1'
b: '2'
c: '3'
class TestLayout extends Layout
template: require '/test_template'
regions:
body: 'section'
events:
'click #testme': 'test'
test: -> app.vent.trigger 'test'
class TestItemView extends ItemView
template: require '/test-item'
serializeData: -> {testMap}
class TestListView extends CollectionView
tagName: 'ol'
className: 'test-list'
itemView: TestItemView
module.exports = class TestPlugin extends Application
testList: null
initialize: =>
#test = new Collection
app.vent.on 'test', #showTest, #
showTest: ->
app.layout.test.show #layout = new TestLayout
#layout.body.show #testList= new TestListView collection: #test
My Html file named test-item looks like this:
<span class='test'>
<%= #testMap %>
</span>
All my globals are defined in the extended files.
I think the culprit here is that I can't get my collection to bind to my itemView. Therefore, when I call #showTest function I get nothing because my testMap object is not talking to my #test variable. How can I marry the two object?
#layout.body.show #listView = new TestListView collection: #test

How to display Backbone nested attribute using Eco?

I've followed Ryan Bates' example on Backbone.js to start a project using Backbone + Eco. It's great. However, I'm stuck trying to display a nested attribute.
For example, I'm trying to do this: <%= #stream.user.get('name') %> in index.jst.eco
and I'm getting Uncaught TypeError: Cannot call method 'get' of undefined
However, I can get <%= #stream.get('stream_type') %> to work.
Here's the REST API data:
[
: {
: : "id":"5004095283de4ca9ff000005",
: : "created_at":"2012-07-16T12:30:10Z",
: : "stream_type":"project",
: : "user":
: : {
: : : "id":"5002f30560de7d0ffb000003",
: : : "name":"Regular User2"
: : },
...
I've also tried extending my Model using Backbone.DeepModel but that didn't seem to work.
class Project1.Collections.Streams extends Backbone.Collection
url: '/streams'
model: Topaz.Models.Stream
class Project1.Models.Stream extends Backbone.DeepModel
Here's my views on the collection, pretty standard.
class Project1.Views.StreamsIndex extends Backbone.View
#views/streams/index.js
template: JST['streams/index']
initialize: ->
#collection.on('reset', #render, this)
#collection.on('add', #appendStream, this) #rerenders entire template
render: ->
$(#el).html(#template())
#collection.each(#appendStream)
this
appendStream: (stream) =>
view = new Topaz.Views.Stream(model: stream)
#$('#streams').append(view.render().el) # looks for the #entries element within the view’s element and not on the page directly
and here's the View for the model
class Project1.Views.Stream extends Backbone.View
template: JST['streams/show']
className: 'stream-item'
initialize: ->
#model.on('change', #render, this) #The change event is triggered by Backbone when a model saves
render: ->
$(#el).html(#template(stream: #model))
this

Resources