I have written the flowing backbone.js program :
<script>
var PostModel = Backbone.Model.extend();
var PostView = Backbone.View.extend({
template: _.template($('#posttemplate').html()),
intialize: function() {
console.log("intializing view");
},
render: function() {
console.log("rendering..");
var htmloutput = this.template(this.model.toJSON());
$('#postcontainer').html(htmloutput);
return this;
}
});
$(document).ready(function() {
var postmodel = new PostModel({title: "hello"});
var postview = new PostView({model: postmodel});
postview.render();
});
</script type="text/javascript">
<script type="text/template" id="posttemplate">
<div> Title: <%= title %> , post: <%= post %> </div>
</script>
<div class="container" id="postcontainer"></div>
when i run the code i get the following error:
Uncaught TypeError: Cannot read property 'replace' of undefined
but it works perfectly fine when i put
template = _.template($('#posttemplate').html()); into the render function.
Your problem is that you're trying to access the template before it exists. The HTML document is parsed from the top to the bottom and when you have
template: _.template($('#posttemplate').html())
then the $('#posttemplate') selector does not return any results because the element containing the template hasn't been parsed yet.
Try moving the
<script type="text/template" id="posttemplate">
<div> Title: <%= title %> , post: <%= post %> </div>
</script>
element up above your first script element.
The reason it works when you put it in the render function is that render is called after the document fires a ready event, at which point the template exists.
Related
I'm using python to create a dummy server storing JSON data. I'm trying to fetch the data to display it in a dashboard. I keep getting
cannot read property html of undefined
and
cannot read property render of undefined
What am I missing?
My backbone script:
// Create a Model
var Dashboard = Backbone.Model.extend({});
// Create a collection
var DashboardCollection = Backbone.Collection.extend({
model: Dashboard,
url: 'http://localhost:8889/api/test'
});
// create an instance of the collection object
var jobList = new DashboardCollection();
jobList.fetch({success:function(){
test.render();
}});
// Create a jobList view
var jobListView= Backbone.View.extend({
el: $('.jobsList'),
template: _.template($('#test-template').html()),
initialize: function(){
this.render();
//this.listenTo(this.model, 'change', this.render);
//this.listenTo(this.model, 'destroy', this.remove);
},
render : function(){
this.$el.html(this.template({'last_name':'test'}));
return this;
}
});
var test = new jobListView;
And my HTML:
<main>
<div class="row">
<div class="left glass">
<!--[if lt IE 9]>
<div class="legacy-ie-fix"></div>
<![endif]-->
<h1>Job List</h1>
<div class ="jobsList">
</div>
</div>
<div class="right glass">
<!--[if lt IE 9]>
<div class="legacy-ie-fix"></div>
<![endif]-->
<h1>Metrics</h1>
<div id="metrics">
<div class="row">
</div>
</div>
</div>
</div>
</main>
</body>
<script type="text/template" id="test-template">
<table class="table striped">
<thead>
<tr>
<th>Data</th>
</tr>
</thead>
<tbody>
<tr>
<td><%= last_name %></td>
</tr>
</tbody>
</table>
</script>
It seems to be an ordering problem.
Make sure the document is ready
If you use jQuery in your script to grab an element from the document (like el: $('.jobsList')), you must ensure that the HTML is ready. You can wrap your code in a jQuery style document ready function:
$(function() {
var JobListView = Backbone.View.extend({
el: $('.jobsList'),
template: _.template($('#test-template').html()),
render: function() {
this.$el.html(this.template({ 'last_name': 'test' }));
return this;
}
});
});
Or just load the scripts at the bottom of the <body> but inside of it.
<script type="text/template" id="test-template">
Put the template above the scripts loading and inside the body.
</script>
<script src="jquery.js">
<script src="underscore.js">
<script src="backbone.js">
<script src="script/my-app.js">
</body>
The order of the <script> tags on the page is important. Backbone requires jQuery and Underscore.js to be loaded before and your own code requires Backbone (and jQuery, but that's already taken care of in the dependency chain).
Declare and assign variable before using them
You call fetch on the collection, which uses the view before it is assigned. While it could work (see var hoisting), it's best to declare and assign variables before using them when possible.
// Create a list view class
var JobListView = Backbone.View.extend({
el: '.jobsList', // no need to use jQuery here.
template: _.template($('#test-template').html()),
render: function() {
this.$el.html(this.template({ 'last_name': 'test' }));
return this;
}
});
// instantiate the view first.
var test = new JobListView();
// then create an instance of the collection object
var jobList = new DashboardCollection();
// and fetch it when everything is ready.
jobList.fetch({
success: function() {
test.render();
}
});
Notice that JS custom types (classes) should be in PascalCase rather than in snakeCase as a generally approved standard, but that's not going to make the code fail.
Pass the element to the view
To be able to easily reuse your views within different views and templates, you should avoid hard-coding the el property.
Instead, pass the element to the view:
var JobListView = Backbone.View.extend({
// ...
});
// ...somewhere else
var view = new JobListView({ el: '.jobsList' });
Or use the element created by a Backbone view.
var JobListView = Backbone.View.extend({
className: 'jobList',
});
// ...inside a parent view's render
var ParentView = Backbone.View.extend({
template: '<div class="job-list-1"></div><div class="job-list-2"></div>',
render: function() {
this.$el.html(this.template);
this.$('.job-list-1').html(new JobListView().render().el);
this.$('.job-list-2').html(new JobListView().render().el);
// ...
return this;
}
});
This would result in:
<div class="job-list-1">
<div class="jobList"></div>
</div>
<div class="job-list-2">
<div class="jobList"></div>
</div>
I am trying to implement the idea of multiple templates per-view in BackboneJs using requireJs and the requiteJs text plugin.
Here is my view code - you can see i have passed in two templates in my define() of witch gets passed through successfully.
define(['Backbone', 'text!Templates/BlogIndex.html', 'text!Templates/Elements/Blog/List.html'], function(Backbone, Template, ElementList){
var BlogPostIndexView = Backbone.View.extend({
initialize: function () {
var $template = $(Template);
$template.prepend(ElementList);
this.template = _.template($template.html());
},
render: function (Template) {
this.$el.html(this.template({posts : this.collection}));
return this;
},
add: function() {
}
});
return BlogPostIndexView; });
You can see i am trying to combine the second template into the html of the first template. This works but unfortunately when i render then i get this....
<div class="outer-wrapper">
<div id="blog-post-wrapper">
<h1>texting views</h1>
</div>
<ul>
<% _.each(posts, function(post){ %>
<li><%= post.title %></li>
<% }); %>
</ul>
I'm missing closing tag for the outer-wrapper, but lets suppose it should be after closing 'ul' tag and your templates looks as follows:
container.html
<div class="outer-wrapper">
<div id="blog-post-wrapper">
<h1>texting views</h1>
</div>
</div>
list.html
<ul>
<% _.each(posts, function(post){ %>
<li><%= post.title %></li>
<% }); %>
</ul>
code:
define([..."container.html", "list.html"...], function (...container, list...) {
...
initialize: function () {
// container:
// no need to compile 'container' if there are no variables..
// list:
this.listTemplate = _.template(list);
}
...
render: function () {
var $container = $(container);
$container.append(this.listTemplate({...}));
this.$el.html($container);
}
BTW: check this !text alternative https://github.com/tbranyen/lodash-template-loader
Say I have a collection of sportsmen. I juste display their names and when there's a click on a name, I'd like to dislay some more details. But that implies I have to retrieve the associate model.
So I have a model, a collection and then two views, one that takes care of rendering each individual name, and the other one manages the list of those names. And then I'll have to create a view to display the details of the clicked sportsman.
But I can't figure out how to retrive the associate model. If I place my event in the view that manages the list, it triggers on click but there's an error saying that the model is undefined.
And if the event listener is located in the view that renders each individual name, it doesn't do anything on click.
Here's my code :
Index.html
<script id="nameListTemplate" type="text/template">
<%= first %> <%= last %>
</script>
<script id="contactTemplate" type="text/template">
<ul>
<li><%= first %></li>
<li><%= last %></li>
<li><%= age %></li>
<li><%= sport %></li>
<li><%= category %></li>
</ul>
</script>
<script src="js/lib/jquery.min.js"></script>
<script src="js/lib/underscore-min.js"></script>
<script src="js/lib/backbone-min.js"></script>
<script src="js/models/sportsManModel.js"></script>
<script src="js/collections/sportsMenCollection.js"></script>
<script src="js/views/nameView.js"></script>
<script src="js/views/nameListView.js"></script>
<script src="js/app.js"></script>
<ul id="sportsMenName"></ul>
<div id="sportsManDetails"></div>
sportsManModel.js
var app = app || {};
app.SportsManModel = Backbone.Model.extend({});
sportsMenCollection.js
var app = app || {};
app.SportsMenCollection = Backbone.Collection.extend({
model: app.SportsManModel
});
nameView.js
var app = app || {};
app.NameView = Backbone.View.extend({
tagName: 'li',
className: 'sportsMan',
template: _.template($('#nameListTemplate').html()),
/*events: {
'click .sportsMan': 'showSportsManDetails'
},
// here it doesn't work at all
showSportsManDetails: function(e){
alert(this.model.get('first'));
},*/
render: function(){
this.$el.append(this.template(this.model.attributes));
return this;
}
});
nameListView.js
var app = app || {};
app.NameListView = Backbone.View.extend({
el: '#sportsMenName',
initialize: function(sportsMen){
this.collection = new app.SportsMenCollection(sportsMen);
this.render();
},
/*events: {
'click .sportsMan': 'showSportsManDetails'
},
// here it says that this.model is undefined
showSportsManDetails: function(e){
alert(this.model.get('first'));
},*/
render: function(){
this.collection.each(function(sportsMen){
this.renderContact(sportsMen);
}, this)
},
renderContact: function(sportsMen){
var nameView = new app.NameView({
model: sportsMen
});
this.$el.append(nameView.render().el);
}
});
The first problem is that you have a view for li.sportsMen element so you don't need to use class .sportsMen in events at all.
And second problem you had wrong method name for click event showSportsManDetails but should be showContactDetail as you have showContactDetail method or rename method name to showSportsManDetails instead.
app.NameView = Backbone.View.extend({
...
events: {
'click': 'showSportsManDetails'
},
showSportsManDetails: function(e){
alert(this.model.get('first'));
},
Here is a working example http://jsbin.com/yutaduvokehu/1/edit
i think the problem may be with the scope , try adding this initalize function in nameView :
initailize : function(){
_.bindAll(this,'showContactDetail');
}
I have been trying to pass a model object to be evaluated in my template but had no luck. I tried the following but had no luck
dashboardmodel.js
var myMod = Backbone.Model.extend({
defaults: {
name: "mo",
age: "10"
}
});
myview.js
var dashView = Backbone.View.extend({
el: '.content-area',
this.mymodel = new myMod({}),
template: _.template(dashBoardTemplate, this.mymodel),
initialize: function() {
},
render: function() {
this.$el.html(this.template);
return this;
}
// more javascript code.............
dahboard.html
<p> Hello <%= name %> </p>
PS: I am using the underscore template engine
In addition , your way to pass a model to a view is not flexible, because you would pass an instance of your model, instead of a default model. Thus, you might want to single out
this.mymodel = new myMod({}),
(btw, above line gives me error message in my chrome browser because of "=" sign)
Then, suppose you have an instance A:
A = new myMod({"name": "World", "age":100})
Then pass it to your view as:
myview = new dashView({mymodel: A})
One more step, you have to do is to call render function:
myview.render();
Here's a complete solution:
<html>
<script src="jquery-1.10.2.min.js"></script>
<script src="underscore-min.js"></script>
<script src="backbone.js"></script>
<body>
<script type="text/template" id="dashBoardTemplate">
<p> Hello <%= name %> </p>
</script>
<div class="content-area">
</div>
<script type="text/javascript">
var myMod = Backbone.Model.extend({
defaults: {
name: "mo",
age: "10"
}
});
var dashView = Backbone.View.extend({
el: '.content-area',
template: _.template($("#dashBoardTemplate").html()),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
mymod = new myMod({"name": "World", "age":100});
myview = new dashView({model:mymod});
myview.render();
</script>
</body>
</html>
If you want to study backone.js, please read this open source book which get me started:
http://addyosmani.github.io/backbone-fundamentals/
You need to get properties of a Backbone Model with the getter syntax, so you need to rewrite your template to:
<p> Hello <%= obj.get('name') %> </p>
Or you need to convert your model into a plain JS object when calling _.template what you can do with the .toJSON() (which creates a clone of the model) or .attributes property:
template: _.template(dashBoardTemplate, this.mymodel.toJSON())
Side note: you should consider to move the template rendering logic into your view. Because your current code render your template when your view is declared and not when you call the render method. So you might get unexpected result. So your code you look like this:
template: _.template(dashBoardTemplate), //only compile the template
render: function() {
this.$el.html(this.template(this.mymodel.toJSON()));
return this;
}
I'm trying to upgrade a Backbone tutorial by Steve Smith's jQuery Mobile version from 1.0 to 1.3.1 - the process doesn't seem as straightforward as I first thought however. I get no errors in the console, but nothing "happens." Everything works fine in 1.0 - however when I upgrade the files to 1.3.1, the data isn't rendered.
JsFiddle
HTML (script imports and css removed):
<title>Exercise</title>
<!-- templates -->
<script type="text/template" id="activity-list-item-template">
<li><%= date %> - <%= type %></li>
</script>
<script type="text/template" id="activity-details-template">
<h3><%= type %></h3>
<ul data-role="listview" id="activitiy-fields" data-inset="true">
<li>Date: <%= date %></li>
<li>Minutes: <%= minutes %></li>
<li>Distance: <%= distance %></li>
<li>Comments: <%= comments %></li>
</ul>
</script>
<body>
<div data-role="page" id="activities">
<div data-role="header">
<h1>Activities</h1>
</div>
<div data-role="content">
<!-- the contents of the list view will be rendered via the backbone view -->
</div>
</div>
<div data-role="page" id="activity-details" data-add-back-btn="true">
<div data-role="header">
<h1>Activity Details</h1>
</div>
<div data-role="content" id="activity-details-content">
<!-- the contents of the list view will be rendered via the backbone view -->
</div>
</div>
</body>
Javascript:
var exercise = {};
(function($){
exercise.Activity = Backbone.Model.extend({
});
exercise.Activities = Backbone.Collection.extend({
model: exercise.Activity,
url: "exercise.json",
comparator: function(activity){
var date = new Date(activity.get('date'));
return date.getTime();
}
});
exercise.ActivityListView = Backbone.View.extend({
tagName: 'ul',
id: 'activities-list',
attributes: {"data-role": 'listview'},
initialize: function() {
this.collection.bind('add', this.render, this);
this.template = _.template($('#activity-list-item-template').html());
},
render: function() {
var container = this.options.viewContainer,
activities = this.collection,
template = this.template,
listView = $(this.el);
$(this.el).empty();
activities.each(function(activity){
var renderedItem = template(activity.toJSON()),
$renderedItem = $(renderedItem); //convert the html into an jQuery object
$renderedItem.jqmData('activityId', activity.get('id')); //set the data on it for use in the click event
$renderedItem.bind('click', function(){
//set the activity id on the page element for use in the details pagebeforeshow event
$('#activity-details').jqmData('activityId', $(this).jqmData('activityId')); //'this' represents the element being clicked
});
listView.append($renderedItem);
});
container.html($(this.el));
container.trigger('create');
return this;
}
});
exercise.ActivityDetailsView = Backbone.View.extend({
//since this template will render inside a div, we don't need to specify a tagname
initialize: function() {
this.template = _.template($('#activity-details-template').html());
},
render: function() {
var container = this.options.viewContainer,
activity = this.model,
renderedContent = this.template(this.model.toJSON());
container.html(renderedContent);
container.trigger('create');
return this;
}
});
exercise.initData = function(){
exercise.activities = new exercise.Activities();
exercise.activities.fetch({async: false}); // use async false to have the app wait for data before rendering the list
};
}(jQuery));
$('#activities').on('pageinit', function(event){
var activitiesListContainer = $('#activities').find(":jqmData(role='content')"),
activitiesListView;
exercise.initData();
activitiesListView = new exercise.ActivityListView({collection: exercise.activities, viewContainer: activitiesListContainer});
activitiesListView.render();
});
$('#activity-details').on('pagebeforeshow', function(){
console.log('activityId: ' + $('#activity-details').jqmData('activityId'));
var activitiesDetailsContainer = $('#activity-details').find(":jqmData(role='content')"),
activityDetailsView,
activityId = $('#activity-details').jqmData('activityId'),
activityModel = exercise.activities.get(activityId);
activityDetailsView = new exercise.ActivityDetailsView({model: activityModel, viewContainer: activitiesDetailsContainer});
activityDetailsView.render();
});
This seems to be an issue that's scuppered a few people. 1.3.0 makes things like the parameter passing plugin break, so there must have been a change in the networking code somewhere.