Backbone JS view render not displaying [duplicate] - javascript

This question already has an answer here:
Unable to display Todo Collection on the page
(1 answer)
Closed 8 years ago.
In the below code, unable to render 'TodoList'. Seems like fetching taking time and so displaying '0' and <div id=​"demo">​</div>​ before only.
and Iam not sure why '3' and 'Descriptions' got displayed later. All I need is to display 'Descriptions List' in the page. Iam able to get data from server but somehow not able to display as soon as the data arrived. Please tell me what changes need to do in the below code?
<html>
<head>
<link rel="stylesheet"
href="http://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/2.1.1/css/bootstrap.min.css">
</head>
<body>
<div id="demo"></div>
<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 TodoItem = Backbone.Model.extend({
urlRoot: 'api',
})
var TodoCollection = Backbone.Collection.extend({
model: TodoItem,
url: 'api/todos'
})
var TodoView = Backbone.View.extend({
template: _.template('<h3> ' +'<input type=checkbox ' +'<% if(status === "complete") print("checked") %>/>' +' <%= description %></h3>'),
render: function(){
this.$el.html(this.template(this.model.toJSON()))
}
})
var TodoListView = Backbone.View.extend({
initialize: function(){
this.listenTo(this.collection,'reset',this.render)
this.collection.fetch({reset:true})
},
render: function(){
console.log(this.collection.length)
this.collection.forEach(this.addOne,this)
},
addOne: function(todoItem){
console.log(todoItem.get('description'))
var todoView = new TodoView({model: todoItem})
this.$el.append(todoView.render())
}
})
var todoItem = new TodoItem()
var todoList = new TodoCollection()
var todoListView = new TodoListView({el: '#demo', collection: todoList})
todoListView.render()
console.log(todoListView.el)
</script>
</body>
</html>
Here is the CONSOLE output Iam getting:
0
<div id=​"demo">​</div>​
3
pick up cookies
Milk
Cookies

For starters you might want to take out the {reset: true} from your fetch.
A fetch wil automatically clear the model/collection anyway.
Please also use semicolons at the end of your command, not using them will let the browser interpret where the semicolon should be. This takes time and is error prone (the browser might just place it where you didn't think it would).
if this does not work you might want to do add the fetch into the render doing this:
render: function(){
var that = this;
this.collection.fetch().done(function(data) {
console.log(that.collection.length);
that.collection.forEach(that.addOne,that);
});
},
What also might work, but you need to test this, I personally always use the one above:
render: function(){
this.collection.fetch().done(function(data) {
console.log(this.collection.length);
this.collection.forEach(this.addOne,this);
}, this);
},

and Iam not sure why '3' and 'Descriptions' got displayed later - Because it the result of a async Ajax request.
now, try to change your code (watch comment):
var TodoView = Backbone.View.extend({
template: _.template('<h3> ' +'<input type=checkbox ' +'<% if(status === "complete") print("checked") %>/>' +' <%= description %></h3>'),
clearItem : function(){
this.$el.find("h3").remove();
},
render: function(){
//all DOM manipulation in view
this.$el.append(this.template(this.model.attributes));
return this;
}
})
var TodoListView = Backbone.View.extend({
initialize: function(){
// split "reset" event and "add" event
this.listenTo(this.collection,'reset',this.removeAll);
this.listenTo(this.collection,'add',this.addOne);
this.collection.fetch({reset:true});
},
removeAll : function(){
//method to remove all element from view
//your problem is that this event will fire before ajax request done
console.log("reset!");
var todoView = new TodoView();
todoView.clearItem();
},
addOne: function(todoItem){
//fire when a model in the collection change (automatic after fetch result) for each model.
console.log("add ITEM:",todoItem);
var todoView = new TodoView({model: todoItem})
todoView.render();
}
});
NOTE: Remove todoListView.render() in your code.
Sorry but, my english is too bad. I do not have time to explain better. Try if my code work
EVENTS in backbone: http://backbonejs.org/#Events
FETCH in collection: http://backbonejs.org/#Collection-fetch
RENDER a view: http://backbonejs.org/#View-render

Related

Backbonejs collection not populated, but fetch works

Heres what I've been working on: http://jsfiddle.net/leapin_leprechaun/29aysou5/3/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Results App Training</title>
<script src="js/libs/jquery.js"></script>
<script src="js/libs/underscore.js"></script>
<script src="js/libs/backbone.js"></script>
<script type="text/javascript">
$( document ).ready(function() {
/*
***********
Models
***********
*/
var MatchInfo = Backbone.Model.extend({
defaults: {
season:"1415"
}
});//model class
var matchInfo = new MatchInfo(); //model instance
/*
***********
Collections
***********
*/
var Matches = Backbone.Collection.extend({
model: MatchInfo, //note this references the model class, not the model instance
url : "http://www.hookhockey.com/index.php/temp-gillian/",
sync : function(method, collection, options) {
// By setting the dataType to "jsonp", jQuery creates a function
// and adds it as a callback parameter to the request, e.g.:
// [url]&callback=jQuery19104472605645155031_1373700330157&q=bananarama
// If you want another name for the callback, also specify the
// jsonpCallback option.
// After this function is called (by the JSONP response), the script tag
// is removed and the parse method is called, just as it would be
// when AJAX was used.
//console.log('sync');
options.dataType = "jsonp";
return Backbone.sync(method, collection, options);
},
parse : function(response) {
// console.log(response.matches);
//.matches is what the json at http://www.hookhockey.com/index.php/temp-gillian/ is putting out
return response.matches;
}
}); //collection class
var matches = new Matches(); //collection instance
matches.bind("sync", matches.render, matches);
matches.fetch({
success : function(collection, response, options) {
/* notes: calling these outside of the success listener meant that nothing got returned. This is because they fire before the fetch returns http://stackoverflow.com/questions/9431673/load-data-into-a-backbone-collection-from-json-file
the alternative is to call them within the success function or to call them like so:
.complete(function() {
console.log(matches);
console.log('length: ' + matches.length);
});
..after the fetch call.
*/
console.log('in collection instance fetch success: ' + matches.length);
return response;
},
error : function(collection, response, options) {
console.log(response.statusText);
},
// A timeout is the only way to get an error event for JSONP calls!
timeout : 5000
});
/*
***********
Views
***********
*/
var MatchModelView = Backbone.View.extend({
// template: _.template( $("#matchTemplate").html() ), // removed because template was not being found - uses underscore and the content from index.html script tag with the id of matchElement that contains the template tags
id : 'someID',
className: 'someClassName',
initialize: function () {
_.bindAll(this, "render");
this.collection.bind("reset", this.render);
},
render: function() {
//var matchTemplate = this.template(this.model.toJSON()); //passes in all of the model data (using this.model.toJSON()) into the template (this.template) so that info is available to the template tags
var matchTemplate = '<p>' + this.model.get('title') + '</p>';
this.$el.html(matchTemplate); //pass the templated info into the el element and return it for render
return this;
}
}); //model view class
//var matchModelView = new MatchModelView({model:matchInfo}); //model view instance
//console.log(matchModelView.render().el);
var MatchesModelView = Backbone.View.extend({
id: 'somethingelse',
initialize: function () {
_.bindAll(this, "render");
this.collection.bind("reset", this.render);
},
render: function(){
console.log('collection length in view:' + this.collection.length); //returns 0
this.collection.each(this.oneMatch, this);
return this;
},
oneMatch: function (aMatch){
console.log(aMatch);
var matchView = new MatchModelView ({ model: aMatch });
this.$el.append(MatchView.render().el);
}
}); //collection view class
var matchesModelView = new MatchesModelView({collection: matches });
$("#allMatches").html(matchesModelView.render().el);
/*
***********
Routers
***********
*/
}); //end doc ready
</script>
</head>
<body>
<div class="site">
<div id="allMatches">adasdasd</div>
<div id="copy"></div>
</div>
<script id="matchTemplate" type="text/template">
<%= title %>
</script>
</body>
</html>
I've just put 2 alerts in there to show where I think my issue is. I can see that the call to json is working and returning items. But at the time the view kicks in I suspect the call hasn't gone through fully.
Is it bad practice to call a view from within the .fetch success callback? Am I losing the whole modularity advantage of backbone by doing that? Or am I missing something to get the returned objects into the collection?
I'm new to Backbone so you can ignore all my comments within the code, just trying to keep track!! :) I realise they should all be separated out into different js files too, am just getting to grips with things first.
Thanks for your time!
I see a lot of good things, here. You're embracing the Backbone event-driven model (by rendering on sync, for example) and you're off to a good start.
Your problem is that you're calling render on the collection, and not the view, in your sync callback.
matches.bind("sync", matches.render, matches);
You want to move
matches.bind("sync", matches.render, matches);
matches.fetch({ ... });
until after you've instantiated your view. So, you'd do:
var matchesModelView = new MatchesModelView({collection: matches });
matches.bind("sync", matchesModelView.render, matches);
matches.fetch({ ... });
and notice that I replaced matches.render with matchesModelView.render as the callback of the sync event.

backbone paginator multi model in a view

I have a shopping cart app made with Backbone.Paginator.Fluenced and forked with this example; https://github.com/msurguy/laravel-backbone-pagination
I made some small changes;
when you click over an item link, it opens a bootstrap modal window.
The code is below.
app.views.ItemView = Backbone.View.extend({
tagName: 'div',
className: 'col-sm-4 col-lg-4 col-md-4',
template: _.template($('#ProductItemTemplate').html()),
events: {
'click a.openModal': 'openModal'
},
initialize: function() {
this.model.bind('change', this.render, this);
this.model.bind('remove', this.remove, this);
},
render : function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
openModal : function () {
var view = new app.views.ModalView({model:this.model});
view.render();
}
});
and this is my ModalView to show product details in a modal window.
app.views.ModalView = Backbone.View.extend({
template: _.template($('#modal-bsbb').html()),
initialize: function() {
_.bind(this.render, this);
},
render: function () {
$('#myModalPop').modal({backdrop: 'static',keyboard: true});
$('#myModalPop').html(this.template({
'model':this.model.toJSON()
}));
return this;
}
});
Everything is fine for above codes.
I decided to optimize this code and wanted some improvements on this.
Firstly I am fetching all product data and send these data to modal windows.
I think i must send only main meta data and must fetch details from these window.
So i made a new Backbone Model and Collection;
app.models.ItemDetails = Backbone.Model.extend({});
app.collections.ItemDetails = Backbone.Collection.extend({
model: app.models.ItemDetails,
dataType: 'json',
url : "/api/item-details",
parse: function(response){
return response.data;
}
});
My api returns JSON :
{"data":{"id":8,"title":"Product 8","seo":"product-8","code":"p8","review":"Lorem30"}}
My problem is adding multiple models to ModalView;
I tried a lot of example and questions in blogs&forums couldnt find any solve.
I tried a lot of things ($.extend, to set model and model vs..)
to change ModalView and below codes are last position of them;
app.views.ModalView = Backbone.View.extend({
template: _.template($('#modal-bsbb').html()),
initialize: function() {
_.bind(this.render, this);
},
render: function () {
var itemDetails = new app.collections.ItemDetails(); // this is new line
var model2 = itemDetails.fetch(); // this is new line
$('#myModalPop').modal({backdrop: 'static',keyboard: true});
$('#myModalPop').html(this.template({
'model1':this.model.toJSON(),
'model2':model2.model // this is new line
}));
return this;
}
});
I want to add a second model to my underscore template. But cant!
Firstly when i run below codes on chrome developer console it gets an Object;
but couldnt convert as a new model or JSON.
var itemDetails = new app.collections.ItemDetails();
var model2 = itemDetails.fetch();
console.log(model2); // gets fetch data as an object
I am afraid I am confused about where the problem exactly is.
Sorry guys I am not a backbone expert and probably I am doing something wrong though I searched a lot about it on the forum. I read about it again and again but I could not solve the problem. Could you please help me. Thank you in advance.
SOLVE:
After searchs and by the help of below reply.
I solved my problem.
app.views.ModalView = Backbone.View.extend({
template: _.template($('#modal-bsbb').html()),
initialize: function() {
_.bind(this.render, this);
},
render: function () {
var _thisView = this;
var itemsDetails = new app.collections.ItemsDetails();
itemsDetails.fetch({
success:function(data){
$('#myModalPop').modal({backdrop: 'static',keyboard: true})
.html(_thisView.template({
'model1':_thisView.model.toJSON(),
'model2':data.at(0).toJSON()
}));
}});
}
});
Every request to server using backbone is async, it means that you will not have the returned data immediately after the request, maybe the server still processing the data.
To solve this problem you have 2 ways.
First Way: Callbacks
Inside your Model/Collection
GetSomeData:->
#fetch(
success:=(data)>
console.log data // the returned data from server will be avaiable.
)
Second way: Listen for an trigger.
This one it's more elegant using backbone because you don't write callbacks.
Inside Model
GetSomeData:->
#fecth()
Inside View
initialize:->
#model = new KindOfModel()
#model.on "sync", #render, #
backbone automatically will trigger some events for you, take a read here.
http://backbonejs.org/#Events
As you're already doing, you'll need to listen to some trigger on the collection too
var itemDetails = new app.collections.ItemDetails(); // this is new line
var model2 = itemDetails.fetch(); // here is the problem

How to load Collection of models in Backbone View?

I'm trying to learn Backbone.js and for this I now want to load a collection of models into a view. By opening a tab in the window, I first add the following template:
<script type="text/template" id="tab-content-template">
<div class="conversation-window" id="conversation<%= ticketId %>"></div>
</script>
In this tempalte I now want to load a Collection of messages belonging to the ticketId. So I made a collection like this:
var MessageCollection = Backbone.Collection.extend({
url: 'ticket/:id/messages'
});
and a view:
var MessageView = Backbone.View.extend({
initialize: function(models, options) {
this.ticketId = options.ticketId;
},
el: function() {
return '#conversation' + this.ticketId;
},
className: 'user-message'
});
So I want the list of messages to be inserted within the #conversation1 (for ticketId 1). I then tried running this:
var messageView = new MessageView(messageCollection, {ticketId: 1});
messageView.render();
console.log(messageView);
Unfortunately nothing happens, and when I look into the console I see that ticketId: 1 but that el: undefined. I'm kinda lost in what I'm doing wrong (kinda lost in Backbone in general).
Does anybody know what I'm doing wrong here and how I can solve it? All tips are welcome!
I think this is what you want:
<div id = "conversation1">stuff insert here</div>
<script>
var MessageCollection = Backbone.Collection.extend({
// url: 'ticket/:id/messages' //<-- put this in router
});
// you need to create an instance of you collection somewhere and pass in
// models as parameter. note that your ticketId is included in the models:
var messageCollection = new MessageCollection([{ticketId:1,attr:'stuff1'}, {ticketId:2,attr:'stuff1'}])
var MessageView = Backbone.View.extend({
initialize: function(models, options) {
this.ticketId_1 = this.collection.models[0].get('ticketId');
this.ticketId_2 = this.collection.models[1].get('ticketId');
},
// can not reference el and define a <div id="user-message"> simultaneously
/*
el: function() {
return '#conversation' + this.ticketId;
},
*/
className: 'user-message',
render: function() {
$('#conversation'+ this.ticketId_1).html(this.$el.html('stuff from model goes in here'));
}
});
// var messageView = new MessageView( messageCollection, {ticketId: 1});
// the above wouldn't work. your ticketId should be passed into your view via model
// then you associate you view to your collection like so:
var messageView = new MessageView({ collection: messageCollection });
messageView.render();
console.log(messageView.$el.html());
</script>

backbonejs beginners issue - fetching and displaying json data

Im new to Backbone (dont hate me) but am pulling my hair out trying to do a very simple thing.
Im loading a json file (correctly as I can see it loading in firebug) and I just want to pull some info from it purely for testing (as its my first backbone code)
However, I cant get this working and end up with one blank li tag (code below)
<ul id="phones"></ul>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.2/underscore-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.rc.1/handlebars.min.js"></script>
<script id="foo" type="text/template">
<li><%= name %></li>
</script>
<script>
var Phones = Backbone.Collection.extend({
url:'http://backbone.local/phones/phones.json'
})
var PhonesView = Backbone.View.extend({
el:'#phones',
initialize:function(){
this.collection = new Phones();
this.collection.fetch();
this.render();
},
template:_.template($('#foo').html()),
render:function(){
var foo = this.collection.toJSON();
$(this.el).html(this.template(foo));
return this;
}
})
var phonesView = new PhonesView();
</script>
Any pointers greatly appreciated.
Cheers,
UPDATE 1
I thought it may be due to fetch being async so i called render in success callback of fetch as below. The console.log fires fine but still no json data in rendered html (i also changed to using handlebars)
<script>
var Phones = Backbone.Collection.extend({
url:'http://backbone.local/phones/phones.json'
})
var PhonesView = Backbone.View.extend({
el:'#phones',
initialize:function(){
var self = this;
this.collection = new Phones();
this.collection.fetch({
success:function(){
console.log('json loaded');
self.render();
}
});
},
template: Handlebars.compile('<li>sdsadsadsadsad {{name}} dsfcdfd</li>'),
render:function(){
var foo = this.collection.toJSON();
$(this.el).html(this.template(foo));
return this;
}
})
var phonesView = new PhonesView();
</script>
With Handlebars, a collection template looks like this:
{{#items}} <li> {{name}} </li> {{/items}}
You also need to wrap your collection JSON in an items object so that the Handlebars template can reference it as above:
var foo = { items: this.collection.toJSON() };
Edit
There's actually one more issue ... collection.toJSON() doesn't convert each model to JSON. So you need to write:
this.collection.models.map(function(x) { return x.toJSON(); });
Fiddle Demo
On your view:
'initialize': function() {
this.template = _.template('<p><% model.text $></p>');
this.collection.fetch({error: function() { console.log(arguments); }, 'success': _.bind(this.onFetch,this) });
return this;
},
'onFetch': function(collection) {
this.collection.on("add", this.onAdd, this);
this.collection.each( _.bind(this.onAdd, this) );
return this;
},
'onAdd': function(model){
//do something like:
this.$el.find('.items').append(this.template({model: model}) )
);
To answer your explicit question of why you only get an empty li. You must send the template some name data. For example:
render:function(){
var firstModel = this.collection.at(0);
var firstName = firstModel.get("name");
$(this.el).html(this.template({name: firstName}));
return this;
}
Of course, the above code is only to understand what's missing, and not how a backbone application should be implemented. I really recommend that you go over the annotated TODO example linked from Backbone's website, and understand the basic patterns that are implemented there.
Update based on your comment:
Different ways of solving this. Really recommend reading: http://backbonejs.org/docs/todos.html
To continue on the "hacky path" of solving this so you can see something:
addOne: function(phone) {
this.$el.append(this.template(phone.toJSON()));
return this;
},
render: function() {
this.$el.html(); //clear everything in the view
this.collection.forEach(_.bind(this.addOne,this)); //Will call addOne for every model in the collection.
return this;
}

Backbone basic app, is this how it should be done?

Hope you can have a quick look at what I'm doing here. Essentially, am I doing it right?
Live demo of it here too: http://littlejim.co.uk/code/backbone/messing-around/
I just wanted to get a solid understanding in Backbone before I go too wild. So this is a simple demonstration of creating a collection from a JSON object, passing it to a view and handling simple events. But am I approaching this right? What can I do that's better?
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Showing a simple view with events</title>
<script type="text/javascript" src="../../media/scripts/jquery-1.5.1.min.js"></script>
<script type="text/javascript" src="../../media/scripts/underscore-min.js"></script>
<script type="text/javascript" src="../../media/scripts/backbone-min.js"></script>
<script type="text/javascript" src="application.js"></script>
</head>
<body>
<header>
<h1>Showing views from a collection and basic events</h1>
<p>The list below is made from JSON, passed to the view as a collection and has basic events</p>
</header>
<article>
</article>
</body>
</html>
Here is the JavaScript I currently have. I just need to know if I'm approaching this correctly?
window.App = {
// namespaces
Controller: {},
Model : {},
Collection : {},
View : {},
// code that starts when the app is first fired
initialize : function () {
var collection = new App.Collection.Inputs([
{title: "Item 1"},
{title: "Item 2"},
{title: "Item 3"}
]);
var view = new App.View.InputSet({collection: collection});
$('article').html(view.render().el);
}
}
/*
Collection: Inputs */
App.Collection.Inputs = Backbone.Collection.extend();
/*
View: _Input */
App.View._Input = Backbone.View.extend({
events: {
"click a": "close"
},
// called as soon as a view instance is made
initialize: function() {
// this makes the render, clear etc available at this
// if not setting this, both render() and clear() method will not have themselves in this
_.bindAll(this, "render", "close");
},
// backbone required method, which renders the UI
render: function() {
// this is using underscore templating, which can be passed context
$(this.el).html(_.template('<p><%=title%> [close]</p>', this.model.toJSON()));
return this;
},
close: function() {
// removes the UI element from the page
$(this.el).fadeOut(300);
return false; // don't want click to actually happen
}
});
/*
View: InputSet, uses _Input */
App.View.InputSet = Backbone.View.extend({
events: {
'click a': 'clear'
},
initialize: function() {
// this makes the render, clear etc available at this
// if not setting this, both render() and clear() method will not have themselves in this
_.bindAll(this, "render");
},
// backbone required method, which renders the UI
render: function() {
var that = this;
views = this.collection.map(function(model) {
var view = new App.View._Input({model: model});
$(that.el).append(view.render().el);
return view;
});
$(that.el).append('[clear]');
return this;
},
clear: function() {
$(this.el).find('p').fadeOut(300);
}
});
// wait for the dom to load
$(document).ready(function() {
// this isn't backbone. this is running our earlier defined initialize in App
App.initialize();
});
This looks fine to me. However, I found that things can get tricky once you start doing non-trivial stuff: complex views, nested collections etc.
One thing that could be done differently is that instead of generating input views using collection.map you could bind the collection's add event to a function that generates an _Input view for that item in the collection instead. So you'd have something like this in your InputSet view:
initialize: function() {
_.bindAll(this, "addInput", "removeInput");
this.collection.bind("add", this.addInput);
this.collection.bind("remove", this.removeInput);
}
addInput: function(model) {
var view = new App.View._Input({model: model});
$(this.el).append(view.render().el);
}
I looks good to me - really the only thing I would suggest is that you bind the collection's 'change' event to _Input.render that way changes to your collection automatically re-render the view:
// called as soon as a view instance is made
initialize: function() {
_.bindAll(this, "render", "close");
this.collection.bind('change', this.render);
},
Other than that I think it looks good!

Categories