triggering events in multiple views - backbone.js

I'm putting together an app using Backbone.js, which has two Views right now, an IndexView and a QuizPartial. The IndexView renders the bulk of the page (some graphs and whatnot), and it contains many QuizPartials. My issue is that when a user clicks a 'delete' link in one of the partials, the partial should be deleted and the appropriate model destroyed, while the IndexView renders a button to create a new quiz. However, I can't get the IndexView to respond to that event.
Code:
class QuizPartial extends Backbone.View
tagName: "div"
className: "quiz"
events:
"click a.delete": "delete_quiz" # Works fine
initialize: -> #render()
delete_quiz: ->
if confirm "Are you sure you want to delete this test?"
$(#el).remove()
#model.destroy()
false
And then the index view:
class IndexView extends Backbone.View
tagName: "div"
id: "quizzes_index"
events:
"click .quiz a.delete": "render_new_quiz_button" # Never fires
initialize: -> #render()
# etc...
Is there something I should be doing differently?
Thanks!

The actual UI event is done within the quiz view. You correctly remove the element and destroy the model. Now you have two choices:
Have your IndexView listen for the "remove" event on your Quiz collection.
Trigger a new event from your quiz view to notify whoever is listening

Related

Detect click on itemview container in Backbone/Marionette

I have the following item view:
return Marionette.ItemView.extend({
template:tpl,
tagName: 'div',
className: 'v_itv_record_type_item',
events:{
'click #ui.item':'itemClicked'
},
ui:{
item:'.v_itv_record_type_item'
},
itemClicked:function(e){
console.log('clicked');
}
});
that uses the following handlebars template:
<div class="clicktarget">
Stuff Goes Here
</div>
If you click on one of these item views, it does not register the click event. I understand that Backbone restricts access to just the views slice of the DOM, but apparently this does not extend to the containing div itself, even though that containing div is not part of any template, parent view or otherwise.
If we change the ui hash and point item at .clicktarget the click is registered. But this gives me me a <div><div>stuff goes here</div></div> structure for seemingly no reason. Is this the only way to detect a click on the entirety of an item views DOM element?
You can register a click event on the view element by omitting the selector:
events:{
'click' :'itemClicked'
}
Note that if you have an event handler at view level, all the clicks inside the view will bubble up and trigger it's handler, unless it was stopped (event.stopPropagation()) on the way. This is the expected behavior.

Backbone JS view theory

I'm new to Backbone JS, and am having some trouble wrapping my head around a concept.
I have an interface with panels, where one panel is displayed at a time on the screen. Each panel is controlled by its own view, with its own model attached. Now, each panel as an "activator" tab that can be clicked to show the next panel.
In my mind, those tabs are actually sub-views of the parent panel view. Without a panel, the tab shouldn't exist. However, all tabs must appear on the screen at once, so that the user can switch between panels (views). So essentially all panels (their templates, anyway) would be loaded, but hidden until triggered by the click of a tab, at which time its content will be populated or else updated.
My problem, architecturally, comes with binding events to the tab views. So, for example:
window.PanelTabView=Backbone.View.extend({
className: 'view panel-tab-view',
el: '#appPanelTabs',
tagName: 'li',
events: {
'click a': 'test'
},
initialize: function() {
},
render: function(panel) {
this.$el.append(this.template(panel.toJSON()));
},
test: function(x) {
console.log(this.cid);
}
});
So when the tab is clicked, every click event is fired for all tab views.
Maybe I should treat all tabs as a single view, then? But I like the idea of each tab having it's own view from the point of modularity in the template.
Or maybe I'm missing something greater about Backbone and its MVC-esque approach.
What would you do in this scenario?
Why not have a parent view that simply manages the tabs and then simply delegates the click to the tab views? It would be a pretty simple approach.
Alternatively, you could be using a router, and in the router you could create your individual tab views based on a particular route taken. This would allow your page to be linkable.
First Approach:
ParentView = Backbone.View.extend({
el : '#your-tabs',
events : {
'click #tab1' : 'tab1',
'click #tab2' : 'tab2'
},
tab1 : function() {
var t1 = new Tab1();
t1.render();
},
tab2 : function() {
var t2 = new Tab2();
t2.render();
}
});
With a Router:
MyRouter = Backbone.Router.extend({
routes : {
"tab/1" : 'tab1',
"tab/2" : 'tab2'
},
tab1 : function() {
var t1 = new Tab1();
t1.render();
},
tab2 : function() {
var t2 = new Tab2();
t2.render();
}
});
Backbone isn't particularly opinionated about how views are constructed, but these seems to fit into their line of thinking.
Treating all tabs as a single view wouldn't make much sense. It would be silly to re-render all tabs just because the data on one tab changed.
I didn't understand much of your problem but at least I got that you in fact have only 1 button. Therefore you shouldn't have several views (it makes no sense anyway).
What you could do however, is use a "selected" attribute in your models which your views would listen to. So basically, when the user clicks on your button, you'll get the next panel thanks to your collection (I guess), unselect the former panel (set the selected attribute to false), therefore its view would disappear, and select the next, and its view would appear. I'll put some code later if you need.

Tabs in Backbone

I'm new with Backbone and I'm making an example app in which I have to include tabs. The thing is that I have a collection of cities and I want to create one tab for each city (the collection fetchs from the server). I made a view called TabsView, which in the render function passes the collection to a template, and this one loops through the collection and renders the tabs.
What I want to do is that the first tab appears as 'active'. What I've done for the moment is that each tab has a href to a route in the router which changes it's class to active using jquery. Don't know if this is the best way to do this but it works. Maybe there's a better way. Also, when the user clicks a tab, I want to be able to render other view.
Hope I made myself clear. Thanks, cheers,
Martin
Ok I solved this problem doing something like the following:
var Tabs = Backbone.View.extend({
template: JST['tabs'],
events: {
'click li' : 'switchTab'
},
tagName: 'ul',
className: 'nav-tabs',
render: function() {
this.renderTabs();
return this;
},
renderTabs: function() {
this.$el.html(this.template({ cities: this.cities }));
this.$('li:first').addClass('active');
},
switchTab: function(event) {
var selectedTab = event.currentTarget;
this.$('li.active').removeClass('active');
this.$(selectedTab).addClass('active');
}
});
It works fine, maybe it can be improved.

Multiple Views bound to a single element in backbone.js

I am trying to bind two click events to a single HTML element in two different views. One of the views triggers the event, the other does not.
One of the view has body as its el attribute. If I change this view's el to the same element as the other view's, then both events get triggered.
Is this expected? How can I bind click events for the same element in two different views?
Yes, this is expected. Backbone uses jQuery delegates for the event binding. Which means, the event is actually bound to the view's EL, not directly to the child node.
When you say, "the same element", do you mean literally the exact same node in the DOM? Or, do you mean a node with the same selector? I guess I'm not entirely clear.
can i ask why you want to have 2 views binding to the same element?
from my point of view, you should only have 1 view that represents the element itself
and event's bound to an element should be defined in that view only.
you will run into trouble when you are binding click events to elements that don't belong to the view
if you bind trough the delegateEvents hash, these events are contained within the el of the view.
if you are however defining the click yourself, your code becomes less managable.
so, on to what you can do:
events!
you can define 1 view, holding your button and trigger an event when the button is clicked, while other views that need to handle some code when that button is pressed don't bind directly to the button click itself, they can listen to that raised event.
example on jsfiddle:
http://jsfiddle.net/saelfaer/Qck5w/2/
the gist of it in code here:
// an event aggregator object to trigger and bind to
var events = _.extend({}, Backbone.Events),
// two views that talk to each other trough the event aggregator
var myButtonView = Backbone.View.extend({
// first view binds a click event to the button
events: {
"click a" : "myClickEvent"
},
initialize: function(){
_.bindAll(this, "render");
this.render();
},
render: function(){
return this;
},
// click event executes this function, which triggers a custom event on the events object.
myClickEvent: function(e){
$(e.target).blur();
events.trigger("sidebar:myCustomClickEvent");
return false;
}
});
var myPanelView = Backbone.View.extend({
// second view binds to that event, and executes the custom click handler
initialize: function(){
_.bindAll(this, "render", "myClickEventHandler");
events.bind("sidebar:myCustomClickEvent", this.myClickEventHandler);
this.render();
},
render: function(){
return this;
},
// the click handler does some logic (appends div) when the event is raised.
myClickEventHandler: function(){
var txt = $('<div/>').text("you just clicked the button. (bound in other view)");
$(this.el).append(txt);
}
});

event.preventdefault() not working after second render

I'm using brunch to work with backbone and I'm having issues with event.preventdefault(). It works initially, but after a page change it stops working.
I have a view that passes a param to the template and according to that the page is rendered. This page has multiple forms, and loads up one according to the param. All the forms have preventdefault bound to the submit buttons, but for some reason, after I switch forms using one of the nav links, the preventdefault stops working and the form gets posted. Any idea why this is the case? Let me know if you need to see code.
An example of how this happens:
I click 'submit story' nav link and type something and hit submit. I get the js alert and nothing happens. Now I click 'submit poem' and click submit, but this time the form gets posted. If I started off with 'submit poem' it works fine. It also works if I click 'submit poem' and hit refresh before submitting. Weird....
EDIT: Added Code Sample. Template renders acording to the passed in type.
class exports.ClientsSettingsView extends UberView
id: 'settings_view'
className: 'view_container'
events:
'submit #credit_card_form' : 'addCard'
'submit #profile_pic_form' : 'processPicUpload'
'submit #edit_info_form' : 'test'
'click #delete_card' : 'deleteCard'
render: (type="info",status=0) ->
$('.spinner#submit').hide()
#ReadUserInfo()
$(#el).html clientsSettingsTemplate {type,status}
#FadeIn()
#
addCard: (e) ->
e.preventDefault()
$el = $(e.currentTarget)
attrs =
card_number: $el.find('#card_number').val()
card_code: $el.find('#card_code').val()
card_expiration_month: $el.find('#card_expiration_month').val()
card_expiration_year: $el.find('#card_expiration_year').val()
options =
success: (response) ->
alert "Added"
error: (e) ->
alert "Error"
model = new app.models.paymentprofile
model.save attrs, options
console.log attrs
How are you loading the additional forms and submit buttons later on? if you are dynamically pulling them in, your event may no longer be attached. You could try binding your event to the form submit using jquery's live method.
.live()
I had the same problem.
A workaround
is to manually call delegateEvents() after the Backbone.View is added to the DOM.
The issue
I was calling subview.remove() which removes all DOM event listeners (via jQuery.remove) and used that same subview instance later on.
A solution
If a View has to remain active but must be temporary hidden use this.$el.hide() & this.$el.show()
In other situations don't reuse Backbone.View instances, but instantiate new ones.

Resources