ItemView event not working when triggered in onClose handler - backbone.js

I have a Backbone Marionette CollectonView that is listening for an event in its child views as follows:
this.on(this, 'itemview:timeline:storyRemoved', this._storyRemoved);
The itemview triggers the event during its onClose:
onClose: function () {
this.trigger('timeline:storyRemoved', { model: this.model });
}
But the _storyRemoved function is never called.
If I move the trigger to onShow then it works fine:
onShow: function () {
this.trigger('timeline:storyRemoved', { model: this.model });
}
I'm guessing it's something to do with the fact that the view has been closed and is therefore not in the collection view any longer?
Any way of getting this to work?

You're right that the view is not listening because it has already been closed when you trigger the event.
Depending on what you're doing, shouldn't the model itself trigger this event? That way, you could add something like this in your collection view:
collectionEvents: {
'remove': '_storyRemoved'
}
As you can see here, Removing a model will trigger a remove event with a reference to the model that was removed.

I like gbsice's point here, and I also wanted to add a more general answer: you can use onBeforeClose rather than onClose if you need something executed prior to the close event.
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.collectionview.md#onbeforeclose-callback
So in your case, it would be
onBeforeClose: function () {
this.trigger('timeline:storyRemoved', { model: this.model });
}

Related

Prevent Backbone event from triggering more than once, how to check if event is registered already?

In my router object, I created an event object to share among my views
I pass the event object to my views
I register events to this shared object like this
var productCatalogView = Backbone.View.extend({
initialize: function (options) {
//bind alert event to sharedEvents
options.sharedEvents.bind("alert", this.alert,this);
},
alert: function () {
alert('alerted');
}
});
//The following view triggers the alert event
var testView = Backbone.View.extend({
initialize: function (options) {
this.sharedEvents = options.sharedEvents;
},
events: {
'click #test': 'triggerAlert'
},
triggerAlert: function (e) {
this.sharedEvents.trigger("alert", null);
}
});
THE PROBLEM:
The problem I experience is that the first time I click on the button which triggers the alert event (second view), the alert event gets called once (good), this causes the first view to be re-rendered by triggering the route passing search parameters, therefore creating the first view and binding the sharedEvents again, hence when I trigger the alert event a second time, it gets triggered twice (bad), the next time I repeat the same process, it gets triggered 3 times, and so on and so forth. I guess it has to do with the event binding in the first view, it occurs more than once, i.e each time the view is initialized (if I am correct)
please how can I make the binding of the event occur once.
Here is my router which shows how I initilze the views:
var Router = Backbone.Router.extend({
sharedEvents:_.extend({},Backbone.Events),
catalog: function (id) {
//....unecessary code left out
var productView = new ProductView({sharedEvents:this.sharedEvents});
this.renderView(productView);
this.renderView(new testView({sharedEvents: this.sharedEvents }));
}
renderView: function (view) {
if (null != this.currentView) {
this.currentView.undelegateEvents();
// this.currentView.remove();
}
this.currentView = view;
this.currentView.render();
}
});
I have tried this solution but problem persists, thanks
Try using Backbone.Events' listenTo method instead of the bind method. Then, in your renderView(), call this.currentView.remove instead of this.currentView.undelegateEvents.
Rationale:
I believe in your renderView() method, you are using undelegateEvents() thinking it releases all event listeners created by your view. It only releases events bound on to your view's $el element. However, using remove() on the view releases events bound to the $el as well as events created using this.listenTo() (and this.listenOnce()).
Now once you render another view, the old currentView will be properly released and you'll only get one alert.

Is it possible to trigger an event when initializing a model?

In a backbone model, is it possible to trigger an event in the initialize function, for a nested view? I based my current code off this example: https://stackoverflow.com/a/8523075/2345124 and have updated it for backbone 1.0.0. Here is my initialize function, for a Model:
var Edit = Backbone.Model.extend({
initialize: function() {
this.trigger('marquee:add');
this.on('change', function(){
this.trigger('marquee:add');
});
}
...
}
I'm trying to call a method renderMarquee when the model is initialized:
var EditRow = Backbone.View.extend({
initialize: function() {
this.listenTo(this.model, "change", this.render); // works
this.listenTo(this.model, "marquee:add", this.renderMarquee); // only called when changed, but not when initially created
...
}
renderMarquee IS called when the model is changed, but not when it is initialized. 'change' events work as expected (this.render is called). Any thoughts?
Thanks!
I am currently facing a similar problem. I needed to trigger the change event in the initialize method of my model.
I looked into the backbone code which revealed why this is not happening:
var Model = Backbone.Model = function(attributes, options) {
...
this.set(attrs, options);
this.changed = {};
this.initialize.apply(this, arguments);
};
the set is executed before the initialize and this.change is emptied setting the model state to "nothing has changed".
In order to overwrite behavior this I added the following code to my initialize method.
initialize: function(attributes, options) {
...
this.changed = attributes;
this.trigger('change');
for (attr_name in attributes) {
this.trigger('change:' + attr_name);
}
},
I trigger all change events manually, this is important for me since inheriting models may bind to change or change:attrxy. But this is not enough, because if I just trigger the events the changedAttributes() method would return false therefore I also set this.changed to the current attributes.
This doesn't make a lot of sense because you are initializing the model somewhere prior to doing the view.listenTo call. Unfortunately, you don't really have a choice in that matter.
You are probably going to want to move the event handling to a Backbone.Collection which already has built in events you can listen on for adding/removing.

marionette simple event delegation

I am trying to add a simple event to the children under my compositeview but it is not triggering at all..and frankly I am not sure why, it seems so simple, I could do this just fine with normal backbone.view.
In the example below, the alert is not triggered at all, however when I purposefully change the function name the event binds to , to something else that doesnt exist, it complaints that the function doesnt exist, so I think it's something else...help?
App.View.ContentContainer = Backbone.Marionette.CollectionView.extend({
className:'content_container',
itemView:App.View.ContentBrowseItem,
events:{
'click .browse_item':'focus_content'
},
initialize:function () {
//this.views = {} //indexed by id
//this.create_modal_container()
var coll = this.collection
coll.calculate_size()
coll.sort_by('title', -1)
},
focus_content:function (e) {
alert('here???')
var $modal_container = this.$modal_container
var content_id = $(e.currentTarget).data('content-id')
var $selected_view = this.views[content_id]
var $focused_content = new App.View.FocusedItem({model:$selected_view.model})
$modal_container.empty().show().append($focused_content.el).reveal().bind('reveal:close', function () {
$focused_content.close()
})
return false
},
onShow:function(){
this.$el.addClass('content_container').isotope({
selector:'.content_item',
resizable:true,
layoutMode:'masonry',
masonry:{ columnWidth:64 }
})
}
EDIT: this is the resulting HTML: http://pastebin.com/uW2X8iPp the div.content_container is the resulting el of App.View.ContentContainer
Is .browse_item a selector for the App.View.ContentBrowseItem itemView element? In that case, you need to bind the event in the ItemView definition, not in the CollectionView definition. The reason is that events are bound when a view is rendered. The CollectionView itself is rendered before any of its child itemViews.
Also, if you are opening up another modal view on this click event, I would let the app handle that, rather than your CollectionView
Try something like this:
App.View.ContentBrowseItem = Backbone.Marionette.ItemView.extend({
...
initialize: function() {
// Maintain context on event handlers
_.bindAll(this, "selectItem")
},
events: {
"click" : "selectItem"
}
selectItem: function() {
App.vent.trigger("item:select", this.model);
}
...
});
And to actually show the modal detail view:
App.vent.on("item:select", function(itemModel) {
var detailView = new App.View.FocusedItem({ model: itemModel });
// You may also want to create a region for your modal container.
// It might simplify some of your `$modal_container.empty().show().append(..).etc().etc()
App.modalRegion.show(detailView);
});
Allowing each of your views to handle their own events is part of what makes Backbone and Marionette so beautiful. You'll just want to avoid one view getting all up in another view's business (eg. a CollectionView trying to handle its ItemView's events, an ItemView creating event bindings to show and close a separate modal view, etc.)
Hope this helps!

How to trigger an event from child view to parent view in backbonejs and requirejs

I have a parentview which dynamically creates a child view and I have triggered an event from child view to parent view and I am passing the model of the child view to parent view with some properties set. now in the parent view i have to take the model and add to collection. here is my code
child view trigger code
$(this.el).trigger('added', this.model);
parentview event
events: {
"added": "addNewAttribute"
},
addNewAttribute: function (model) {
console.log(model);
this.collection.add(model);
console.log(this.collection);
}
console.log returns undefined. can someone let me know how to pass the model back to parent view?
thanks
Just faced the same problem!
The thing is that the parent function takes as first parameter the event
and the trigger function passes variables as an array.
so the code should be like:
$(this.el).trigger('added', [this.model]);
parentview event:
events: {
"added": "addNewAttribute"
},
addNewAttribute: function (event,model) {
console.log(model);
this.collection.add(model);
console.log(this.collection);
}
this worked for me.
I may be off base but I'd guess that the child view did not do binding for the function that contains this code:
$(this.el).trigger('added', this.model);
As a result when you get into the function that code is in, "this" doesn't point to the child view, it points to some DOM object or something. Then this.model doesn't point to what you expect.
For example:
initialize : function () {
_.bindAll(this, "addFunction");
}
addFunction : function () {
$(this.el).trigger('added', this.model);
}

Translating the `on` pattern to Backbone's `event` hash

Inside my Backbone views, in the initialize function I do stuff like:
initialize: function () {
$(this.el).on('click', '.button', function () {
$(this).fadeTo(0.5);
}
}
This seems to go against Backbone's convention of using events. Rewriting with the events hash:
events: { 'click .button': 'fadeButton' },
fadeButton: function () {
$(this).fadeTo(0.5);
}
The problem is inside fadeButton's scope the value of this is not the same as when using .on(). What is the correct way of doing this using the events hash?
Like paul said, Backbone automatically sets the context for event callbacks to the view itself. So this in the callback will be the view instance.
So you can get the effect you intend by using the view's scoped selector function...
events: {
'click .button': 'fadeButton'
},
fadeButton: function () {
this.$('.button').fadeTo(0.5);
}
... but if you've got multiple elements with class "button" in your view, that'll fade all of them, in which case you can always use the event object that jQuery gives you to get the event target:
fadeButton: function (event) {
$(event.target).fadeTo(0.5);
}
You defined the events hash correctly.
And for every event handler defined, Backbone automatically sets the context to the view. So this within fadeButton is the view, and you will want to access the view's element.
The code below shows how you need to update the fadeButton function.
fadeButton: function () {
$(this.el).fadeTo(0.5);
}

Resources