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.
Related
Below code is my backbone view. :
app.WorkerView = Backbone.View.extend({
tagName: 'tr',
className: 'odd',
template: _.template(templat1e),
render: function() {
//this.el is what we defined in tagName. use $el to get access to jQuery html() function
window.alert("I am in render function of WorkerView");
this.$el.html(this.template(this.model.attributes));
return this;
}
});
But this can produce html with class odd only. My goal is to produce html with different class for odd and even row. What I need to do?
I never worked with backbone but if you are using template to render your html fragment then you may try this out
<% _each(list, function(index) { %>
<tr <% if (index%2 == 0) { %>class="even" <% } else { %>class="odd" <% } %> ></tr>
<% }); %>
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/
I just started learning Backbone and from what I've seen so far when you create a view and you define a tagName and a className the view you create is created inside that element but it doesn't that it works on the code below, could someone please explain to me why? I've spend wayyy too much time on this and my head is spinning.
var app = {};
(function ($) {
app.Todo = Backbone.Model.extend({
defaults : {
name : '',
priority: '',
description: ''
}
});
app.TodoList = Backbone.Collection.extend({
model: app.Todo,
url: '#todolist'
});
app.TodoListView = Backbone.View.extend({
tagName: 'ul',
className: 'todolist',
initialize: function() {
this.template = _.template($('#todolist-template').html());
this.render();
},
render: function() {
this.$el.empty();
this.$el.append(this.template({items: this.collection.toJSON()}));
return this;
}
});
app.todoList = new app.TodoList([
new app.Todo({
name: 'unclog the sink',
priority: '10',
description: 'FIX THE SINK!!!'
}),
new app.Todo({
name: 'get bread',
priority: '0',
description: 'We are out of bread, go get some'
}),
new app.Todo({
name: 'get milk',
priority: '2',
description: 'We are out of milk, go get some'
})
]);
new app.TodoListView({el: $('#container'), collection: app.todoList});
})(jQuery);
template:
<script type="text/template" id="todolist-template">
<% _.each(items, function(item){ %>
<li>
<%= item.name %>
<%= item.description %>
<%= item.priority %>
</li>
<%}); %>
</script>
result:
<div id="container">
<li>unclog the sink FIX THE SINK!!! 10</li>
<li>get bread We are out of bread, go get some 0</li>
<li>get milk We are out of milk, go get some 2</li>
</div>
I am not expert in BackboneJS but trying to resolve the issue. As per backboneJS doc about el property, its always there in to render backbone view. The default element is <div> if you don't specify. You have understood right, that element would be render inside it.
In your code you are providing <div> element as an el, so its overriding your tagName property when creating new object of view. And another thing is you are calling render before creating object which may cause a problem. So as per my opinion, you code should looks like this :
$("$container").html((new app.TodoListView({collection: app.todoList})).render())
You should read about the el property of View, here. When you specify tagName and className it creates the DOM element <ul class="todolist"></ul> but doesn’t append it to the DOM. If the element already exists in the DOM, you can set el as a CSS selector that matches the element.
So in your case your template is getting created in an ul element, but the ul element itself is not added in the DOM.
Try doing following:
NOTE : div with an id as container should already be present in DOM.
View definition:
app.TodoListView = Backbone.View.extend({
el: '#container',
initialize: function() {
this.template = _.template($('#todolist-template').html());
this.render();
},
render: function() {
this.$el.html(this.template({items: this.collection.toJSON()}));
return this;
}
});
Template:
<script type="text/template" id="todolist-template">
<ul class="todolist">
<% _.each(items, function(item){ %>
<li>
<%= item.name %>
<%= item.description %>
<%= item.priority %>
</li>
<%}); %>
</ul>
</script>
View creation :
new app.TodoListView({collection: app.todoList});
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.
I'm using the backbone-on-rails gem with javascript (as opposed to coffeescript) to render views for a model. Everything works fine, but my view code is kind of long so I'd like to refactor things out to use includes/partials.
Heres' my code:
in my views/model_show.js file
AppName.Views.ModelNameShow = Backbone.View.extend({
template: JST['main'],
initialize: function() {
this.render();
},
render: function() {
this.model.fetch({async:false});
$(this.el).html(this.template({ model: this.model }))
$('#main_content').html(this.el);
}
});
in my templates file I'd like to so something like this in templates/main.jst.ejs
<% if(this.model) { %>
<h2> model found</h2>
<%- include top_section %>
<%- include middle_section %>
<%- include bottom_section %>
<% } else { %>
<h3>Claim not found <a href='#'>Back</a></h3>
<% } %>
where top_section would be a file under templates (like templates/top_section.jst.ejs)
Problem is I keep getting the following errors (firebug output).
SyntaxError: missing ) in parenthetical [Break On This Error]
...2> model found \n\t',(''+ include top_section ).replace(/&/g,
'&').replace(/
which causes this error because the template isn't rendered
TypeError: this.template is not a function
[Break On This Error]
$(this.el).html(this.template({ model: this.model }))
I should mention that I've tried a few different syntaxes like the ones below, but those just throw different errors.
<%= include top_section %>
<%- include(top_section) %>
Thanks in advance
In EJS the correct syntax for a partial is this:
<%= this.partial({url: 'templates/partial.ejs'}) %>
As a stop-gap for now I just created a hash of templates and enumerate over them in the render. Not ideal, but works for now. Here's what the code looks like now.
views/model_show.js file
AppName.Views.ModelNameShow = Backbone.View.extend({
templates: {
top_section: JST['top_section'],
middle_section: JST['middle_section'],
bottom_section: JST['bottom_section']
},
initialize: function() {
this.render();
},
render: function() {
this.model.fetch({async:false});
var zuper = this;
$.each(this.templates, function(key, value) {
//iterate over each template and set the html for the div with id=key with template
//For example $('#top_section).html(JST template for top_section)
$('#' + key).html(zuper.templates[key]({ model: zuper.model }))
})
}
});
template file looks like this now.
<% if(this.model) { %>
<h2> model found</h2>
<div id="top_section"/>
<div id="middle_section"/>
<div id="include bottom_section"/>
<% } else { %>
<h3>Model not found <a href='#'>Back</a></h3>
<% } %>
As I said -- not ideal, but a functional workaround for now.