Backbone View: $el not working as expected - javascript

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

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.

How execute a oncomplete script for many divs based on class selector?

I've a webpage that uses Ractive js & tries to execute a script for element's with a particular class. Please find the below sample:
<body>
<div class='container'></div>
<div class='container'>Hello</div>
<script>
var ractive = new Ractive({
// The `el` option can be a node, an ID, or a CSS selector.
el: '.container',
oncomplete: function () {
console.log("22");
}
});
</script>
</body>
For the above code, I can see only one entry in the console. Why is this the case? How do I get ractive to execute the oncomplete script for all elements having container as class?
The problem is that you can't really initiate Ractive with multiple elements. The idea is that you initialize Ractive on the wrapper and then you build up your page with partials.
If you for some reason want to do what you are proposing, you can create multiple Ractive instances on one page like this: http://jsfiddle.net/7yxnd5wb/1/
var ractive = new Ractive({
// The `el` option can be a node, an ID, or a CSS selector.
el: '.c1',
template: 'rendered 1',
onrender: function () {
console.log("11");
}
})
var ractive = new Ractive({
// The `el` option can be a node, an ID, or a CSS selector.
el: '.c2',
template: 'rendered 2',
onrender: function () {
console.log("22");
}
})
I believe the option to use css class selector is a new one and might be confusing. Most people seem to be using ids for this use.
If you really want to create many ractive instances based on class, consider this function: http://jsfiddle.net/7yxnd5wb/2/
function ractive_class (class_selector) {
return $('.' + class_selector)
.toArray()
.map(function (element) {
return new Ractive({
el: element,
template: 'rendered',
onrender: function () {
console.log("rendered");
}
}
)
})
}
console.log(ractive_class('c'))

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