backbone click event fires as many times as there is views - events

I searched info on this topic but found only info about getting event element.
Yes, I can get an element of clicked div, but why it's fired all 19 times? (it's the number of views total). Info of clicked event is same - of the clicked div.
Here is what divs look like: http://d.pr/i/AbJP
Here is console.log: http://d.pr/i/zncs
Here is the code of index.js
$(function () {
var Table = new Backbone.Collection;
var TrModel = Backbone.Model.extend({
defaults: {
id: '0',
name: 'defaultName'
},
initialize: function () {
this.view = new Tr({model: this, collection: Table});
this.on('destroy', function () {
this.view.remove();
})
}
});
var Tr = Backbone.View.extend({
el: $('.pop-tags').find('.container'),
template: _.template($('#td_template').html()),
events: {
'click .tag': 'clicked'
},
clicked: function (event) {
event.preventDefault();
event.stopPropagation();
console.log(event.currentTarget);
},
initialize: function () {
this.render();
},
render: function () {
this.$el.append(this.template(this.model.attributes));
return this;
}
});
for (var i = 0, size = 19; i < size; i++) {
var trModel = new TrModel({id: i, name: 'name_' + i});
Table.add(trModel);
}
});
How can I avoid all elements from firing an event and fire only one that clicked and only 1 time?

el: $('.pop-tags').find('.container'),
Don't do that. You are attaching every view instance to the same DOM node. Each backbone view needs a distinct DOM node or, as you see, delegate events become complete chaos. In your view, set tagName: 'tr', then when creating your views, create them, call .render() and then append them to the DOM with something like $('.table-where-views-go').append(trView.el);.
You also may want to brush up on the basic MVC concept because Tables and Rows are view-related notions, not model-related, so a class called TrModel is a code smell that you aren't clear on Model vs View.

I would use a slightly different approach to solve your problem.
Instead for one view for every tr I would create one view for the entire table.
When I create the view I would pass the collection containing the 19 models to the view and in view.initialize use the collection to render the rows.
I created a jsbin with a working example.

Related

Kendo UI droppable drop event not firing

I have a simple application that binds to a view model using Knockout JS. It uses a foreach loop that fires the Knockout afterAdd event when a new item is added to the view model. The result is supposed to be a Kendo draggable that can be dropped on a target. For some reason I can't get the drop event on the target to fire.
JSFiddle
<button data-bind="click: $root.add">Add</button>
Drop target
var ViewModel = function () {
this.operations = ko.observableArray([]);
this.add = function () {
this.operations.push("drag");
}.bind(this);
this.bind = function () {
$(".draggable").kendoDraggable({
hint: function (e) {
$("#console").append("<li>firing hint</li>");
return e.clone();
},
});
$(".droptarget").kendoDropTarget({
drop: function (e) {
$("#console").append("<li>firing drop</li>");
}
});
};
};
ko.applyBindings(new ViewModel());
The problem is that you're instantiating the KendoDropTarget widget multiple times. If I click the Add button in your example kendoDropTarget() is invoked three times. If I add a guard against this (see http://jsfiddle.net/tj_vantoll/rk6qwsy4/1/) the drop event works as expected.

How to use SuperScrolloRama with Backbone.js Views

I am trying to understand how to use a plugin like http://johnpolacek.github.io/superscrollorama/, with Backbone.js by integrating it into my Views. I know that I need to hook into the backbone View-Events, but I want to do a horizontal scroll with the plugin, and I don't know of a horizontal scroll-event. How can I still utilize the plugin? Thanks in advance for any ideas.
Views:
var ArtistsView = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.cleanUp();
$("body").attr('id','artists');
this.render();
},
events: {
"click div.open" : "largeArtViewOpen",
"click div.close" : "largeArtViewClose",
},
render: function () {
this.collection.each(function(model) {
var artistView = new ArtistView({ model: model });
this.$el.append(artistView.render().el);
}, this);
console.log('and a new view was rendered!')
return this;
},
cleanUp: function(){
if (this != null) {
this.remove();
this.unbind();
console.log('View was removed!');
}
},
largeArtViewOpen: function(e){
var thisArt = $(e.currentTarget).parent().attr("class");
console.log(thisArt);
$("#open-view, li."+thisArt).show();
},
largeArtViewClose: function(e){
//var thisArt = $(e.currentTarget).parent().attr("class");
console.log('clicked!');
$("#open-view, ul#large li").hide();
},
scrollFx: function(){
var controller = $.superscrollorama({
isVertical:false
});
controller.addTween('h2#fade-it', TweenMax.from( $('h2#fade-it'), .5, {css:{opacity: 0}}), 800);
//$('h2#fade-it').css({'color':'#dbdbdb'});
console.log('scroll message!');
},
});
var ArtistView = Backbone.View.extend({
tagName:'li',
className:'artistLink not-active',
render: function(){
this.id = this.model.get('idWord')+"-menu-item";
this.$el.attr('id', this.id).html(this.template(this.model.toJSON()));
return this;
},
});
So, in the past 3 days since I've asked this question, I've spent some time trying different scrollable 'targets' for Superscrollorama...Document vs. Window vs. Body vs. other DOM elements within the HTML, and the questions that I've had to consider are, should the scroll event be bound to the View's top element? Should it be bound to the body, but initialized in the view? In both cases I tried, I couldn't get the scroll events to continuously fire...this may just be due to bad code, but I couldn't make it happen.
So, what I arrived at, was, avoiding the view entirely: I instantiating and called Superscrollorama in a function called scrollFx() within a separate 'helper.js' document, and then called scrollFx() from my view's router.
I'm thinking I will just empty the target's styles and unbind any existing scroll events in the beginning of scrollFx(), before I call the Superscrollorama function so that the resulting scroll styles/animations are cleaned up, and events aren't exponentially bound.
I'm still very much working through these issues, though now the scroll events are working, so if anyone happens to read through this train of thought, please feel free to add your two sense, especially, if you have better ideas about re-implementing the Superscrollorama function within the View itself.
Thanks.

Backbone.js: Dynamically adding events not firing

I am trying around with Backbone.js and get along so far, but I have got a problem.
Lets say I got a root element and a child element.
When the document loads, I create 3 "root" instances. The root instance appends a tag.
Each root instance creates one child instance which creates a tag in the ul tag.
Now I would like the child instance to attach and onclick event to the tag. Unfortunately, it won't work.
I created a fiddle:
http://jsfiddle.net/Fluxo/sEjE5/17/
var child = Backbone.View.extend({
template: _.template('<li>Item '+count+'</li>'),
events: {
'click li': function() {
alert('listitem Click Child Element');
}
},
initialize: function() {
_.bindAll('render');
this.render();
}, render: function() {
this.$el.html(this.template())
}
});
var root = Backbone.View.extend({
template: _.template('<div><h3>List</h3><p /><ul></ul><hr />'),
events: {
'click li': function() {
alert('listitem Click - Root Element');
}
},
initialize: function() {
_.bindAll('render');
this.render();
},
render: function() {
this.$el.html(this.template());
$('body').append(this.el);
var item = new child();
this.$el.find('ul').append(item.$el.html());
}
});
The events created in the root element will fire, but not the ones in the child element.
Am I doing anything wrong?
You're doing two things wrong.
First of all, your child is an <li>, it doesn't contain an <li>:
template: _.template('<li>Item '+count+'</li>'),
events: {
'click li': ...
},
so your click li event won't do anything. Events are bound to the view's el using delegate:
delegateEvents delegateEvents([events])
Uses jQuery's delegate function to provide declarative callbacks for DOM events within a view. [...] Omitting the selector causes the event to be bound to the view's root element (this.el).
So if you want to bind a click handler directly to the view's el rather than one of its children, you want to leave out the selector:
events: {
'click': ...
}
The next problem is that you're not actually inserting the child element into the DOM, you're copying the HTML:
this.$el.find('ul').append(item.$el.html());
By appending item.$el.html() instead of item.el, you're grabbing the correct HTML as a string and inserting that HTML but you lose the events in the process; the events are bound to the DOM object, item.el, not to the HTML string. You can fix this by appending item.el:
this.$el.find('ul').append(item.el);
// Or you could say, the two approaches are the same
this.$('ul').append(item.el);
Demo: http://jsfiddle.net/ambiguous/K76JM/ (or http://jsfiddle.net/ambiguous/kFxHQ/)

Backbone click event fires events for all collection rather than model

Can't figure out what's wrong. When I click on a model title, it fetches all models in collection at once rather than fetch one model. If I move this event from logView to logsView it works properly but doesn't have access to model, well I can find this model using index or ant other model's ID but don't think this is a nice way.
var Log = Backbone.Model.extend({});
window.LogsList = Backbone.Collection.extend({
model:Log,
url:function (tag) {
this.url = '/logs/' + tag;
return this;
}
});
window.colList = new LogsList();
window.logView = Backbone.View.extend({
el:$('.accordion'),
template:_.template($('#log').html()),
initialize:function () {
this.model.bind('add', this.render, this);
},
events:{
"click .accordion-toggle" :"getLogBody"
},
render:function () {
return this.template(this.model.toJSON());
},
getLogBody:function () {
this.model.fetch();
}
});
window.LogsView = Backbone.View.extend({
el:$("#content"),
initialize:function (options) {
colList.bind('reset', this.addAll, this);
colList.url(options.data).fetch();
},
addOne:function (model) {
var view = new logView({model:model});
$("#accordion").append(view.render());
},
addAll:function () {
colList.each(this.addOne);
}
});
window.listView = new LogsView({data:"Visa_Cl"});
The problem is caused by your el in the LogView: el:$('.accordion')
Backbone's view events are scope to the view's el. In this case, you've specified the view's el as ALL HTML elements with a class of "accordion". Therefore, when you click on any of your HTML elements with this class, the code runs for all of them, which is why you are seeing this behavior.
This article will show you a few options for doing what you want, correctly:
Backbone.js: Getting The Model For A Clicked Element
I would also recommend reading this one, to better understand the use of el in Backbone, and a few of the tricks and traps of it:
Backbone.js: Object Literals, Views Events, jQuery, and el

Backbone.js views & parent elements

Does it make sense to let a backbone.js-view know about it's parent element, when you have a simple view containing very little logic, or is it bad practice?
Like this:
var BooklistRow = Backbone.View.extend({
tagName: "li",
parent: "#booklist",
render: function() {
$(this.el).html("<b>" + this.model.get("title") + "</b>");
$(this.parent).append(this.el);
return this;
}
});
It is better if a view knows nothing outside of itself. This will make it more reusable.
Also in your example, you are adding to the parent on render. At some point, you may want to re-render the BooklistRow after it is already appended to the parent.
I think it is better for the parent to render and add the children:
var Booklist = Backbone.View.extend({
tagName: "ul",
render: function() {
// maybe should remove existing books here first
this.model.each(this.addOneBook);
return this;
},
addOneBook: function(book) {
var view = new BooklistRow({
model: book
});
$(this.el).append(view.render().el);}});
Now, if a single book changes, it can re-render itself without the list even knowing.

Resources