Backbone + Handlebars: template not getting the data - backbone.js

I'm sending data from my backbone view to a handlebars template (js fiddle: http://jsfiddle.net/8NhjD/) like this:
this.$el.html(this.template({
users: that.users.toJSON(),
audiences: that.audiences.toJSON()
}));
and I'm trying to access the list of users and audiences like this:
<select name="user" class = "form-control">
{{#each users}}
<option value="{{name}}">{{name}}</option>
{{/each}}
</select>
But the dropdown menus for the users and audiences are empty. What am I doing wrong here?

Your problem is that you are passing in the models, which do not expose their attributes directly. Try something like this:
this.$el.html(this.template({
users: that.users.toJSON(),
audiences: that.audiences.toJSON()
}));
UPDATED:
Without a complete fiddle, it is hard to see where you went wrong. Here is a working fiddle: http://jsfiddle.net/moderndegree/qW7Tz/
HTML:
<script id="thing-template" type="text/x-handlebars-template">
<ul>
{{#each things}}
<li>{{this.thing}}</li>
{{/each}}
</ul>
</script>
<div id="thing-view"></div>
JS:
var ThingModel = Backbone.Model.extend({}),
ThingCollection = Backbone.Collection.extend({
model: ThingModel
}),
ThingView = Backbone.View.extend({
el: '#thing-view',
template: Handlebars.compile($("#thing-template").html()),
initialize: function(){
this.things = new ThingCollection([{thing: 'purple'}, {thing: 'monkey'}, {thing: 'dishwasher'}]);
},
render: function(){
console.log(this.things.toJSON());
this.$el.html(this.template({
things: this.things.toJSON()
}));
return this;
}
});
var view = new ThingView().render();

Moving the collection-fetching from the view's initialize method to the route-handler resolved the issue.

Related

Backbone - Cannot get model on click using localstorage

I'm trying to setup a little app in backbone where I can add items to a list and, when I click them, they'll be deleted. I've managed to add items to the list but when using model.destroy() nothing happens.
When I console.log the click event on the list models I get:
child {cid: "c0", attributes: Object, _changing: false, _previousAttributes: Object, changed: Object…}
for any item I click.
Code is below:
Html:
<h1>INDEX!</h1>
<form class="add-form">
<input type="text" name="name"/>
<hr />
<button type="submit" class="btn">Submit</button>
</form>
<h2>LIST STUFF</h2>
<ul class="blah">
{{#each indexCollection}}
<li class="li-class">{{name}}</li>
{{/each}}
</ul>
Javascript:
//Local Storage
App.Storage.Local = new Backbone.LocalStorage('localIndexList1-backbone');
//Index Model
App.Models.IndexModel = Backbone.Model.extend({
localStorage: App.Storage.Local,
defualts:{
name:''
},
urlRoot: '/'
});
//Index Collection
App.Collections.IndexCollection = Backbone.Collection.extend({
localStorage: App.Storage.Local,
model: App.Models.IndexModel,
initialize: function(){
console.log('Collection initialised');
},
url: '/'
});
//View for H1 and input form
App.Views.IndexView = Backbone.View.extend({
el: '.page',
events:{
'submit .add-form' : 'addNew',
'click' : 'deleteMe'
},
initialize: function(){
console.log('IndexView initialised');
},
addNew: function(ev){
// ev.preventDefault();
var submitEntry = $(ev.currentTarget).serializeObject();
var newEntry = new App.Models.IndexModel();
newEntry.save(submitEntry, {
success: function(newEntry){
// router.navigate('', {trigger: true});
console.log('SUCESSS!!!!!!!!!');
}
});
},
deleteMe: function(){
console.log(this.model);
//Whatever I put here will not work
}
});
//View for list
App.Views.ListView = Backbone.View.extend({
el: '.page',
initialize: function(){
console.log('ListView initialised');
},
template: Handlebars.compile($('#list').html()),
render: function(){
this.$el.html(this.template);
var that = this;
var indexCollection = new App.Collections.IndexCollection();
indexCollection.fetch({
success:function(indexCollection){
that.$el.html(that.template({indexCollection: indexCollection.toJSON()}));
}
});
}
});
Would anyone be able to help letting me know where I am going wrong?
Thanks!
Where are you creating one IndexView for each of your collection models? You should have an item view, configure its model to be one IndexModel, and move your delete code to that particular view. When you do that, you should also call remove in this item view.
This is why something like Backbone.Marionette helps a lot. Just throw in a CollectionView and you're done.
Think of it like this:
"list view" -> has a collection
"item view" -> has a single model
Anything you need to on the collection level (like adding a new one, re-loading, whatever), do it on your list view. Anything you need on model level (editing, saving, deleting), do it on your item view.

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 backbone view is not getting rendered

this is below is code which i am using for simple task add , view is not getting render, i am not able to find the mistake ,
<!doctype html>
<html lang="en">
<head>
<title>Calculator</title>
<link rel="stylesheet" href="styles/bootstrap/css/bootstrap.min.css">
<script src="js/libs/jquery-1.9.1.min.js"></script>
<script src="js/libs/underscore-min.js"></script>
<script src="js/libs/backbone-min.js"></script>
<script type="text/template" id="display-template">
<div class="row">
<div class="span4">
<%=content%>
</div>
</div>
</script>
<script language="javascript">
var cdate;
var tasks={};
var app = app || {};
// App view responsible for rendering app
app.TaskView = Backbone.View.extend({
el: $('#tasks'),
template: _.template($('#display-template').html()),
initialize: function () {
this.render();
},
render: function () {
console.log("render called");
console.log(this.template());
this.$el.html(this.template());
}
});
app.task = Backbone.Model.extend({
defaults:{
content:null
}
});
app.bUsers = Backbone.Collection.extend({
model : app.task,
initialize: function(models, args) {
this.bind('add', this.renderone);
this.bind('remove', this.destroy); },
renderone:function(user){
console.log(user);
var view = new app.TaskView({model: user});
},
destroy:function(user){
$(user.view.el).remove();
}
});
app.Users = new app.bUsers();
$(document).ready(function() {
cdate=new Date();
$("#cdate").html(new Date());
$("#pre").click(function(){
cdate=new Date(cdate.getTime()-(1*24*3600*1000));
$("#cdate").html(cdate);
});
$("#next").click(function(){
cdate=new Date(cdate.getTime()+(1*24*3600*1000));
$("#cdate").html(cdate);
});
$("#submit").click(function(){
if(tasks[cdate]==undefined) tasks[cdate]=[];
tasks[cdate].push($("#task").val());
// app.appView = new app.TaskView({
// model: new app.task({"content":$("#task").val()})
// });
var data ={"content":$("#task").val()};
app.Users.add(data);
});
});
</script>
</head>
<body>
<a id="pre" href="#">Prev</a>
<div id="cdate"></div>
<a id="next" href="#">Next</a>
<input type="text" id="task" ></input>
<input type="button" value="submit" id="submit" ></input>
<div id="tasks"></div>
</body>
Oye, you've got a few problems.
To answer your specific question, your render method of your view should take your view's model instance and get something from this.model.toJSON() it to get at its data to pass to the template method (toJSON really returns "JSONable" objects).
But that's not all.
Besides a few html issues, you also have stylistic problems.
Collections generally should not be concerned with views, only data (*). Views should be concerned with with collections and models. Collections communicate to views via event binding, which I see you are doing. However, for reuse purposes, you may have more than one combination views that might want to listen to events in the collection. By setting up the event binding in the collection, you've effectively limited your collection for only one use.
Views can do alot. There's not much reason to manually add DOM event handlers when you can code the view to do it for you.
I haven't written Backbone in a little while (not by choice!), but generally found it was a good idea to have a view dedicated to the collection, and then have a separate model view that the collection view might create or destroy based upon whatever events took place.
Here's a bit of a cleanup of your code to give you a starting example:
http://jsfiddle.net/jfcox/SmPNv/
HTML:
<a id="pre" href="#">Prev</a>
<div id="cdate"> </div>
<a id="next" href="#">Next</a>
<input type="text" id="task" />
<input type="button" value="add" id="submit" />
<div id="tasks"></div>
Backbone definitions:
var defs = defs || {};
//first define the data handlers
defs.Task = Backbone.Model.extend({
defaults: function () {
return {
content: null,
addDate: (new Date()).toString()
};
}
});
defs.Users = Backbone.Collection.extend({
model: defs.Task
});
// App view responsible for rendering app
defs.SingleTaskView = Backbone.View.extend({
//since we can't control where the js is loaded, go ahead and make the template inline for jsFiddle demo.
tagName: 'div',
template: _.template('<div class="row"> <div class="span4"><%=content%></div> <em><%=addDate%></em> <button class="remove"> remove</remove> </div>'),
events: {
"click button.remove": "remove"
},
initialize: function (opts) {
this.model.on('change', this.render, this);
},
render: function () {
console.log("render called");
var modelBare = this.model.toJSON();
return this.$el.html(this.template(modelBare));
},
remove: function () {
//removes from local collection, does not delete on server
//for that, you'd want `this.model.destroy`
this.collection.remove(this.model);
//removes this view's element.
this.$el.remove();
}
})
defs.TasksView = Backbone.View.extend({
el: 'body',
events: {
"click #pre": "doPrevious",
"click #next ": "doNext",
"click #submit ": "doSubmit"
},
cdate: null,
initialize: function (opts) {
this.cdate = new Date();
this.render();
this.collection.on('add', this.renderone, this);
},
render: function () {
$("#cdate").html(this.cdate.toString());
},
doPrevious: function () {
this.cdate = new Date(this.cdate.getTime() - (1 * 24 * 3600 * 1000));
$("#cdate").html(this.cdate.toString());
},
doNext: function () {
this.cdate = new Date(this.cdate.getTime() + (1 * 24 * 3600 * 1000));
$("#cdate").html(this.cdate.toString());
},
doSubmit: function () {
var data = {
"content": $("#task").val()
};
this.collection.add([data]);
},
renderone: function (userModel) {
var view = new defs.SingleTaskView({
model: userModel,
collection: this.collection
});
this.$el.find('#tasks').append(view.render());
}
});
The application, itself.
var app = app || {};
app.users = new defs.Users();
(function ($) {
$(document).ready(function () {
app.usersview = new defs.TasksView({
collection: app.users
});
});
})(jQuery);
(*) This is a guideline, not an absolute rule, of course. If you think a collection might work as some sort of workflow manager, etc, that might be fine, but that's an advanced topic.
Edit: I included the template inline, partially for reasons that I don't trust jsFiddle with inline "text" scripts. I'm not recommending any way to handle that, just that's how I did it here.

Rendering collection view in backbone.js

I am having problems understanding how to render a collection in a view using a template. Here is my code:
<div id="mydiv"></div>
<script type="text/template" id="details">
<ul>
<% _.each(?, function(person) { %>
<li><%= person.name %></li>
<% }); %>
</ul>
</script>
<script>
var m = Backbone.Model.extend();
var c = Backbone.Collection.extend({
url: 'retrieve.php',
model: m
});
var v = Backbone.View.extend({
el : $('#mydiv'),
template : _.template($("#details").html()),
initialize : function() {
var coll = new c();
coll.fetch({success: function(){alert(JSON.stringify(coll));} });
this.render();
},
render : function() {
//what do I put here?
return this;
}
});
var view = new v();
I am confused about how to get the data returned from my php file into the template. What code do I need in the view and ._each? My php code is returning:
[{"id":"1","name":"John","age":"5"},{"id":"2","name":"Jane","age":"2"}]
and I see this in the alert().
You should call your render function from the success handler as a collection.fetch returns result asynchronously (you can also bind render function to a collection reset event).
var v = Backbone.View.extend({
el : '#mydiv',
template : _.template($("#details").html()),
initialize : function() {
var self = this,
coll = new c();
coll.fetch({ success: function(){ self.render() ; } });
},
render : function() {
// the persons will be "visible" in your template
this.$el.html(this.template({ persons: this.coll.toJSON() }));
return this;
}
});
And in the template refer to the same persons object
<script type="text/template" id="details">
<ul>
<% _.each(persons, function(person) { %>
<li><%= person.name %></li>
<% }); %>
</ul>
</script>
Update:
I've created the working fiddle, but I had to modify the original source code as I can't use your retrieve.php endpoint
What you have asked is a generic question of how to use a collection to generate a view. Most of them are comfortable with generating a view with a model, but not with a collection. I followed the following tutorial Binding a collection to a view. Might be helpful for you also.

Backbone View not appending on Collection change

I have a text input which I use to collect a keyword from the user, and then send an ajax request (which is getting successful results). I would like to append the <ul> with <li> items, each representing a result.
Here is the which includes the search input and should get the appended <li> results.
<div class="search_group pull-right">
<h5>Find Other Groups<span class="caret"></span></h5>
<ul>
<li class="search-input">
<input type="text" class="search" name="search">
</li>
</ul>
</div>
I have the following underscore template which would ideally represent each result in the collection.
<script type="text/template" class="template" id="template-search-result">
<% _.each(results,function(result){ %>
<li class="result">
<a href="/surnames/<%=URL%>">
<%=result.name%>
<span><%=result.members%> – Members</span>
<span><%=result.ancestors%> – Ancestors</span>
</a>
</li>
<% }); %>
</script>
And now, my Backbone code.
I fetch my Results collection after the user enters something in the input field, and when that collection returns results from my server (which I can see successfully in the console), I would expect the ResultView to append the ul with additional li results.
window.Results = Backbone.Collection.extend({
url: function() {
return '/ajax/groups/search?q=' + query;
}
});
var results = new Results();
window.SearchInput = Backbone.View.extend({
el: ".search-input input",
events: {
"change": "getResults",
"keyup": "getResults",
"keydown": "getResults",
"paste": "getResults"
},
getResults: function() {
query = $('input').val();
results.fetch();
}
});
var searchInput = new SearchInput({ collection: results });
window.ResultView = Backbone.View.extend({
el: ".search-group ul",
initialize: function() {
_.bindAll(this, 'render');
this.collection.bind('change', this.render);
this.template = _.template($("#template-search-result").html());
},
render: function() {
this.$el.append( this.template( {results: this.collection.toJSON()} ));
return this;
}
});
var resultView = new ResultView({ collection: results });
You're either looking for the add or reset events, depending how you're loading your collection.
If you're doing a fetch, you'll want to listen to reset. If you are adding models to the collection manually, or calling fetch({update:true}), you'll get the add events.
The collection change event only fires when the one of the collection's models fires a change of its own.
Here's a list of all the built-in Backbone events for reference.
Instead of defining the "el" in my ResultView as:
el: ".search-group ul",
I changed it to an id and modified my markup accordingly:
el: "#result-list",
and:
<ul id="result-list">
<li class="search-input">
<input type="text" class="search" name="search">
</li>
</ul>

Resources