Can I stop Backbone LayoutManager from generating extra <div>s? - backbone.js

Given these templates:
<script id="template1" type="layout">
Template 1 contents
</script>
<script id="template2 type="layout">
Template 2 contents
<section class="subview"></section>
</script>
And this view setup:
var View1 = Backbone.LayoutView.extend({
template: "#template1"
});
var View2 = Backbone.LayoutView.extend({
template: "#template2",
views: {
".subview": new View1();
}
});
I get this output:
Template 2 contents
<section class="subview"><div>Template 1 contents</div></section>
How can I instead get this output?
Template 2 contents
<section class="subview">Template 1 contents</section>

I would use the following in your template keeping template 1's template the same:
<script id="template2 type="layout">
Template 2 contents
</script>
Then in your view
var View2 = Backbone.LayoutView.extend({
template: "#template2",
beforeRender: function() {
this.insertView(new View1());
}
});
var View1 = Backbone.LayoutView.extend({
template: "#template1",
tagName: "section",
className: "subview"
});
This would give you the following
<div>
Template 2 contents
<section class="subview">Template 1 contents</section>
</div>
I have found no other way of removing the 'div' unless by specifying a tagName attribute.

If template1 had the contents:
<div class="myContents"></div>
then you may use jQuery's unwrap() to remove the element in backbone layout managers afterRender() function
afterRender: function() {
this.getView('.subView').$el.find('.myContents').unwrap();
},

Related

Can't display Backbone/Underscore template in Jade view

I'm trying to link the template in my .jade view to my Backbone model, and it's just not displaying anything inside my template script.
My .jade view:
extends ../layout
block content
.page
script(type="text/template" id="createRecipeTemplate").
<div class="ingredients-pane">
<form id="ingredientForm">
*[form]*
</form>
*[etc.]*
</div>
script(src='/js/myBackboneFile.js')
myBackboneFile.js:
var Recipe = Backbone.Model.extend({
defaults: {*[defaults]*}
});
var Recipes = Backbone.Collection.extend({
url: '/api/recipes'
})
var recipes = new Recipes();
var RecipeView = Backbone.View.extend({
model: new Recipe(),
el: '.page',
initialize: function() {
this.template = _.template($('#createRecipeTemplate').html());
},
events: {
*[events]*
},
*[functions for my events]*,
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var RecipeView = new RecipeView();
Everything's linked up - if I type 'RecipeView' into the browser console, it gives me:
n {cid: "view2", $el: n.fn.init[1], el: div.page}
I've tried a few variations on the jade template syntax - putting pipes at the beginning of each line, using the dot after the script tag (as above), and just indenting normally (with the template in both standard HTML and Jade syntax each time), but no joy.
Am I missing something obvious?

Rendering a template getting the data from a model in Backbone

I'm having some issues with rendering my view in backbone getting the data from my model so I'd appreciate any help. Here goes my html code:
<ul id="datos">
</ul>
<script type="text/template" id="listado-template">
<h2>Mi listado</h2>
<li>Item 1: <%= item1 %></li>
</script>
<script>
modelo = new Modelo();
modeloView = new ModeloView({model: modelo});
</script>
And this are my model and my view:
Modelo = Backbone.Model.extend({
urlRoot: 'myurljson',
});
Backbone.View.extend({
tagName: "ul",
el: "#datos",
template: _.template($('#listado-template').html()),
initialize: function() {
this.model.on('change', this.render, this);
},
render: function() {
this.$el.append(this.template(this.model.toJSON()));
return this;
}
});
The problem is my template is not renderized well. I'm getting an undefined error with item1. But if I delete this tag li, the tag h2 is renderized so I guess the problem is in getting my data from my model through "myurljson". Any help? Thank you!
Underscore will fail if you try to render an undefined variable. See this Fiddle trying to reproduce your problem : http://jsfiddle.net/nikoshr/PELfV/
You can
define a default item1 property in your model wit a sensible value
Modelo = Backbone.Model.extend({
urlRoot: 'myurljson',
defaults: {
item1: ''
}
});
A demo at http://jsfiddle.net/nikoshr/PELfV/1/
or test the value before printing it
<script type="text/template" id="listado-template">
<li>Item 1: <% if (typeof item1!=="undefined") print(item1) %></li>
</script>
And its demo http://jsfiddle.net/nikoshr/PELfV/2/

Why doesn't this.$el.append() work?

I'm trying to follow along http://addyosmani.github.io/backbone-fundamentals. I'm not getting how $el is supposed to work in a view.
Here's my HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Dashboard</title>
</head>
<body>
<h1>Dashboard</h1>
<ol class="foo" id="recent-station">
</ol>
<!-- Templates -->
<script type="text/template" id="station-template">
<li><%= station %></li>
</script>
<!-- Javascript -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone-min.js"></script>
<script src="static/js/script.js"></script>
</body>
</html>
And script.js is:
var RecentStation = Backbone.Model.extend( {
defaults: {
station: "",
},
initialize: function() {
console.log('initialized: ' + JSON.stringify(this));
this.on('change', function() {
console.log('changed: ' + JSON.stringify(this));
})
}
});
var RecentStationView = Backbone.View.extend( {
tagName: 'ol',
id: 'recent-station',
initialize: function() {
this.model.bind('change', _.bind(this.render, this));
},
render: function() {
console.log('render');
this.$el.append('<li>foo</li>');
$('ol#recent-station').append('<li>bar</li>');
return this;
},
});
var recent = new RecentStation();
var recentView = new RecentStationView({model: recent});
recent.set('station', 'My Station');
The interesting stuff is happening in the render function. I can see "render" logged to the console, and the "bar" text gets appended to the node, but not the "foo" text. I thought this.$el and $('ol#recent-station') were the same thing, but obviously not. What am I missing?
If you don't specify a dom element using el attribute, one will be created using tagName,id,className, and attributes from the view.
In your case you don't specify an el attribute in your view so you create an element that looks like:
<ol id='recent-station'></ol>
You then append <li>foo</li> into it, but your view element is still not in the DOM.
$('ol#recent-station') returns the dom element included in your html which is different than your view element, but has the same attributes.
So, in your example you would need to specify an existing element by supplying an el attribute.
var RecentStationView = Backbone.View.extend( {
// remove tagName and id
el:'#recent-station',
/* rest of your code below */
A fiddle with the changes, http://jsfiddle.net/DsRJH/.

Backbone.js view + underscore.js templates

I have a div container element and inside this container element I have multiple templates. I want to selectively load templates based on the json returned from backend. It's a trivial problem but facing issues doing this. Some logical code.
<div id = "container">
<div class = "row page">
<script type = "text/template" id = "template1">
<div id = "template1_id">
</div>
</script>
<script type = "text/template" id = "template2">
<div id = "template2_id">
</div>
</script>
</div>
</div>
In my Backbone View I'm doing something like:
var someView = Backbone.view.extend({
el: '.page'
render: function() {
el: '.page';
var template1 = _.template($('#template1').html());
this.$el.html(template1);
//get Json from backend and render the template within the fetch method
var collection1 = new someCollection();
someCollection.url = "blah";
someCollection.fetch ( function() {
success: function() {
var template2 = _.template($('#template2').html());
$('#template1_id').html(template2);
}
});
},
});
Only the first template is rendered and not the second one. Am I doing something fundamentally wrong here ?
If you are rendering both of the templates into one container, you should be using $.append, rather than $.html, so both will be appended consequently.
you have a typo(mismatched quote)
$('#template1_id").html(template2);
should be
$("#template1_id").html(template2);

Losing Scope when Repeating using View Render in Backbone.js

I'm a newbie at Backbone.js and am coming across a scope issue with a simple view & model scenario.
I've created a simple model with a single default "score" value. I also created a simple view containing a template rendered value of "score" and a button to increment score by one on each press. The view repeats the render every time the score value is changed.
I've got this to work but in a way that I think may be a botch. The template will only render the first time unless I cache the value of "this" in view variable "thisView". If I don't it seems to lose focus and the rendering errors. Is this a good idea? Or am I missing something about repeatedly applying the render.
Thanks for any advice
<!DOCTYPE html>
<html>
<head>
<title>Demo</title>
<style>
#view_container{background-color: rgba(12, 5, 11, 0.14);width: 100px;height: 100px;padding: 10px;}
</style>
</head>
<body>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<script src="http://ajax.cdnjs.com/ajax/libs/underscore.js/1.1.4/underscore-min.js"></script>
<script src="http://ajax.cdnjs.com/ajax/libs/backbone.js/0.3.3/backbone-min.js"></script>
<!-- View Template -->
<script type="text/template" id="view-template">
<div class="profileSpace">
<p>Score: <%= score %></p>
</div>
<button id="increaseScoreButton">Increase Score</button>
</script>
<div id="view_container"></div>
<script type="text/javascript">
(function ($) {
MyModel = Backbone.Model.extend({
defaults:{
score:0
},
initialize: function(){
},
increaseScore: function(){
//Increase Score by 1
var currentScore = this.get("score");
var newScore = currentScore +1;
this.set({score:newScore});
}
});
MyView = Backbone.View.extend({
el: $("#view_container"),
template: _.template($('#view-template').html()),
initialize: function(model){
thisView =this;
this.model.bind('change', this.render, this);
this.render();
},
events: {
"click #increaseScoreButton": "increaseScore"
},
increaseScore: function(){
this.model.increaseScore();
},
render: function(){
var currentScore = thisView.model.get("score");
var html = thisView.template({"score":currentScore});
$(thisView.el).html( html );
return thisView;
}
});
myModel = new MyModel;
myApp = new MyView({model:myModel});
})(jQuery);
</script>
</body>
</html>
You bind the change event via this.model.bind('change', this.render, this);
This syntax was introduced in Backbone 0.5.2 but you use Backbone 0.3.3 in your example.
0.5.2 — July 26, 2011
The bind function can now take an optional third argument to specify the this of the callback function.
Upgrade Backbone to a more recent version (0.9.2 as of today) and you should get the expected behaviour.
Or, as CoryDanielson pointed out in the comments, you could use _.bindAll to have a guaranteed context:
MyView = Backbone.View.extend({
initialize: function(model) {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
this.render();
},
render: function(){
var currentScore = this.model.get("score");
var html = this.template({"score":currentScore});
$(this.el).html( html );
return this;
}
});

Resources