in my project i am not able to trigger click event registered in one backbone view from another backbone view. its actually i am having a file type input placed hidden from the user and i need to trigger the file type input.
var FileView = Backbone.View.extend({
....
events : {
"click .delete-image" : "deleteFile",
}
....
});
var FilesView = Backbone.View.extend({
....
events : {
"click #attach" : "attachFile",
},
attachFile : function() {
this.fileView.trigger("click .delete-image");
}
....
});
but i tried like this the event is not get triggered. how is it possible.
the events hash attaches itself to the jquery element that represents the view, not the backbone view itself. So you would most likely have to do something like this:
attachFile : function() {
$('.delete-image', this.fileView.$el).trigger("click");
}
but I would discourage this kind of non-pattern and instead work towards using something we call an Event Aggregation pattern. You can find a collection of really good SO solutions next:
fire an event from one view to another in backbone
Backbone.js global events
Multiple view on same page with backbone.js
Related
Hi I'm learning backbone and I am having trouble with binding events to views. My problem is that I have a view constructor that when called, binds all views to a button press event that is only part of one view. I would like the button press event to be bound to only the 1 view that contains the button.
http://jsbin.com/tunazatu/6/edit?js,console,output
click on all of the view buttons
then click back to view 1
click the red button (all view's models console.log their names)
So I've looked at the code from this post mutliple event firing which shows that you can have multiple views that have the same el thru tagName but map events only to their html elements. This is also what is done in the localtodos example from Jérôme Gravel-Niquet
I have also tried not declaring el /tunazatu/7/edit?js,console,output but then it seems like no event gets bound.
var AppView = Backbone.View.extend({
tagName:"div", //tagName defined
getName:function(){
console.log(this.model.get('name'));
},
initialize:function(options){
this.listenTo(this.model, 'change', this.render);
var temp_mapper = {appView1:'#route1',appView2:'#route2',appView3:'#route3'};
var m_name = this.model.get('name');
this.template = _.template($(temp_mapper[m_name]).html()); //choose the correct template
},
render:function(){
var temp = this.template(this.model.toJSON()); //populate the template with model data
var newElement = this.$el.html(temp); //put it in the view's tagName
$('#content').html(newElement);
},
events:{
"click button":"log"
},
log:function(){
this.getName();
}
});
Your problem is that your AppView really looks like this:
var AppView = Backbone.View.extend({
el: "#content",
//...
Every time you create a new AppView, you bind another event delegator to #content but you never remove those delegations. If you create three AppViews, you end up with three views listening to click button inside #content.
I would recommend two things:
Avoid trying to re-use views, create and destroy them (via View#remove) as needed. Views should be lightweight enough that putting them together and tearing them down should be cheap.
Don't bind multiple views to the same el. Instead, let each view create its own el and then let the caller put that el inside some container.
If you do both of those things then your problem will go away. Your AppView would look more like this:
var AppView = Backbone.View.extend({
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this; // Common practise, you'll see why shortly.
},
// As you already have things...
});
Then your router methods would look more like this:
view1: function() {
if(this.appView)
this.appView.remove();
this.appView = this.createView('appView1');
$('#content').html(this.appView.render().el);
// that `return this` is handy ----------^^
},
If you must stick with your current approach then you'll have to call undelegateEvents on the current AppView before you render another one and delegateEvents on the new AppView after you render it.
But really, don't be afraid to destroy views that you don't need right at this moment: destroy any view that you don't need on the page right now and create new instances when you need them. There are cases where you don't want to destroy your views but you can usually avoid it.
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.
I've got a view which contains several textarea components. The question is how to unbind 'click' event from the textarea that was clicked? Only from the particular one.
var StreamView = Backbone.View.extend({
el: "#stream",
events: {
"click textarea" : "addSendCommentButton"
},
addSendCommentButton : function(event) {
this.undelegateEvents();
}
});
If you want to unbind only a specific event you can use something like this:
addSendCommentButton : function(event) {
this.$el.off('click.delegateEvents' + this.cid, 'textarea');
}
Backbone attach the events using the jQuery on with a specific namespace delegateEvents plus the cid.
I am afraid that this also unbinds the events from other textareas. This is so because the off method needs the same selector that the passed to on as the jQuery documentation says:
To remove specific delegated event handlers, provide a selector
argument. The selector string must exactly match the one passed to
.on() when the event handler was attached.
Suggestion
You can have a similar behaviour changing a little your code:
var StreamView = Backbone.View.extend({
el: "#stream",
events: {
"click textarea.unfinished" : "addSendCommentButton"
},
addSendCommentButton : function(event) {
$(event.target).removeClass("unfinished");
}
});
Use a more specific selector to attach the event and remove that class when the callback is called.
YD1m answer is correct but I want to share with you another way how to do this.
If you want implement one-time event you can use one jQuery method.
You can do this in two ways:
By overriding delegateEvents method
By attaching event after rendering - this.$('textarea').one('click', _.bind(this. addSendCommentButton, this))
Check this:
var StreamView = Backbone.View.extend({
el: "#stream",
events: {
"click textarea" : "addSendCommentButton"
},
addSendCommentButton : function(e) {
e.target.removeEventListener('click', 'addSendCommentButton');
}
});
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!
Say I have a View that displays a search box with a submit button.
When I click on the submit button how do i pass the value of the search box to another view ?
I tried:
In view 1, inside the submit callback : this.trigger('abc', $('#searchBox').val())
In view 2, in the initialize function: this.bind('abc', function(data){ console.log(data); })
but that does not seem to work: the custom event is fired but View 2 does not see it.
Here's a great article by Derick Bailley # LosTechies.com:
References, Routing, And The Event Aggregator: Coordinating Views In Backbone.js
This article discusses a simple solution using PubSub that is built in Backbone.JS. I agree with Derick when he mentions that views should be decoupled.
Unfortunately you can't bind this way - you will need to share a reference to view1 in view2:
var View2 = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'foo');
this.view1.bind('abc', this.foo);
},
foo: function(data) {
console.log(data);
}
});
This also means that at some point you need to set view1 on your instance of View2 so that you can bind against it.
If you don't want to pass the references around, simply bind the two views together in whatever container you are holding them in (i.e. another view or a controller):
var view1 = new View1();
var view2 = new View2();
view1.bind('abc', view2.foo);
I suggest using a PubSub framework in addition to backbone. MinPubSub is a popular choice. I use the jquery pubsub extension extracted from https://github.com/phiggins42/bloody-jquery-plugins.
Then, View 2 doesn't need a reference to View 1. To modify Andrew Hare's example, you would do:
var View2 = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'foo');
$.subscribe('abc', this.foo);
},
foo: function(data) {
console.log(data);
}
});
Then in View 1:
$.publish('abc', $('#searchBox').val());
Of course, with a pubsub system, you will probably want to use something better than "abc", perhaps instead choosing "searchBox:submit" as the topic.