Event delegation failing to attach events in Backbone.js app - backbone.js

My Backbone.js app has a popup editor view that will be repeatedly closed and reopened as the user makes changes. I'm trying to figure out the cleanest way to implement this, and I'm stuck on an event delegation issue.
I believe the problem lies in the way I'm applying the template in my render method. I haven't had any issues with getting events to fire in other views, but those views differed in that they had a model. The view I'm having issues with is more of an application view that contains sub-views, so I'm not sure how to pass the view's context to the MyApp view.
Here's my code:
MyApp = Backbone.View.extend({
tagName: 'div',
template: _.template($('#app-template').html()),
initialize: function() {
_.bindAll(this);
this.render();
},
render: function() {
$('#container').html(this.template);
return this;
},
events: {
"click .save" : "onSaveClicked"
},
onSaveClicked: function () {
console.log("Save clicked.");
this.$el.remove();
}
});
$('#show').click(function () {
var myapp = new MyApp;
});
I've also posted it as a jsFiddle.
I stepped through the Backbone.js source, and it appears that render is called first, then events are assigned, which is what I'd expect. Everything looks OK from what I can tell, but onSaveClicked never fires when I click Save.
The desired functionality is that clicking Show displays the form and Save removes it. If there's a better way to do this that's more inline with Backbone's underlying philosophy I'm open to that as well. You'll notice that I'm nesting an unnamed div inside container, and that's because I wanted to maintain a consistent anchor point for my popup.

The events are bound to the view el, but you never append the el to the DOM. Try
MyApp = Backbone.View.extend({
tagName: 'div',
template: _.template($('#app-template').html()),
initialize: function() {
_.bindAll(this);
this.render();
},
render: function() {
this.$el.html(this.template);
return this;
},
events: {
"click .save" : "onSaveClicked"
},
onSaveClicked: function () {
console.log("Save clicked.");
this.$el.remove();
}
});
$('#show').click(function () {
var myapp = new MyApp;
$("#container").append(myapp.$el);
});
http://jsfiddle.net/WBPqk/18/
Note that in your Fiddle you bound the click event to .save where your template uses a done class.

Related

How to use events on backboneJS views

So I'm trying to learn how to use the backbone events using the documentation
but I'm stuck at the events part, when I click on the page class content it should alert "page tag has been clicked" but it throws an error on the commented line.
<body>
<div class="page"></div>
</body>
<script type="text/javascript">
var View = Backbone.View.extend({
initialize: function()
{
this.render();
},
render: function()
{
this.$el.html('Click me im an element');
},
events: function()
{
//Uncaught SyntaxError: Unexpected token :
"click .page" : "callme"
},
callme: function()
{
alert('page tag has been clicked');
}
});
var view = new View({
el: '.page'
});
</script>
Your events property must be either a hash
events: {
"click .page" : "callme"
}
or a function that returns a hash
events: function() {
return {
"click .page" : "callme"
};
}
You indicate a .page selector but that's your view element. Either use a global selector
events: {
"click " : "callme"
}
or set your el to an ancestor node, for example
var view = new View({
el: 'body'
});
The events is actually just an object it isn't a function. It'll work with the code below. Hope that helps.
events: {
"click .page" : "callme"
}
The other error you have is that you're not actually using the Backbone view.
You create the View correctly but you're not appending it to the view as far as I can tell.
You'd need to do something like.
$('body').html(view.render().el);
That will append your view to the DOM and add all of the event listeners.
Also instead of passing in the el to the BackboneView you could just add the class of page onto the View. Example below.
className: page,
You actually have two problems with your code, the first is that you are using a function for our events hash without returning an object (using a function is fine, but you need to return an object, or bind directly to the events hash). The second is that your are listing for an event for a child element with the page class, which doesn't exist. You want to either remove the class from where you are binding to the event and just listen to a click anywhere in your view, or listen to an existing element.
For example
var View = Backbone.View.extend({
initialize: function()
{
this.render();
},
render: function()
{
this.$el.html('Click me im an element');
},
events: {
"click" : "callme"
},
callme: function()
{
alert('page tag has been clicked');
}
});
var view = new View({
el: '.page'
});
http://jsbin.com/temicema/1/
That should be enough to get your code working, however it is probably worth understanding how backbone events work. When you specify an event in backbone that event is bound to the root el and and then listens to the events you specify matching the selector you specified. Under the hood backbone is basically using jQuery's .on to delegate the events, so in your case backbone is basically doing this.$el.on('click, '.page', this.callme).

listenTo not working when using in another view in backbone.js

I am a newbee to backbone.I have a view called AbcView
abc.js
var AbcView = Backbone.View.extend({
events: {
"click" : "display",
},
display: function(e){
console.log("hello");
alert("click function");
}
});
Now I am passing this abc.js to another xyz.js file and calling it in another view using ListenTo.
xyz.js
var xyzView = Backbone.View.extend({
initialize: function(){
var AbcView = new AbcView ();
this.lisenTo(AbcView, "click",this.display);
},
render: function(){
var html = this.template(AbcView);
this.$el.html(html);
return this;
},
display: function(e){
console.log("parent hello");
alert("parent display function");
}
});
With abc.js click event is triggering fine. But with xyz.js click event is not triggering.
Is this the correct way to call listenTo.
DOM events aren't delegated on the View object.
If you want to emulate this though, you'd have to manually emit the event in ABC display method:
display: function(e){
// Trigger manually
this.trigger("click");
// Your usual code
console.log("hello");
alert("click function");
}
In term of best practice, I'd probably rename "click" to a more descriptive event name.
Backbone's on and listenTo are intended for listening to events on Backbone Models and Collections.
http://backbonejs.org/#Events-catalog
This is an important thing to understand. It is not the same as UI event binding, as described in http://backbonejs.org/#View-delegateEvents.
That being said, you can use something like what Simon suggests to intermingle the two.

Backbone Layout Manager subview's events doesn't work after reload

I'm using the Backbone Layout Manager Boilerplate. Unfortunately, a quite frustrating bug occurred. I like render a list of items as subviews inserted by insertView function. At the first load everthing works fine. But after a reload the the click events doesn't work anymore :(. I already tried to call delegateEvents() on the TableItem View manually but nothing changed. I hope anyone can give me a clue.
App.Views.Item = Backbone.View.extend({
template: "templates/item",
tagName: "li",
events: {
"click .applyButton" : "apply",
"click .viewDetailsButton" : "showDetail"
},
serialize: function() {
return { table : this.model.toJSON() };
},
apply: function(ev) {
ev.preventDefault();
alert("apply button clicked");
},
showDetail: function(ev) {
ev.preventDefault();
var id = this.model.get("_id");
app.router.navigate("#events/"+ id, {trigger : true})
}
});
/*
* List View
*/
App.Views.List = Backbone.View.extend({
template: "templates/list",
tagNam: "ul",
className: "tableList",
beforeRender: function() {
var events = this.model.get("userEvents").get("hosting");
events.each(function(model) {
this.insertView(new App.Views.Item({ model : model }));
}, this);
},
serialize: function() {
return {};
}
});
I think you might want to add a cleanup function on your Item view to undelegate the events when layoutmanager removes the view. I don't know if this will fix your problem, but it seems like good practise.
When you say after a reload, do you mean reloading the page with the browser reload button? if so, how do you get it to work in the first place?
It would help if you could provide a jsfiddle of your setup, or point us to a repo so we can test it on our machines. Make sure you include the router so that we can have a look at how the view and the layout that contains it are initialised.

Display a view using an existing rendered HTML with Backbone Marionette

I am having an application layout like the one attached. The upper panel is already to the page (i.e. in the server's HTML response). While the user interacts with the elements in that panel the content of the dynamic panel below changes accordingly.
I've studied Backbone Marionette various View types and Region Manager. But I still can't figure out a way to implement this. I need to capture events from the already rendered elements and change the dynamic content accordingly. As I understand, every time a region is created to show a specific Marionette view, the region's content is replaced by that view's el. And with that I cannot have a Layout view for the container of the whole thing.
So can this be done in anyway using Marionette?
You can certainly support what I would call a "pre rendered" or partial view. In fact, here's a Marionette View that I use quite a bit, as I'm working under with an app that includes server side partial views:
My.PartialView = Backbone.Marionette.Layout.extend({
render: function () {
//noop
if (this.onRender) {
this.onRender();
}
return this;
},
onShow: function () {
// make sure events are ready
this.delegateEvents();
}
});
It's simple to use:
My.NavBar = My.PartialView.extend({
events: {
"change #search-input": "searchRequested",
"click #faq-link": "faqRequested",
"click #home-link": "homeRequested",
},
searchRequested: function (e) {
// search
},
faqRequested: function (e) {
// show the faq
},
homeRequested:function () {
// go home
}
});
var navbar = new main.views.NavBar({ el: ".my-nav" });
someRegion.show();
// or, just wire up the events manually
navbar.delegateEvents();
I think the better way is using constructor.
Make your rendered layout class.
App.RenderedLayout = Marionette.Layout.extend({
render: function () {
if (this.onRender) {
this.onRender();
}
return this;
},
constructor: function(){
this._ensureElement();
this.bindUIElements();
Marionette.Layout.prototype.constructor.apply(this, slice(arguments));
}
});
Then you can use full of Marionette capabilities.
App.module('Some.Page', function (Mod, App, Backbone, Marionette, $, _) {
Mod.SomeLayout = App.RenderedLayout.extend({
el: '#renderedDiv',
events: {
'click .something': 'onSomethingClick'
},
regions: {
'innerRegion': '#innerRegion'
},
ui: {
something: '.something div'
},
initialize: function () {
},
onSomethingClick: function(e){
return false;
}
});
Mod.addInitializer(function(){
App.addRegions({renderedRegion: '#renderedDiv'});
Mod.someLayout = new Mod.SomeLayout();
App.renderedRegion.attachView(Mod.someLayout);
});
});

Listen to body click inside a view with backbone.js

I'm creating a modal dialog like this
window.NewPageModalView = Backbone.View.extend({
template: _.template($('#view-template-new-page-dialog').html()),
el: $('div#main'),
events: {
'click input[type=radio]': 'newPage'
},
newPage: function (event) {
$(event.currentTarget).closest('form').submit();
},
initialize: function () { },
render: function () {
$(this.el).append(this.template());
return this;
}
});
and then I create it inside another view like this
addPage: function (event) {
event.preventDefault();
var modal = new NewPageModalView();
modal.render();
}
this works just great but what is the best way if I want to close the dialog on body click or when pressing escape?
Generally speaking when you bind events in backbone using the events hash they are delegated to the view's el, however you can still bind events to something else in the initialize method (in your case the body).
initialize: function() {
$('body').bind('click', yourfunction);
}
Edit:
As #muistooshort mentions, you will want to make sure to also unbind the event.

Resources