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);
}
})
Related
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.
I have defined a View as follows:
var BookView = Backbone.View.extend({
tagName: "div",
className: "bookContainer",
initialize: function() {
this.render();
},
render: function() {
this.$el.html("Hello World"); // Problem is here
return this;
}
});
var bookView = new app.BookView({});
The problem is, Hello World is not getting added in the specified div which I reference using this.$el.
Instead, if I do this(in render() method):
$(".bookContainer").html("Hello World");
it works fine. Why isn't this.$el referencing to $(".bookContainer")?
Note: If I log this.$el, I get following object:
[div.bookContainer, context: div.bookContainer]
You misunderstand how tagName and className work. From the fine manual:
el view.el
[...]
this.el can be resolved from a DOM selector string or an Element; otherwise it will be created from the view's tagName, className, id and attributes properties.
So the tagName and className properties are used to create the el, not find it in the DOM. If you want to bind your view to an existing element, use el:
el: '.bookContainer'
You need to insert bookView into the page.
When you call bookView = new app.BookView({}); you have an instance of a Backbone.View that contains the html of:
<div class="bookContainer">
Hello World
</div>
However, this element has not yet been inserted into the DOM. You still need to do that. As an example:
bookView = new app.BookView({});
// put the bookview element into the dom
$('body').html(bookView.el);
The reference to el is the element of the Backbone.View, documentation for this can be found here
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"});
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.
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);
}
});