I'm new to backbone and I have a collection of objects and a view that displays them as a list. It is set up so that every time an object is added to the collection the view is re-rendered. This is really inefficient because if I add 20 things to the collection all at once, it will be rendered 20 times when it only needs to be rendered one time after the last item has been added. How do I make backbone hold off on rendering until I'm done adding things to the collection?
Here is my code:
<html>
<head>
<meta charset="utf-8" />
<title>Looking At Underscore.js Templates</title>
</head>
<body>
<script type="text/template" class="template">
<ul id="petlist">
<% _.each(model.pets, function(pet) { %>
<li><%- pet.name %> (<%- pet.type %>)</li>
<% }); %>
</ul>
</script>
<div id="container"/>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script type="text/javascript" src="http://underscorejs.org/underscore.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>
<script type="text/javascript">
var serviceData = [
{
name: "Peaches",
type: "dog"
},
{
name: "Wellington",
type: "cat"
},
{
name: "Beefy",
type: "dog"
}
];
$(document).ready(function() {
var Pet = Backbone.Model.extend({
name: null,
type: null
});
var Pets = Backbone.Collection.extend();
var AppView = Backbone.View.extend({
updateUi: function(model) {
_.templateSettings.variable = "model";
var template = _.template($("script.template").html());
var model = { pets: model.collection.toJSON() };
var html = template(model);
$("#container").html(html);
}
});
var pets = new Pets();
var av = new AppView();
pets.bind("add", av.updateUi);
pets.set(serviceData);
});
</script>
</body>
</html>
You could also create an extra method called, lets say, add.
So, if you add a new object, the application just adds a single object instead of rendering the hole collection again.
Somethin like this:
App.Model = Backbone.Model.extend();
App.Collection = Backbone.Collection.extend({
model: App.Model
});
App.CollectionView = Backbone.View.extend({
tagName: 'ul',
render: function(){
this.collection.each(function(model){
var objView = new App.ModelView({ model: model });
this.$el.append( objView.render().el );
});
return this;
},
showAddForm: function(){
// Here you show the form to add another object to the collection
},
save: function(){
// Take form's data into an object/array, then add it to the collection
var formData = {
type: $('select[name=type]').val(),
name $('input[name=name]').val()
};
// Once added to the collection, take the object/array and...
this.addElement(formData);
},
addElement: function(model){
var modView = new App.ModelView({ model: model });
this.$el.append( modView.render().el );
}
});
App.ModelView = Backbone.View.extend({
tagName: 'li',
template: _.template( "<li><%= name %> (<%= type %>)</li>" ),
render: function(){
this.$el.html( this.template( this.model.toJSON() ) );
return this;
}
});
Do you get the idea?
When you render the hole collection, the collectionView render method calls a modelView for each object/pet.
So, this way, when you get the info of a new pet/object you can just create an instance of ModelView, and append it to the actual rendered view.
Hope it helps.
You need to re-factor a couple of things on your code. For your specific case you need to use reset instead of set, to dispatch only one event when data is set. Also you could pass the collection to the view and the view could listen the reset event.
Additionally, to prevent the stress of the browser you could use document.createDocumentFragment that is a DOM holder that speed up the treatment of append. Check point number two for more reference:
http://ozkatz.github.io/avoiding-common-backbonejs-pitfalls.html
<html>
<head>
<meta charset="utf-8" />
<title>Looking At Underscore.js Templates</title>
</head>
<body>
<script type="text/template" class="template">
<ul id="petlist">
<% _.each(model.pets, function(pet) { %>
<li><%- pet.name %> (<%- pet.type %>)</li>
<% }); %>
</ul>
</script>
<div id="container"/>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script type="text/javascript" src="http://underscorejs.org/underscore.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>
<script type="text/javascript">
var serviceData = [
{
name: "Peaches",
type: "dog"
},
{
name: "Wellington",
type: "cat"
},
{
name: "Beefy",
type: "dog"
}
];
$(document).ready(function() {
var Pet = Backbone.Model.extend({
name: null,
type: null
});
var Pets = Backbone.Collection.extend();
var AppView = Backbone.View.extend({
initialize : function(){
this.collection.bind("reset", av.updateUi);
},
updateUi: function() {
$items = document.createDocumentFragment();
this.collection.each(function(model){
var itemView = new ItemView({model : model});
$items.append(itemView.el);
itemView.render();
});
$("#container").html($items);
}
});
var pets = new Pets();
var av = new AppView({collection : pets});
pets.reset(serviceData);
});
</script>
</body>
</html>
You can also do:
pets.set(serviceData, { silent: true });
pets.trigger('add', pets);
And modify av.updateUi to work with whole collection and create the HTML at once, but 'reset' event would be probably more appropriate than 'add' in this case.
Related
i want to pass data from model to view and i want to get this data length in view and make for loop on it but the property of length get undefined and i can't pass data to view there is an error in template html
<html>
<head>
<link href='//fonts.googleapis.com/css?family=Lato:100' rel='stylesheet' type='text/css'>
</head>
<body>
<div id="container">Loading...</div>
<div class="list">
<button id="list">LIST</button>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js" type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min.js" type="text/javascript"></script>
<script type="text/template" id="view_list">
</script>
<script type="text/javascript">
var app = {};
app.postModel = Backbone.Model.extend({
url: 'https://jsonplaceholder.typicode.com/comments',
defaults: {
postId: 0,
id: 0,
email:"",
body:""
}
});
app.viewlist = Backbone.View.extend({
el:$(".list"),
initialize:function(){
this.model = new app.postModel();
},
template: _.template($('#view_list').html()),
events:{
"click #list" : "list"
},
list:function(e)
{
this.model.fetch({
success: function (post) {
console.log(post.toJSON().length);
this.$el.html(this.template(post.toJSON()));
}
});
}
});
app.viewpost = new app.viewlist();
</script>
</body>
and the error in html say
Uncaught TypeError: Cannot read property 'html' of undefined
at success (backbone:49)
at Object.a.success (backbone-min.js:12)
at o (jquery.min.js:2)
at Object.fireWith [as resolveWith] (jquery.min.js:2)
at w (jquery.min.js:4)
at XMLHttpRequest.d (jquery.min.js:4)
Based on the error, looks like you don't have the view within the scope of the success function. This should work:
var view = this;
this.model.fetch({
success: function (post) {
console.log(post.toJSON().length);
view.$el.html(view.template(post.toJSON()));
}
});
Although you should probably think about adding a render function to the view, and possibly having the view listen to model changes in order to trigger it.
initialize: function() {
this.model = new app.postModel();
this.model.on('sync', this.render, this); // Backbone 0.9.2 way
// Backbone 0.9.9+ way
// this.listenTo(this.model, 'sync', this.render);
}
render: function() {
this.$el.html(this.template(this.model.toJSON()));
},
...
list: function(e) {
this.model.fetch();
}
UPDATE
I finally learned Backbone back in late 2017. I'd delete this post but StackOverflow says it's not wise to delete answered questions. Please ignore this question.
I've read countless posts here on StackExchange as well as countless tutorials across the Internet but I seem to be just off from understanding basic Backbone use and implementation.
I'm attempting to build a custom Twitter timeline using pre-filtered JSON that is generated from a PHP file on my work's server.
I feel close but I just can't seem to get things to work. At times I'm able to view 20 tweets in my console but am only able to get 1 tweet to render via my template.
Here is my current Backbone setup:
(function($){
if(!this.hasOwnProperty("app")){ this.app = {}; }
app.global = this;
app.api = {};
app.api.Tweet = Backbone.Model.extend({
defaults: {}
});
app.api.Tweets = Backbone.Collection.extend({
model: usarugby.api.Tweet,
url: "https://custom.path.to/api/tweets/index.php",
parse: function(data){
return data;
}
});
app.api.TweetsView = Backbone.View.extend({
el: $('#tweet-wrap'),
initialize: function(){
_.bindAll(this, 'render');
this.collection = new app.api.Tweets();
this.collection.bind('reset', function(tweets) {
tweets.each(function(){
this.render();
});
});
return this;
},
render: function() {
this.collection.fetch({
success: function(tweets){
var template = _.template($('#tweet-cloud').html());
$(tweets).each(function(i){
$(this).html(template({
'pic': tweets.models[i].attributes.user.profile_image_url,
'text': tweets.models[i].attributes.text,
'meta': tweets.models[i].attributes.created_at
}));
});
$(this.el).append(tweets);
}
});
}
});
new app.api.TweetsView();
}(jQuery));
And here is my current HTML and template:
<div id="header-wrap"></div>
<div id="tweet-wrap"></div>
<script type="text/template" id="tweet-cloud">
<div class="tweet">
<div class="tweet-thumb"><img src="<%= pic %>" /></div>
<div class="tweet-text"><%= text %></div>
<div class="tweet-metadata"><%= meta %></div>
</div>
</script>
<script> if(!window.app) window.app = {}; </script>
I also have a CodePen available for testing. Any advice would be greatly appreciated.
Like the comments suggest, additional reading and code rewrite may be needed. The simplest example for a view rendering multiple views is here adrianmejia's backbone tutorial example.
The snippet below includes an additional view and a couple of added functions along with updating the render and initialize functions. Search for 'cfa' to review changes.
(function($){
if(!this.hasOwnProperty("app")){ this.app = {}; }
app.global = this;
app.api = {};
app.api.Tweet = Backbone.Model.extend({
idAttribute: 'id_str'
});
app.api.Tweets = Backbone.Collection.extend({
model: app.api.Tweet,
url: "https://cdn.usarugby.org/api/tweets/index.php",
parse: function(data){
return data;
}
});
app.api.TweetView = Backbone.View.extend({
tagName: 'div',
template: _.template($('#tweet-cloud').html()),
initialize: function(){
},
render: function(){
var j = {};
j.pic = this.model.get('user').profile_image_url;
j.text = this.model.get('text');
j.meta = this.model.get('meta');
this.$el.html(this.template(j));
return this;
},
});
app.api.TweetsView = Backbone.View.extend({
el: $('#tweet-wrap'),
initialize: function(){
this.collection = new app.api.Tweets();
this.collection.on('reset', this.onReset, this);
this.collection.on('add', this.renderATweet, this);
this.collection.fetch();
},
onReset: function(){
this.$el.html('');
this.collection.each(this.renderATweet, this);
},
renderATweet: function (tweet) {
var tweetView = new app.api.TweetView({ model: tweet });
this.$el.append(tweetView.render().el);
},
});
}(jQuery));
$(document).ready(function(){
new app.api.TweetsView();
});
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://static.usarugby.org/lib.min.js"></script>
<div id="header-wrap"></div>
<div id="tweet-wrap"></div>
<script type="text/template" id="tweet-cloud">
<div class="tweet">
<div class="tweet-thumb"><img src="<%= pic %>" /></div>
<div class="tweet-text">
<%= text %>
</div>
<div class="tweet-metadata">
<%= meta %>
</div>
</div>
</script>
<div id="footer-wrap"></div>
<script>
if(!window.app) window.app = {};
</script>
I'm new to Backbone Framework and this is my first app. I couldn't see any rendering of my app's view in my browser. I've checked the error console and didn't find any errors. Could you guys have a look and help me? I appreciate your time on my behalf and many thanks in advance
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://documentcloud.github.com/underscore/underscore-min.js"></script>
<script src="http://documentcloud.github.com/backbone/backbone-min.js"></script>
<script src="http://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js"></script>
<script id="contactTemplate" type="text/template">
<h1> <%= name %> </h1>
<dl>
<dt> <%= address %> </dt>
<dt> <%= tel %> </dt>
<dt> <%= email %> </dt>
<dt> <%= type %> </dt>
</dl>
</script>
<script>
// Your code goes here
(function ($) {
/* Dummy JSON DataSet */
var contacts = [
{name:"Test1",address:"Test Address",tel:"0123",email:"test#test.com",type:"family"},
{name:"Test2",address:"Test Address",tel:"01234",email:"test#test.com",type:"friends"},
{name:"Test3",address:"Test Address",tel:"012345",email:"test#test.com",type:"office"}
];
/* Defining Model */
var Contact = Backbone.Model.extend({
defaults:{
name:'',
address:'',
tel:'',
email:'',
type:''
}
});
/* Defining Collection (Set of Models) */
var Directory = Backbone.Collection.extend({
model:Contact
});
/* View for rendering indivijual Model*/
var ContactView = Backbone.View.extend({
tagName:'div',
className:'contact-container',
template:$('#contactTemplate').html(),
render:function(){
var tmpl = _.template(this.template);
this.$el.html(tmpl(this.model.toJSON()));
return this;
}
});
/* View for rendering collection of Models */
var DirectoryView = Backbone.View.extend({
el:$("#contacts"),
intialize:function(){
this.collection = new Directory(contacts);
this.render();
},
render:function(){
var that = this;
_.each(this.collection.models, function(item){
this.renderContact(item);
},this);
},
renderContact:function(item){
var contactView = new ContactView({
model:item
});
this.$el.append(contactView.render().el);
}
});
/* Initializing the view */
var directory = new DirectoryView();
} (jQuery));
</script>
</head>
<body>
<div id="contacts">
</div>
</body>
</html>
Beware,
(function ($) {...})(jQuery)
won't guarantee that your code will be executed when the DOM is ready. At the time of rendering DirectoryView, <div id="contacts"> isn't yet available so $el is also undefined.
Putting your script after </body> or into document.ready will solve your problem.
You also have a typo here:
el:$("#contacts"),
intialize: function(){ // <- initialize: function()
this.collection = new Directory(contacts);
this.render();
}
#akoskm's answer is correct, and i changed your code a few look like this:
ContactView = Backbone.View.extend({
tagName: 'div',
className: 'contact-container',
template: $('#contactTemplate').html(),
render: function(){
var tmpl = _.template(this.template,this.model.toJSON());
this.$el.html(tmpl);
return this;
}
});
DirectoryView = Backbone.View.extend({
el: "#contacts",
initialize: function(options){
contacts = options.contacts
this.collection = (new Directory(contacts)).models;
this.render();
},
render: function(){
var that = this;
_.each(this.collection, function(item){
this.renderContact(item);
},this);
},
renderContact: function(item){
var contactView = new ContactView({
model: item
});
this.$el.append(contactView.render().el);
}
});
I have a simple backbone.js example I am working on. The problem is upon page load it is not displaying anything on the page. However, in the Chrome debugger console, if I explicitly make a call to the view and it's render() method then the results show up on the screen with the correct json data.
Any help would be really, really appreciated!
var Clients = Backbone.Collection.extend({
model: Client,
url: 'api/Contacts'
});
var clients = new Clients();
var UserItemView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#contacts-template').html()),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var UserListView = Backbone.View.extend({
el: $('#contacts'),
render: function() {
this.$el.empty();
var self = this;
_.each(this.collection.models, function(model) {
self.renderItem(model);
});
},
renderItem: function(item) {
var itemView = new UserItemView({
model: item
});
this.$el.append(itemView.render().el);
}
});
Here's the code for the index.html page:
<ul id="contacts"></ul>
<script id="contacts-template" type="text/template">
<%= FirstName %> <%= LastName %>
</script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.3/underscore-min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min.js"></script>
<script src="scripts/app/app.js"></script>
<script>
$(document).ready(function () {
alert('HI'); // I verified this alert works
clients.fetch();
var userListView = new UserListView({ collection: clients });
userListView.render();
});
</script>
Every asynchronous call should have a callback when it's done, here you're trying to use clients collection before it has data from the server. I would change the code to:
$(document).ready(function () {
alert('HI'); // I verified this alert works
clients.fetch(
success: function() {
var userListView = new UserListView({ collection: clients });
userListView.render();
},
error: function() {
alert('An error has occurred');
},
);
});
Regards,
I'm trying to display a list of projects using backbone.js.
Basically, backbone should be able to .fetch() the projects into the Projects collection.
This works, as I can tell from the async request which is filled with projects.
But, how do I approach rendering them on page load? There's not much documentation about using the reset() method for 'bootstrapped models'. Any help is appreciated! Thanks.
app.js:
var oldSync = Backbone.sync;
Backbone.sync = function(method, model, success, error){
var newSuccess = function(resp, status, xhr){
if(xhr.statusText === "CREATED"){
var location = xhr.getResponseHeader('Location');
return $.ajax({
url: location,
success: success
});
}
return success(resp);
};
return oldSync(method, model, newSuccess, error);
};
(function($) {
window.Project = Backbone.Model.extend({});
window.Projects = Backbone.Collection.extend({
model: Project,
url: PROJECT_ENDPOINT,
parse: function(data) {
return data.objects;
}
});
window.ProjectView = Backbone.View.extend({
tagName: 'li' ,
className: 'project',
initialize: function() {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
this.projects = new Projects();
this.projects.fetch(function(data) {
console.log("haha");
});
this.template = _.template($('#project-template').html());
},
render: function() {
var renderedContent = this.template(this.model.toJSON());
$(this.el).html(renderedContent);
return this;
}
});
})(jQuery);
Template:
.........
<script>
PROJECT_ENDPOINT = "{% url api_dispatch_list resource_name="project" %}";
</script>
<script type="text/template" charset="utf-8" id="project-template">
<span class="project-title"><%= title %></span>
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>
You can add bootstrapped models to the template:
<script>
PROJECT_ENDPOINT = "{% url api_dispatch_list resource_name="project" %}";
INITIAL_DATA = <%= collection.to_json %>
</script>
And then in your view constructor replace this.projects.fetch(...) with this.projects.reset(INITIAL_DATA)
I like to set up my apps to have some sort of "start" function that i call with the json data for the preloaded items.
function MyApp(){
this.start = function(initialModels){
var myModels = new MyCollection(initialModels);
var modelsView = new MyModelsView({collection: myModels});
modelsView.render();
// ... other startup code here
}
}
and the html page has a script blog that looks something like this:
<script language="javascript">
var myApp = new MyApp();
myApp.start(<%= mymodel.to_json %>)
</script>
hope that helps