event not firing in backbone.js view - javascript

This is my view for a single row as "tr". I want want to click on the name cell and pop up a view for that cell. I could not get the event firing..
am I missing something? Thanks!
So this issue is solved by gumballhead, the issue I was having is that there needs to be a tagName associated with the ItemRowView. and then in the render function, I need to do self.$el.html(this.template(model));
Thought it might be helpful to share with..
ItemRowView = Backbone.View.extend({
initialize : function() {
},
template : _.template($('#item-row-template').html()),
render : function() {
var self=this;
var model = this.model.toJSON();
self.$el = this.template(model);
return self.$el;
},
events : {
"click td .item-name" : "viewOneItem"
//Even if I change it to "click td":"viewOneItem", still not firing
},
viewOneItem : function() {
console.log("click");
}
});
collection View:
ItemsView = Backbone.View.extend({
initialize : function() {
},
tagName : "tbody",
render : function() {
var self = this;
this.collection.each(function(i) {
var itemRowView = new ItemRowView({
model : i
});
self.$el.append(itemRowView.render());
});
return self.$el;
}
});
app view:
AppView = Backbone.View.extend({
this.items = new Items();
this.items.fetch();
this.itemsView = new ItemsView({collection:this.items});
$('#items-tbody').html(itemsView.render());
});
for template:
<script type="text/template" id="item-row-template">
<tr>
<td class="item-name">{{name}}</td>
<td>{{description}}</td>
</tr>
</script>
<table>
<thead>
<tr>
<th>name</th>
</tr>
</thead>
<tbody id="items-tbody">
</tbody>
</table>

Use "click td.item-name" for your selector. You are currently listening for clicks on a descendant of td with the class "item-name".
FYI, you've also got a closing tag for an anchor element without an opening tag in your template.
Edit: I think you want self.$el.html(this.template(model)); rather than self.$el = this.template(model);
But there's no need to alias this to self with the code you posted.
Edit 2: Glad you got it sorted out. Let me give you an explanation.
All Backbone Views need a root element. That's the element that the events in the events hash are delegated to on instantiation. When a Backbone View is instantiated without an existing element, it will create one based on configuration settings like tagName, whose default is "div". The element won't appear in the DOM until you explicitly inject it.
So when you set self.$el in your render method, you were overwriting the root element (along with the events, though they would have never fired because it would have listened for a click on a td that was a descendant of a div that didn't exist in the DOM).
As a side note, and it would not be the right way to do it in your case, you could have done this.setElement($(this.template(model)); to redelegate the events from the div created on instantation to the tr created by your original template.

Related

How to wrap a Backbone view element with a div

I am facing issues in adding wrapper div on strut Slidesnapshot which uses Backbone.js.
render: function() {
if (this._slideDrawer) {
this._slideDrawer.dispose();
}
this.$el.addClass('testall');
this.$el.wrap('<div class="check"></div>');
this.$el.html(this._template(this.model.attributes));
.addClass added the class on div but I am not able to wrap the html inside parent div.
A Backbone view represents one DOM element, which is accessible with view.el.
Often, a parent view is rendering the child view before putting its element in the DOM. So the child view wraps itself with a div, but then the parent still uses view.el to get the original element.
Though I strongly suggest rethinking the need to wrap a child div, here's a way to accomplish it with Backbone:
var Child = Backbone.View.extend({
template: "<span>This is the template</span>",
render: function() {
// create a wrapper
var $wrap = $('<div class="check"></div>');
// keep a reference to the original element
this.$innerEl = (this.$innerEl || this.$el).addClass('testall')
.html(this.template);
// wrap the inner element.
$wrap.html(this.$innerEl);
// then replace the view el.
this.setElement($wrap);
return this;
}
});
var Parent = Backbone.View.extend({
el: '#app',
initialize: function() {
this.child = new Child();
},
render: function() {
this.$el.html(this.child.render().el);
return this;
}
});
var parent = new Parent();
parent.render();
parent.render(); // make sure it's idempotent
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<div id="app"></div>
Inspect the result with dev tools to see the wrapping div.
The right way to go about this would be to customize the view element as your wrapper, and adding a child <div> with required classes (current view element) to the template if required. For example:
var V = new Backbone.View.extend({
className: 'check',
render: function(){}
});
This will result in the same DOM structure. You can customize the view element as you wish using View-attributes. This will keep the code more maintainable and less prone to bugs in future. I don't see a reason to hack around.

Marionette: How to prepend a LayoutView as a row into a CompositView table?

I'm building a table with Backbone.js and Marionette.js. My collection of models creates the rows in the table, that get inserted into the <tbody>.
I want to attach 1 single row at the top of the table which is not part of my collection. It's a previously defined LayoutView.
Usually I would just add another region, but because I'm working with a table, I cannot add extra div's to the tbody as the table will spit it out of the table as its not a <tr> within a <tbody>.
Below is my attempt at building this in my onShow function of the CompositView:
var SetAllTargets = Backbone.Marionette.LayoutView.extend({
template: JST["common/cubes/table/set-all-rows"],
tagName: "tr",
className: "set-all-targets"
});
App.Table = Marionette.CompositeView.extend({
template: JST["common/targets/layout"],
className: "targets",
childView: RowTarget,
childViewContainer: "tbody",
regions: {
targetsRegion: ".targets-region"
},
onShow: function() {
var setAllTargetsRow = new SetAllTargets();
var setAllTargetsRegion = "<tr class='target set-all-targets'></tr>";
this.$el.find("tbody").prepend(setAllTargetsRegion);
this.$el.find(".set-all-targets").show(setAllTargetsRow);
}
)};
Another attempt was to put the
<tr class='target set-all-targets'> ...child elements here </tr>
into the template and attach the view.el in my onShow function like this:
onShow: function() {
var setAllTargetsRow = new SetAllTargets();
this.$el.find("tbody").prepend(setAllTargetsRow.el);
}
But that results in just the html <tr class='target set-all-targets'></tr> being placed into the top of the table, but none of the child elements.
You can use the CollectionView.addChild method on your composite to insert something before your collection is rendered:
onBeforeRenderCollection: function() {
this.addChild(null, <View>, 0);
}
See this fiddle for an example of it in action.

Backbone - Access a View's $el.attr outside of the ItemView

I have the following ItemView (there is no model associated with the view, it's a very basic "form" which has a submit or cancel and a single input field):
App.BasicForm = Backbone.Marionette.ItemView.extend({
template: "build/templates/basic-form.html",
tagName: "div",
attributes: {
id: "some-id",
style: "display: none;"
},
events: {
"click button#bf-submit": "bfSubmit",
"click button#bf-close": "bfClose"
},
bfSubmit: function() {
var bfInputField= document.getElementById('bfSomeData').value;
},
bfClose: function() {
this.$el.hide();
}
});
So by default, this view is hidden (but is instantiated when App starts).
I want to have a button which, when clicked, simply changes the attribute style display to block.
I can do this easily like this:
document.getElementById('bfBasicFormDiv').style.display = "block";
However, I'd rather call the view's $el.attr and edit it there, something along the lines of:
App.BasicForm.$el.attr({style: "display: block;"});
However, this returns an undefined, and I can see no way of retrieving the attribute of the View (it's easy with models using .get()) but that doesn't hold for a view.
Thank you for any advice.
Gary
App.BasicForm is not an instance, so it doesn't hold an element. You need to initialize it and you will be able to reference the element with $el:
var basicForm = new App.BasicForm({
el: document.getElementById('bfBasicFormDiv')
});
basicForm.$el.css({display: "block"});

Wiring up Backbone.js view and template for dialog box

In a Backbone.js template, templates/nutrients/show_template.jst.ejs, I have this:
<div id="nutrient_container">
<table class="person_nutrients">
...
<% for(var i=0; i < person[nutrientsToRender]().length; i++) { %>
<% var nutrient = y[x[i]]; %>
<tr class="deficent_nutrients">
<td>
<span class="nutrient_name"><%= I18n.t(nutrient.name) %></span>
</td>
<td><a id="show_synonyms" href="#"><%= I18n.t("Synonyms") %></a></td>
<% } %>
</table>
</div>
Then, in the Backbone.js view, views/nutrients/show_view.js, I have this:
el: 'table.person_nutrients',
parent_el: 'div#nutrient-graphs',
template: JST["backbone/templates/nutrients/show_template"],
initialize: function(options) {
...
this.render()
},
events: {
'click a#show_synonyms':'synonyms_event'
},
render: function() {
...
$(this.parent_el).append(this.template({person: this.model_object, nutrientsToRender: this.nutrientsToRender(), x: x_prep, y: y_prep}))
},
synonyms_event: function(event) {
alert("I got called");
}
Why doesn't the event (the alert box) get triggered? I click the link for "Synonyms" and all I get is the root url with a # after it. Why doesn't the Javascript match up?
I think you are misunderstanding the way a view's el works.
In backbone.js views always reference a DOM element which is its el, this DOM element can either be an existing element in the page's DOM or it can be a new element that does not exist on the DOM.
The way views in backbone.js listen to events is by delegating (binding) them to its el. If a view's el changes, or you want to change what events it listens to you can also manually (re)delegate the events by calling the delegateEvents method
There are two common patterns with views, one is where a view references an existing element on the DOM (this is done by specifying it's el property or passing in an el when it's instantiated), and the second is where the view's el doesn't reference an existing element on the DOM and instead renders some HTML in it's el which is then inserted into the DOM.
Very often what's done is that you have one parent view (often a collections view) which references an existing element on the DOM, and then a bunch of child view's whose el gets appended.
In your case you probably want to split up your view into a parent view that references a higher container div, and have the child views' els appended into it.
For example
<div id="dvContainer">
<div id="synomCont"></div>
<input type="button" id="btnAdd" value="add" />
</div>
var ParentView = Backbone.Views.extend({
el: 'dvContainer',
events: {
"click #add" : "addSyn"
},
addSyn: function() {
//here you can create a model with the approptate data, and pass that in to
// the view
var view = new SynomView();
this.$el.find('#synomCont').append(view.render().el);
}
})

changing backbone views

I have a question about the way backbone handles it views.
Suppose I have the following code:
<div id="container">
<div id="header">
</div>
</div>
After this I change header into a backbone view.
How can I now remove that view from the header div again after I'm done with the view and add ANOTHER view to the same div?
I tried just overwriting the variable the view was stored in. This results in the view being changed to the new one...but it will have all the event handlers of the old one still attached to it.
Thanks in advance!
http://documentcloud.github.com/backbone/#View-setElement
This won't automatically remove the original div - you'll want to do that yourself somehow, but then by using setElement you'll have the view's element set to whatever you passed it.. and all of the events will be attached as appropriate. Then you'll need to append that element wherever it is that it needs to go.
--- Let's try this again ----
So, first thing to keep in mind is that views reference DOM elements.. they aren't super tightly bound. So, you can work directly with the jquery object under $el.
var containerView = new ContainerView();
var headerView = new HeaderView();
var anotherHeaderView = new AnotherHeaderView();
containerView.$el.append(headerView.$el);
containerView.$el.append(anotherHeaderView.$el);
anotherHeaderView.$el.detach();
containerView.$el.prepend(anotherHeaderView.$el);
Or you can create methods to control this for you.
var ContainerView = Backbone.View.extend({
addView: function (view) {
var el = view;
if(el.$el) { //so you can pass in both dom and backbone views
el = el.$el;
}
this.$el.append(el);
}
});
Maybe setting the views by view order?
var ContainerView = Backbone.View.extend({
initialize: function () {
this.types = {};
},
addView: function (view, type) {
var el = view;
if(el.$el) { //so you can pass in both dom and backbone views
el = el.$el;
}
this.types[type] = el;
this.resetViews();
},
removeView: function (type) {
delete this.types[type];
this.resetViews();
},
resetViews: function () {
this.$el.children().detach();
_.each(['main_header', 'sub_header', 'sub_sub_header'], function (typekey) {
if(this.types[typekey]) {
this.$el.append(this.types[typekey]);
}
}, this);
}
});

Categories