I looping through a collection in an attempt to add a row to a table on every loop. Here is the code that loops the collection, and build the single view,
App.Views.OrganisationMembersTab = Backbone.View.extend({
el: '#members',
template: _.template( $('#tpl-members-tab-panel').html() ),
events: {
},
initialize: function() {
this.$el.html( this.template() );
this.render();
},
render: function() {
this.addAll();
},
addAll: function() {
this.collection.each( this.addOne, this);
},
addOne: function(model) {
console.log(model);
var tableRow = new App.Views.OrganisationsMemberRow({
model: model
});
tableRow.render();
}
});
The single view that gets called to build the row looks like this,
App.Views.OrganisationsMemberRow = Backbone.View.extend({
el: '.members-list tbody',
template: _.template($('#tpl-organisation-member-row').html() ),
events: {
},
initialize: function() {
},
render: function() {
this.$el.prepend( this.template({
member: this.model.toJSON()
}));
return this;
}
});
The model that is being used once it has been parsed to JSON using toJSON() looks like this,
email: "john.doe#email.com"
first_name: "John"
last_name: "Doe"
The template for the row looks like this,
<script type="text/template" id="tpl-members-tab-panel">
<table class="table table-striped members-list">
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
<th>D</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="4"><button class="btn btn-success btn-sm pull-right">Add +</button></td>
</tr>
</tbody>
</table>
</script>
The above builds the main table components, and the next template is actually for a data row.
<script type="text/template" id="tpl-organisation-member-row">
<tr>
<td>#</td>
<td><%= first_name %> <%= last_name %></td>
<td>Admin <input type="checkbox" /></td>
<td>Remove</td>
</tr>
</script>
All I get output the the main table and then in the main tbody I get either nothing prepended or an empty <tr> why is this?
The problem is with your template which doesn't use member property, just use whole model instead.
You need replace
this.$el.prepend( this.template({
member: this.model.toJSON()
}));
with
this.$el.prepend( this.template(
this.model.toJSON()
));
working example
Your current implementation is a little confused. Your row view has no tagName so by default you'll be appending divs to your tbody.
The first thing I'd do is take the <tr> tag out of your tpl-organisation-member-row template and then alter your row view like so:
App.Views.OrganisationsMemberRow = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#tpl-organisation-member-row').html() ),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
Member row template:
<script type="text/template" id="tpl-organisation-member-row">
<td>#</td>
<td><%= first_name %> <%= last_name %></td>
<td>Admin <input type="checkbox" /></td>
<td>Remove</td>
</script>
Then I'd prefer to control appending the rows from your App.Views.OrganisationMembersTab view. So in your addOne method do the following:
addOne: function(){
var tableRow = new App.Views.OrganisationsMemberRow({
model: model
});
this.$('tbody').append(tableRow.render().el);
}
Related
I have a huge project in C# MVC. In one of the views, in index.cshtml, I have a code like this:
#model IEnumerable<Library.Models.Customer>
#{
ViewBag.Title = "Customers";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Klienci</h2>
<p>
#Html.ActionLink("Nowy klient", "New", "Customers", null, new { #class = "btn btn-primary" })
</p>
<table id ="customers" class="table table-bordered table-hover">
<thead>
<tr>
<th>Klient</th>
<th>Typ Czlonkostwa</th>
<th>Usun</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
#section scripts
{
<script>
$(document).ready(function () {
var table = $("#customers").DataTable({
ajax: {
url: "/api/customers",
dataSrc: ""
},
columns: [
{
data: "Name",
render: function(data, type, customer) {
return "<a href='/customers/edit/" + customer.Id + "'>" + customer.Name + "</a>";
}
},
{
data: "MembershipType.Name"
},
{
data: "Id",
render: function(data) {
return "<button class='btn-link js-delete' data-customer-id=" + data + ">Delete</button>";
}
}
]
});
$("#customers").on("click", ".js-delete",
function () {
var button = $(this);
if (confirm("Na pewno chcesz usunac?")) {
$.ajax({
url: "/api/customers/" + button.attr("data-customer-id"),
method: "DELETE",
success: function () {
//datatable methods - row, remove and draw
table.row(button.parents("tr")).remove().draw();
}
});
}
});
});
</script>
}
It works like a charm. But in another view, I have the code like this:
<table id="customers" class="table table-bordered table-hover">
<thead>
<tr>
<th>Ksiazka</th>
<th>Gatunek</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
#foreach (var book in Model)
{
<tr>
<td>#Html.ActionLink(book.Name, "Edit", "Books", new { id = book.Id }, null)</td>
<td>#book.Genre.Name</td>
<td>
<button class="btn-link" js-delete>Delete</button>
</td>
</tr>
}
</tbody>
#section scripts
{
<script>
$(document).ready(function() {
$("#customers .js-delete").on("click",
function() {
confirm("Sure?");
});
});
</script>
}
and it doesn't work. I mean, it compiles without any errors or warnings, but when I click Delete button nothing happens (should pop up confirm box).
What am I doing wrong?
If needed, I can provide whole code from these both views.
.js-delete means that "js-delete" is assumed to be a css class, while in the html it is an attribute. To search for elements with a particular attribute, you need "has attribute" selector:
$("#customers [js-delete]")
When i trigger a click on a cell with backbone event im not getting the cell view, im getting the view of the row.
I'm sending the models and the week atributes to my UserView.
So what i want is that every cell is a unique backbone view. Can anyone help me?
Backbone view
app.types.UserView = Backbone.View.extend({
tagName: 'tr',
$sidebar: $('#userView'),
template: _.template($('#user-template').html()),
events:
{
//"click .test": "open",
"click td.test": "useri",
},
initialize: function(options)
{
this.options = options;
console.log("this.options", this.options.object.tjedan);
console.log("this.model: ", this.model);
this.$sidebar.append(this.render());
this.tableClick();
//this.open();
//this.getWeeksInMonth();
//console.log("this.tjedan: ", this.tjedan);
},
render: function()
{
this.$el.html(this.template(_.extend(this.model.attributes, {model_cid: this.model.cid, tjedan: this.options.object.tjedan})));
console.log("this.render: ", this);
return this.$el;
},
useri: function()
{
console.log("this.model", this);
}
});
HTML
<div class="container-fluid">
<div class="row">
<div id="collection" class="col-md-12 sidebar">
<table class="table">
<thead id="weekView">
<script type="text/template" id="week-template">
<th>Users</th>
<% for(var i=0;i<tjedan.length;i++)%> {
<th class="list-group week" scope="row"><%= tjedan[i].dan + " " + tjedan[i].datum + "." + tjedan[i].mjesecBrojevi + "." %></th>
}%>
</script>
</thead>
<tbody id="userView">
</tbody>
<script type="text/template" id="user-template">
<th class="list-group model" scope="row" data-cid="<%= model_cid %>"><%= username %></th>
<% for(var i=0;i<tjedan.length;i++)%> {
<td id="<%= id + tjedan[i].dan + tjedan[i].datum %>" class="list-group" scope="row" data-id="<%= model_cid + '_' + tjedan[i].dan + tjedan[i].datum + tjedan[i].mjesecBrojevi %>"></td>
}%>
</script>
</table>
</div>
</div>
</div>
You need to create a view which will serve as the cell view and render each cell creating a new view.
Cell view
var CellView = Backbone.View.extend({
tagName: 'td',
className: 'list-group',
template: _.template('stuff inside your `td`'),
attributes: function() {
return {
id: this.model.get('your_choice'),
scope: 'row',
"data-id": this.whatever
};
},
events: {
"click": "onClick",
},
render: function() {
this.$el.empty().append(this.template(this.model.toJSON()));
return this;
},
onClick: function(e) {
// click on cell
}
});
Row view
var RowView = Backbone.View.extend({
tagName: 'tr',
template: _.template('<th class="list-group model" scope="row" data-cid="<%= model_cid %>"><%= username %></th>'),
initialize: function(options) {
this.options = _.extend({ /* default options */ }, options);
this.childViews = [];
},
render: function() {
// when using extend, the first object is modified, `toJSON` returns
// a shallow copy of the attributes, avoiding modifying them.
var data = _.extend(this.model.toJSON(), {
model_cid: this.model.cid,
tjedan: this.options.object.tjedan
});
this.$el.empty().append(this.template(data));
this.collection.each(this.renderCell, this);
return this; // render always return this for chaining.
},
renderCell: function(model) {
var view = new CellView({
model: model
});
this.childViews.push(view);
this.$el.append(view.render().el);
},
remove: function() {
// explicitely remove the views to avoid memory leaks with listeners.
_.invoke(this.childViews, 'remove');
}
});
This is just an example of how to render a view per cell, it doesn't properly take into account your models and every attributes in the templates.
I have been trying to figure out how templates work along with models and collections. Parts of tutorials make sense, but other parts don't. So I have been messing in JSFiddle trying to get the following example to work.
All I really am trying to do is build a couple of objects. Then output them into a table in a specific div.
Based on the error it is almost as if the data isn't getting passed into the template. From my understanding what I am doing should work.
var Note = Backbone.Model.extend({
defaults : {
title: "",
description: ""
}
});
var note1 = new Note({title: "Patience", description: "Something we all need"});
var note2 = new Note({title: "Fun Times", description: "All the things"});
var Notebook = Backbone.Model.extend({
model: Note
});
notes = new Notebook([note1, note2]);
var NoteView = Backbone.View.extend({
el: '.content',
initialize: function() {
alert("hello");
this.render();
},
render: function () {
var template = _.template($('#notes-templates').html(), {notes: notes.models});
this.$el.html(template);
}
});
new NoteView();
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.js"></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/backbone.js/1.1.2/backbone-min.js"></script>
<div class="content">
</div>
<script type="text/template" id="notes-templates">
<table>
<thead>
<tr>
<th>title</th>
<th>scripture</th>
</tr>
</thead>
<tbody>
<% _.each(notes, function(note) { %>
<tr>
<td><%= note.get('title') %></td>
<td><%= note.get('description') %></td>
</tr>
<% }); %>
</tbody>
</table>
</script>
Try making Notebook a Backbone collection and using the collection api to iterate in the view. Also posted at http://jsfiddle.net/rossta/vn8hh5o7/2/
var Note = Backbone.Model.extend({
defaults : {
title: "",
description: ""
}
});
var note1 = new Note({title: "Patience", description: "Something we all need"});
var note2 = new Note({title: "Fun Times", description: "All the things"});
var Notebook = Backbone.Collection.extend({
model: Note
});
notes = new Notebook([note1, note2]);
var NoteView = Backbone.View.extend({
el: '.content',
initialize: function() {
alert("hello");
this.render();
},
render: function () {
var template = _.template($('#notes-templates').html(), {notes: notes});
this.$el.html(template);
}
});
new NoteView();
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.js"></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/backbone.js/1.1.2/backbone-min.js"></script>
<div class="content">
</div>
<script type="text/template" id="notes-templates">
<table>
<thead>
<tr>
<th>title</th>
<th>scripture</th>
</tr>
</thead>
<tbody>
<% notes.forEach(function(note) { %>
<tr>
<td><%= note.get('title') %></td>
<td><%= note.get('description') %></td>
</tr>
<% }); %>
</tbody>
</table>
</script>
I want to render collection in handlebars precompiled templates,
this.courses.fetch() is working
handlebars view is rendering correctly...(only each loop not rendering collection data)
here is my code...
router.js
App.Router = Backbone.Router.extend({
routes: {
'master/courses' : 'courses'
},
initialize: function(){
this.courses = new App.Collections.Courses();
this.list = new App.Views.List( {collection: this.courses} );
},
courses: function() {
$('#page').html(this.list.render().el);
}
});
var app = new App.Router();
Backbone.history.start();
courses.js
App.Collections.Courses = Backbone.Collection.extend({
model: App.Models.Course,
url: '/api/courses'
});
list.js
p.Views.List = Backbone.View.extend({
initialize: function() {
this.listenTo(this.collection, 'reset', this.render);
},
render:function(){
this.$el.html( Handlebars.templates.list(this.collection) );
return this;
}
});
list.handlebars
<h1>Courses</h1>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr>
{{#each models}}
<td>{{attributes.id}}</td>
<td>{{attributes.name}}</td>
{{/each}}
</tr>
</tbody>
</table>
this is my first attempt on backbone project kindly suggest good practice also
I don't see here the point where collection fetching is fired. The first line in your courses probably should be this.collection.fetch({ reset: true });
As I see in provided HTML structure, each should be before tr tag. Like this:
<h1>Courses</h1>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
{{#each models}}
<tr>
<td>{{attributes.id}}</td>
<td>{{attributes.name}}</td>
</tr>
{{/each}}
</tbody>
</table>
I just got started on using backbone.js. I have a view ListingListView that refreshes a table with new content when fetch() is called.
Problem: This table contains some <th> elements. If I were to do a $(this.el).empty(); and this.render() during the update of the table contents, the <th> elements will be removed. How can I prevent this? I want the <th> elements to remain. Thanks!
JS Code
// Views
window.ListingListView = Backbone.View.extend({
el: '#listing_list table',
initialize: function() {
this.model.bind('reset', this.refreshList, this);
this.model.bind('add', function(listing) {
$(this.el).append(new ListingListItemView({ model: listing }).render().el);
}, this);
},
render: function() {
_.each(this.model.models, function(listing) {
$(this.el).append(new ListingListItemView({ model: listing }).render().el);
}, this);
return this;
},
close: function() {
$(this.el).unbind();
$(this.el).empty();
},
refreshList: function() {
$(this.el).empty();
this.render();
}
});
HTML Code
<div id="listing_list">
<table class="table table-bordered table table-striped">
<th>Address</th>
<th>Beds</th>
<th>Baths</th>
<th>Price</th>
</table>
</div>
You could add some structure to your table, using thead and tbody:
<div id="listing_list">
<table class="table table-bordered table table-striped">
<thead>
<tr>
<th>Address</th>
<th>Beds</th>
<th>Baths</th>
<th>Price</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
And target the tbody in your render and refreshList functions:
render: function() {
var $tbody=this.$("tbody"); // or $(this.el).find("tbody")
_.each(this.model.models, function(listing) {
$tbody.append(new ListingListItemView({ model: listing }).render().el);
}, this);
return this;
},
refreshList: function() {
this.$("tbody").empty();
// or $(this.el).find("tbody").empty() if you prefer
this.render();
}
Notes:
don't forget you can use a collection as a special option instead of a model : http://backbonejs.org/#View-constructor It could be a bit clearer in the end.
Backbone proxies Underscore functions on collections, _.each(this.model.models... can be written as this.model.each (this.collection.each if you apply the note above)