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>
Related
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.
Iam learning Backbone js and so I have started creating sample application.
BTW, iam facing one problem now ie., model is saving more than once in my database. I mean when you click 'Create User' , you'll see a form, so when I click that 'Create User' button, details are getting saved more than once in my DB and so all duplicate users info displayed in the home page.
Actually iam trying to practice this video: https://www.youtube.com/watch?v=FZSjvWtUxYk
The output would look like this: http://backbonetutorials.com/videos/beginner/#/new
Here is my Code:
<html>
<head>
<link rel="stylesheet"
href="http://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.1.1/css/bootstrap.min.css">
<script type="text/javascript">
/*$.getJSON('api/users/1',function(data){
console.log(data);
});*/
</script>
</head>
<body>
<div class="container">
<h1> User Manager</h1>
<hr/>
<div class="page"></div>
</div>
<script type="text/template" id="user-list-template">
New User
<hr/>
<table class="table stripped">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
<% _.each(users, function(user){ %>
<tr>
<td><%= user.get('firstName') %></td>
<td><%= user.get('lastName') %></td>
<td><%= user.get('age') %></td>
</tr>
<% }); %>
</tbody>
</table>
</script>
<script type="text/template" id="add-user-template">
<legend>Create User</legend>
<form class="add-user-form">
First Name <input type="text" id="firstName"/><br/>
Last Name <input type="text" id="lastName"/><br/>
Age <input type="text" id="age"/><hr/>
<input type="submit" value="Create User">
</form>
</script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.2/backbone-min.js"></script>
<script type="text/javascript">
var UsersList = Backbone.Collection.extend({
url: 'api/users'
});
var User = Backbone.Model.extend({
urlRoot: 'api/users'
});
var UsersListView = Backbone.View.extend({
el: '.page',
render: function(){
var that = this;
var users = new UsersList();
users.fetch({
success: function(usersList){
var template = _.template($('#user-list-template').html())({users: usersList.models});
that.$el.html(template);
}
});
}
});
var AddUserView = Backbone.View.extend({
el: '.page',
render: function(){
var template = _.template($('#add-user-template').html())({user:null});
this.$el.html(template);
},
events: {
'submit .add-user-form': 'saveOrUpdateUser'
},
saveOrUpdateUser: function(e){
e.preventDefault();
var userDetails = {firstName: $('#firstName').val(), lastName: $('#lastName').val(), age: $('#age').val()};
var user = new User();
user.save(userDetails,{ //SEEMS LIKE HERE HAVING SOME PROBLEM
success: function(){
console.log('INSIDE SUCCESS..');
router.navigate('',{trigger: 'true'});
}
});
}
});
var Router = Backbone.Router.extend({
routes:{
'':'home',
'new':'addUser'
}
});
var router = new Router();
router.on('route:home',function(){
var usersListView = new UsersListView();
usersListView.render();
});
router.on('route:addUser',function(){
var addUserView = new AddUserView();
addUserView.render();
});
Backbone.history.start();
</script>
</body>
</html>
Please suggest me what went wrong and how to fix this?
#MarciaYudkin you'll notice that when you first load your site and create a user, that first user does not save a duplicate. However, the next time you fill out a CreateUser form, the user will get saved twice. What's happening here is that you are suffering from Zombie Views! (Ahh!)
A Zombie View is a view that you thought went away, but in fact remains in the background. Since they're still alive, they are also still bound its view events. The zombie view you detected in your code is:
router.on('route:addUser',function(){
var addUserView = new AddUserView(); // <---Right here!
addUserView.render();
});
Every time a user follows the route:addUser route they end up creating a new AddUserView. This view renders itself and attaches itself to the DOM. You would think that since you "removed" your old view from the DOM, it would simply disappear, right? Well, it does---from the DOM---but not from memory! Since that view still has event bound to the DOM, it does not get garbage collection. When a DOM element that was bound to any previous views is triggered (like by clicking it), the current, as well as all the old, undisposed, views are still bound to it, and all of them respond to the trigger. That's what's happening to you. Here, see this fiddle I cooked up.
How to fix it
The way out of this is to keep a reference to the view around, so that you could properly dispose of it. So, for example, you could attach a reference to the view in your router, and do,
router.on('route:addUser',function(){
// If the view exists, remove it
if (router.addUserView) {
router.addUserView.remove();
router.addUserView.off();
}
router.addUserView = new AddUserView();
router.addUserView.render();
});
Here we call Backbone.View.remove(), which really does this.$el.remove() behind the scences, effectively removing the DOM elements referred to by our view and releasing the bound events. Now our view can be garbage collected!
You can see how I answered this question recently for another angle on this: Saving User Data more than once. And I think I'd be remiss if I didn't include Derick Bailey's seminal article on zombie views (from where I got most of my information), Zombies! RUN! (Managing Page Transitions In Backbone Apps)
i am just writing a simple backbone program. But i am not getting how to fetch data from backbone collection to backbone template. Complete code is written below:
<!doctype html>
<html>
<head>
<title>Backbone tutorial</title>
</head>
<body>
<div class="user">user</div>
<div class="page"></div>
<script type="text/template" id="user-list-template">
I am not able to get data on daya.key
<h1> <%= data.key %> </h1>
</script>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="underscore.js"></script>
<script type="text/javascript" src="backbone.js"></script>
<script>
This is my Collection code
var Album = Backbone.Collection.extend({
url : "/data.json"
});
This is my view code
var UserList= Backbone.View.extend({
el:'.page',
template:_.template($('#user-list-template').html()),
render : function(){
var that=this;
var album= new Album();
album.fetch({
success:function(album){
var _data = {data : album.models} ;
$(that.el).html(that.template(_data));
}
})
}
});
This is my Router code
var Router = Backbone.Router.extend({
routes: {
"": "home" // #help
}
});
var userList= new UserList();
var router = new Router();
router.on('route:home', function(){
userList.render();
});
Backbone.history.start();
</script>
</body>
</html>
and here is the data.json file
{ "key" : "value to print on template "}
Two modifications i would suggest
1.First check the data feild in your template. Since you are fetching data from the collection it will be array of models.
<h1> <%= data[0].key %> </h1>
or you can use a for loop iterating over the collections
2.The data.json file should look like this
[{"key" : "value"}]
Server needs to return a JSON array of model object for collection.fetch() request. So the data.json should look like this:
[{"key":"value to print on template"},{"key":"another value"}]
And try this collection view render implementation:
Model:
var User = new Backbone.Model.extend({});
Collection:
var Album = new Backbone.Collection.extend({
model: User,
url: "/data.json"
});
//create collection instance
var album = new Album();
View:
var UserList= Backbone.View.extend({
el:'.page',
template:_.template($('#user-list-template').html()),
initialize: function(){
//register a collection data add event handler
this.listenTo(album,"add",this.addUser);
//register a collection data remove event handler
this.listenTo(album,"remove",this.removeUser);
album.fetch();
},
render : function(){
},
addUser: function(user){ //apply model data to view template and append to view element
this.$el.append(this.template(user.toJSON()));
},
removeUser: function(user){
//view state update implementation when data have been removed from collection
}
});
Template:
<script type="text/template" id="user-list-template">
<h1><%= key %></h1>
</script>
div.user view will add/remove user-list view dynamically according to collection data manipulation.
Hope this helpful.
i am trying to learn backbone.js ( Backbone.js 1.0.0) this is my sample html page where iam using collection. fetch() method to get the collection,and it is displayed using view .i am getting result in
google chrome,but nothing is displayed in mozilla. i don't know the exact reason.
while i refere to backone site http://backbonejs.org/#Collection-fetch
it is qouted that :
Note that fetch should not be used to populate collections on page load — all models needed at load time should already be bootstrapped in to place. fetch is intended for lazily-loading models for interfaces that are not needed immediately: for example, documents with collections of notes that may be toggled open and closed.
is this is related with my issue?
this is my sample html page
<!DOCTYPE html>
<html>
<head>
<title>Backbone Application</title>
<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/underscore.js" type="text/javascript"></script>
<script src="js/backbone.js" type="text/javascript"></script>
</head>
<body>
<div class="list"></div>
<script id="personTemplate" type="text/template">
<td> <strong><%= name %></strong></td>
<td>(<%= age %>) </td>
<td> <%= occupation %> </td>
</script>
<script type="text/javascript">
//Person Model
var Person = Backbone.Model.extend({
defaults: {
name: 'Guest User',
age: 30,
occupation: 'worker'
}
});
// A List of People
var PeopleCollection = Backbone.Collection.extend({
model: Person,
initialize: function(){
alert("intialise")
},
url:'/RestFul/rest/members/info',
});
// View for all people
var PeopleView = Backbone.View.extend({
tagName: 'table',
render: function(){
this.collection.each(function(person){
var personView = new PersonView({ model: person });
this.$el.append(personView.render().el); // calling render method manually..
}, this);
return this; // returning this for chaining..
}
});
// The View for a Person
var PersonView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#personTemplate').html()),
////////// initialize function is gone from there. So we need to call render method manually now..
render: function(){
this.$el.html( this.template(this.model.toJSON()));
return this; // returning this from render method..
}
});
var peopleCollection = new PeopleCollection();
//peopleCollection.fetch();
peopleCollection.fetch({ success: function () { console.log("collection fetched"); } });
//peopleCollection.fetch({context:collection}).done(function() {
// console.log(this.length)
// })
//console.log(peopleCollection.toJSON())
alert(JSON.stringify(peopleCollection));
var peopleView = new PeopleView({ collection: peopleCollection });
$(document.body).append(peopleView.render().el); // adding people view in DOM
</script>
</body>
</html>
any help will be appreciated
Try with
var fetching = peopleCollection.fetch({ success: function () { console.log("collection fetched"); } });
$.when(fetching).done(function(){
var peopleView = new PeopleView({ collection: peopleCollection });
$(document.body).append(peopleView.render().el); // adding people view in DOM
});
var fetching = peopleCollection.fetch({ success: function () {
var peopleView = new PeopleView({ collection: peopleCollection });
$(document.body).append(peopleView.render().el);
} });
I think we can call the view render inside the success callback
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;
}