Events bubling from child views doesn't work as expected - marionette

I would like to use the bubling behaviour of collection views but it doesn't seem to work.
A bit of context: I display a modal AddPartFromPurchase that show a table populated with a collectionView. This works well.
When the user clicks a row, the itemview triggers the purchase:chosen event so according to the documentation, I expect the collection view to receive the itemview:purchase:chosen event but no such event is ever triggered: neither in AddPartFromPurchase or Purchases. :(
Here is the example code.
AddPartFromPurchase = Backbone.Marionette.ItemView.extend
template: 'pages/vehicles/modifications/add_part_from_purchase'
initialize: (attributes)->
#purchases = attributes.purchases
onRender: ->
view = new Purchases(el: #$('tbody'), collection: #purchases)
#bindTo(view, 'all', #foo)
view.render()
foo: (event, foo, bar, baz)->
console.log(event, foo, bar, baz)
Purchase = Backbone.Marionette.ItemView.extend
template: 'pages/vehicles/modifications/purchase'
tagName: 'tr'
events:
'click' : 'selectPurchase'
selectPurchase: ->
#trigger('purchase:chosen', #model)
false
serializeData: ->
purchase: #model
part: #model.get('part')
Purchases = Backbone.Marionette.CollectionView.extend
itemView: Purchase
initialize: ->
#bindTo(#, 'all', #foo)
foo: (event, foo, bar, baz)->
console.log(event, foo, bar, baz)
Maybe I'm doing it wrong, I feel bad about defining the listener in the onRender, but as I use a el I can't do that in initialize.
How can I deal with that?

Answer based on comment stream: be sure you're on v0.7.6 or higher, when this feature was introduced.

Related

how do I show just one item of my marionette collection and navigate between them

I have a route
routes: { "pages/:id" : "page"
},
page: function (id) {
var pageView = new contentCollectionView({
collection: collection,
tagName: "div",
className: "pages"
});
pageView.close();
PAGE.content.show(pageView);
},
However what I want to do is to only show the individual page in my collection that matches the id I am passing - I also want to be able to be navigate from that page to the preceding and next page in the collection.
So - how do I pass the identifier in to my pageView at the time of rendering so it only does the one item in the collection ( I believe I know how to get the item index in the collection as it loops through, but if you want to show an example of that as well it would be nice)
According to your requirement i would recommend to render ItemView instead of CollectionView.
If you have CollectionView you have ItemView for sure :)
and pass special by ID model from you collection.
So you code may look like this:
page: function (id) {
var pageView = new contentItemView({
model: collection.get(id)
});
pageView.close();
PAGE.content.show(pageView);
}
About navigation - you can check with at method index of model in collection to actualize navigation logic. If you also need to keep browser navigation(back and forward buttons) you have to checkout Backbone.history

Can I trigger an event on a specific model in a collection in backbone?

Say I have the following structure -
Model: Folder
Collection: Folders
From within my collection view, I want to trigger an event on a folder with a specific name -
So,
FolderCollectionView = Backbone.View.extend({
...
...
editFolder: function() {
this.collection.findWhere({ name: "abcd" }).trigger("editThisFolder");
}
});
FolderModelView = Backbone.View.extend({
...
...
editThisFolder: function() {
//This should get called
}
});
Is this possible? I'm using event aggregators, however, i haven't found a way wherein, I can trigger an event on a specific folder, I can make the folder view subscribe to a collection view event, but then all folder views respond to that event, I haven't found a way to make only a specific folder view respond to a collection event. Or somehow trigger an event on a specific folder view from the collection view.
I'm new to this, so let me know if i'm missing something important.
Event aggregator reference - http://lostechies.com/derickbailey/2011/07/19/references-routing-and-the-event-aggregator-coordinating-views-in-backbone-js/
Backbone has underscore.js as a dependency. One of the reasons for this is the fact that Backbone.js Collections implement a whole lot of underscore methods. So your solution should work
this.collection.findWhere({ name: "abcd" }).trigger("editThisFolder");
Then of course you should make your FolderView listen to that event and call the function
var FolderView = Backbone.View.extend({
initialize: function() {
// assuming a folder model assigned to each view
this.listenTo(this.model, 'editThisFolder', this.editThisFolder);
}
});
Did this answer your question?

Event not triggering in Marionette Itemview

I am trying to trigger an event in the marionette itemview (List.SendQuestion), however, I was unable to register the trigger in the controller (as seen below)
Essentially, after clicking on the 'a.send', a trigger was supposed to happen and the 'send_qn_view' should capture the event and print out the message 'triggered'. But that was not happening.
Can someone advise me what might be going wrong here?
#Dailymuses.module "SidebarModule.List", (List, App, Backbone, Marionette, $, _) ->
List.Controller =
showSidebar: ->
send_qn_view = new List.SendQuestions
collection: Onethingaday.Public.friends
send_qn_view.on "itemview:ask:user", (itemview, question) ->
console.log('triggered') #THIS IS NOT EXECUTED
class List.SendQuestion extends Marionette.ItemView
template: "sidebar/list/templates/send_question"
className: 'qn_askee'
tagName: 'li'
events:
"click a.send" : "sendQuestion"
sendQuestion: (e) ->
e.preventDefault()
debugger #this debugger was triggered
#trigger "ask:user", #model
class List.SendQuestions extends Marionette.CompositeView
template: "sidebar/list/templates/send_questions"
itemView: List.SendQuestion
itemViewContainer: "ul.friends"
Edit: Corrected my answer and updated my fiddle
Sorry about that. Your syntax for event bubbling is correct.
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.collectionview.md#childview-event-bubbling-from-child-views
"itemview:*" event bubbling from child views
When an item view within a collection view triggers an event, that
event will bubble up through the parent collection view with
"itemview:" prepended to the event name.
That is, if a child view triggers "do:something", the parent
collection view will then trigger "itemview:do:something".
Something to note. Marionette supports view triggers. If your callback is just triggering a view event you can remove the event hash and callback and shorten your code to this:
triggers: {
"click a.send": "ask:user"
}
http://lostechies.com/derickbailey/2012/05/15/workflow-in-backbone-apps-triggering-view-events-from-dom-events/
Fiddle:
http://jsfiddle.net/FRHkt/1/
For anyone new coming to this example, with Marionette 2.x, itemview:* for children views have been replaced with childview: triggers in the parent view, and the collection and composite views have also had their parameters changed from itemView and itemViewContainer to childView and childViewContainer

Backbone JS where to put Application main logic

Using Backbone i'm starting to build an App where i have everything cleanly separated. But now i have the following question. Where should i put the App main logic, in the views or in the model.
For example i have a view and a model, which are binded to a button and when i click that button i have to make
$.ajax(params)
do i put that in the view or the view calls a method with :
this.model.doAction(params)
which do you think is the best approach?
You can define an events property in the view which is of the format {"event selector": "callback"} for eg. {"click .collapse": "collapse"} where collapse would be a function defined as a property of the view. Then write your ajax request code in this callback function.
Also, unless I am missing something, "binding a view and model to a button" doesn't sound correct Backbone way to me. Instead you should think of one instance of model associated with one instance of the view. Whenever an attribute of the the model instance changes, a model change event will be triggered. You can bind a view function to this event so that change in the model is reflected in the view. Here is a quick example
var Book = Backbone.Model.extend({
// ...
});
var BookView = Backbone.View.extend({
initialize: function () {
this.model.bind('change', this.render, this);
},
render: function () {
// here, make changes to the dom as per changes in model
}
});
To associate a model with a view instance, you can pass the it while instantiating a new
view object..
var book = new Book({
title: "A great book"
});
var view = new BookView({model: book});
view.model.set('author', 'AGreatAuthor');
The set function call will fire change event and will result in render function of view
to be called.
Refer to the annotated source of Todos app example for a complete example.

Binding to model change event not working in view in backbone.js

I'm creating a very simple backbone app to familiarize myself with the framework. All is working except for binding of model change events to a function in my view. I've checked previous questions on SO and none have helped.
My Model has one variable 'counter'. View has 2 buttons which increment or decrement model's 'counter'. Simple stuff. That's all working fine, but when I try and listen for model change events in the view, I only receive notification once -on model creation (when defaults are created, I assume).
I know that counter is being updated because if I manually call render after updating the model I can see the effect, but in the interest of better mvc-ish structure, I want to view to be notified of change events and to update itself.
Below is the coffeescript code.
$ ->
class Count extends Backbone.Model
defaults: counter : 0
change: -> console.log('changed')
class Spinner extends Backbone.View
el: $('#counterView')
initialize: =>
#model = new Count()
#model.bind 'change' , #update()
events:
'click button#incBtn' : 'inc'
'click button#decBtn' : 'dec'
inc: ->
#model.set counter : #model.get('counter') + 1
dec: ->
#model.set counter : #model.get('counter') - 1
update: ->
console.log('update')
$('#num').html(#model.get 'counter')
view = new Spinner()
HTML:
<body>
<div id="counterView">
<button id="incBtn">Increment</button>
<button id="decBtn">Decrement</button>
<div id="num">Number</div>
</div>
</body>
Thanks in advance.
b
Your error is here:
#model.bind 'change' , #update()
What you're telling it is to bind the change event to what #update() returns, when you want to bind it to #update itself. So it should be:
#model.bind 'change' , #update
(Without the brackets). As it were, Spinner.update would execute immediately on Spinner.initialize, exactly as you found out. A few more notes:
It's unnecessary to wait for document.ready to create your classes. You could do that first (outside of document.ready), and only instantiate the models, views etc on document.ready.
It seems kind of weird to create a new model inside of your view. You probably want to do something like this instead:
view = new Spinner(model: new Count)
Edit: As Trevor Burnham notes below, you want the fat arrow => on inc, dec and update. Fixed below.
Taken together:
class Count extends Backbone.Model
defaults: counter : 0
change: -> console.log('changed')
class Spinner extends Backbone.View
el: '#counterView'
initialize: =>
#model.bind 'change' , #update
events:
'click button#incBtn' : 'inc'
'click button#decBtn' : 'dec'
inc: =>
#model.set counter : #model.get('counter') + 1
dec: =>
#model.set counter : #model.get('counter') - 1
update: =>
console.log('update')
#$el.find('#num').html(#model.get 'counter')
$ ->
view = new Spinner(model: new Count)
Ok, so after some fiddling, the following is working. For some reason, when the model is updated, it fails to dispatch the event back to the view so I'm manually dispatching a change event from the model with:
#trigger('change')
Pretty clunky, but it's the only thing that works for me.
I also noticed quirky behaviour when using defaults, so I'm now setting counter with initialize (rather than through defaults). To see the unintended behaviour with defaults, uncomment defaults and comment out initialize. It's like change event is not dispatched whenever counter = original value of 0.
class Count extends Backbone.Model
#defaults: counter : 0
initialize: ->
#set counter : 0
change: ->
#trigger('change') #this lil fella made it work
$ ->
class Spinner extends Backbone.View
el: $('#counterView')
initialize: =>
#model = new Count
#model.bind 'change' , #update
#update()
events:
'click button#incBtn' : 'inc'
'click button#decBtn' : 'dec'
inc: =>
#model.set counter : #model.get('counter') + 1
dec: =>
#model.set counter : #model.get('counter') - 1
update: =>
$('#num').html(#model.get 'counter')
view = new Spinner()

Resources