Refactor Backbone View code to eliminate repetition - backbone.js

I've created a simple Backbone View for a filter that hides table elements based on which 'input' tags are clicked:
var Filter = Backbone.View.extend({
tagName: "form",
events: {
"click input[name='2013']" : "filter2013",
"click input[name='2012']" : "filter2012"
},
filter2013: function() {
$("tr:contains('2013-')").toggle();
},
filter2012: function() {
$("tr:contains('2012-')").toggle();
},
render: function() {
this.$el.html(JST['acquisitions/filter']);
$("#filter-container").append(this.$el);
return this;
}
});
Not elegant at all, especially if I want to add more 'input' elements, which will result in more custom functions in the View that repeat the code. Perhaps a for loop combined with jquery to find numbers (say 2013, 2012, 2011, and so on) within the webpage and generate events based on that. Or perhaps I'll need to take the events out of the View altogether.
Any suggestions?

I would add a class to the <input>s, say class="year", to make them easier to find and then bind to click events on input.year and grab the year out of the event's currentTarget.name. Something like this:
var Filter = Backbone.View.extend({
// ...
events: {
"click input.year" : "filter_year"
},
filter_year: function(ev) {
$("tr:contains('" + ev.currentTarget.name + "-')").toggle();
},
//...
});

Related

Referring to Current View (this) in BackboneJS Events [duplicate]

I have a div generated by a backbone.js view. When the user clicks on this div, a class active is added to the div and the function addToSet is executed.
Problem: I want another function to be triggered when the View's div has the class active. However, my attempt shown below always cause addToSet function to run when its clicked.
Now, I remove 'click': 'addToSet' from the events function, leaving only 'click .active': 'removeFromSet'. Clicking on the div does not cause anything to happen! Is this because the event handler cannot select the div of the view itself, just the elements inside it?
Any idea how I can solve this problem? Thanks!
JS Code
SetView = Backbone.View.extend({
tagName: 'div',
className: 'modal_addit_set',
template: _.template( $('#tpl_modal_addit_set').html() ),
events: {
'click': 'addToSet',
'click .active': 'removeFromSet'
},
initialize: function(opts) {
this.post_id = opts.post_id;
},
render: function() {
$(this.el).html( this.template( this.model.toJSON() ) );
if(this.model.get('already_added'))
$(this.el).addClass('active');
return this;
},
addToSet: function() {
$.post('api/add_to_set', {
post_id: this.post_id,
set_id: this.model.get('id'),
user_id: $('#user_id').val()
});
},
removeFromSet: function() {
$.post('api/remove_from_set', {
post_id: this.post_id,
set_id: this.model.get('id')
});
}
});
Have you tried to use a :not(.active) selector for one of your event delegates? This may help differentiate between the two scenarios.
Something like this:
events: {
'click :not(.active)' : callback1
'click .active' : callback2
}
These events:
events: {
'click': 'addToSet',
'click .active': 'removeFromSet'
}
don't work and you sort of know why. From the fine manual:
Events are written in the format {"event selector": "callback"}. The callback may be either the name of a method on the view, or a direct function body. Omitting the selector causes the event to be bound to the view's root element (this.el).
So your 'click': 'addToSet' binds addToSet to a click on the view's el itself but 'click .active': 'removeFromSet' binds removeFromSet to a .active element inside the view's el.
I think the easiest solution is to have a single event:
events: {
'click': 'toggleInSet'
}
and then:
toggleInSet: function() {
if(this.$el.hasClass('active')) {
$.post('api/remove_from_set', {
post_id: this.post_id,
set_id: this.model.get('id')
});
}
else {
$.post('api/add_to_set', {
post_id: this.post_id,
set_id: this.model.get('id'),
user_id: $('#user_id').val()
});
}
}
You could use an instance variable instead of a CSS class to control the branching in toggleInSet if that makes more sense.

Is there a way to make a Marionette View listen for events on itself via the events hash?

I'm having trouble figuring out how to quickly, and simply attach a listener for events triggered on a Backbone.Marionette view.
I can currently accomplish what I'm looking for by adding a listener via .on, but is there a quick way via the events or triggers hashes? Something like this seems like it should work but doesn't:
return Marionette.ItemView.extend({
triggers: {
"click .close": "menu:close"
},
events: {
"menu:close #": "close",
},
close: {
// do stuff
}
}
Update
There is actually a (simple) way to do exactly what you want.
// Itemview
var itemView = Marionette.ItemView.extend({
initialize: function() {
Marionette.bindEntityEvents(this, this, this.events);
},
template: "#item",
triggers: {
"click .btn": "menu:performAction"
},
events: {
"menu:performAction": "performAction"
},
performAction: function() {
console.log('test');
}
});
In short this binds your events attribute containing the hashes to the views events.
Fiddle: http://jsfiddle.net/8T68P/
Documentation: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.functions.md#marionettebindentityevents
Old answer
See this fiddle: http://jsfiddle.net/Cardiff/K5TTQ/
Listening to events like that won't work indeed. And if you happen to use the .on method from within your view. Please use listenTo. That will be cleaned up properly when the view is closed. Like this:
// Itemview
var itemView = Marionette.ItemView.extend({
initialize: function() {
var view = this;
view.listenTo(view, "menu:performAction", view.performActionFromListenTo);
},
template: "#item",
triggers: {
"click .btn": "menu:performAction"
},
performActionFromListenTo: function() {
console.log('test');
}
});

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).

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.

How to handle click events of similar but different views?

I'm trying to implement a simple toolbar using Backbone.js. I have the following simple Backbone code:
var Toolbox = Backbone.View.extend({
el: $("#toolbox ul"),
initialize : function() {
_.bindAll(this, "addOne");
toolCollection.each(this.addOne);
},
addOne : function(tool) {
var view = new ToolView({ model: tool });
$(this.el).append(view.render().el);
}
});
// Tool model and collections
var Tool = Backbone.Model.extend();
var toolCollection = new Backbone.Collection([
new Tool({
tool: "toolName1"
}),
new Tool({
tool: "toolName2"
})
]);
// The view of the individual tools
var ToolView = Backbone.View.extend({
tagName: "li",
template : _.template($("#tool-template").html()),
events: {
"click #toolbox ul li a": "toolClick"
},
initialize : function() {
_.bindAll(this, "render", "toolClick");
this.model.view = this;
},
render : function() {
var mj = this.model.toJSON();
$(this.el).html(this.template(mj));
return this;
},
toolClick : function() {
console.log("Tool clicked");
}
});
var tb = new Toolbox;
So, with this, I have a question. I obviously need to handle each click on a tool differently.
When I instantiate my view, can I bind a specific click event to handle the click of that specific tool, and if so, where would I write the click event at? I'm not even sure if I'm missing something here, but could anyone suggest a pattern of how I can have a group of related, but different views and how to handle the click of view separately? Any help would be appreciated. Thanks in advance.
I hope I understood you right.
You have a toolbox with different tools. And of course you have to handle clicks on different tools differently.
So, why don't you have all the click events in the ToolBox view with IDs attached to the tools.
events: {
"click #toolbox #zoom": "zoomClick",
"click #toolbox #pen": "penClick",
"click #toolbox #line": "lineClick"
}
You can have the tools created in the ToolBox render() function. Hope that helps.
Another way to handle this. You are already capturing the click on the individual tools and passing it to the toolclick function. The function is aware of the model that was clicked you could just pass it to a switch statement to create your separate behavior.
toolClick: function() {
var toolname = this.model.get("tool");
switch(toolname) {
case "toolName1":
//Do something;
break;
case "toolName2":
//Do something else;
break;
}
}
This way you don't have to do a bunch of prep in render or templates.
If your tool is the same but does something different than the other tools, then you need to create that separate tool by extending your "vanilla" tool. With extend you can either add new properties and functions or override them entirely.
var ExtendedToolView = ToolView.extend({
toolClick: function() {
console.log("Extended Tool clicked");
}
});

Resources