CompositeView and/or CollectionView won't render table headers - backbone.js

I'm having some problems rendering headers in a composite or collection view. I tried both, but this simply won't render.
Let me show you a little bit of my code:
// composite view
define(function(require) {
var BusinessTableTemplate = require("text!templates/BusinessTableTemplate.html");
var BusinessRowView = require("views/BusinessRowView");
var Marionette = require("marionette");
return BusinessTableView = Marionette.CompositeView.extend({
tagName: "table",
className: "table table-hover",
template: BusinessTableTemplate,
itemView: BusinessRowView,
appendHtml: function(collectionView, itemView){
collectionView.$("tbody").append(itemView.el);
}
});
});
// BusinessTableTemplate
<script type="text/template">
<thead>
<tr>
<th>Name</th>
<th>Address</th>
<th>Building</th>
<th>Phone</th>
<th>Website</th>
<th>Categories</th>
</tr>
</thead>
<tbody></tbody>
</script>
// itemview
define(function(require) {
var BusinessRowTemplate = require("text!templates/BusinessRowTemplate.html");
var Marionette = require("marionette");
return BusinessRowView = Marionette.ItemView.extend({
template: BusinessRowTemplate,
tagName: "tr"
});
});
// BusinessRowTemplate
<script type="text/template">
<td>
<%= name %>
</td>
<td>
<% if (address) { %>
<%= address %>
<% } else { %>
NA
<% } %>
</td>
<td>
<% if (building) { %>
<%= building %>
<% } else { %>
NA
<% } %>
</td>
<td>
<td>
<% _.each(phones, function(phone) { %>
<span class="label label-default label-info"><%= phone %></span>
<% }); %>
</td>
<td>
<% if (website) { %>
<%= website %>
<% } else { %>
NA
<% } %>
</td>
<td>
<% _.each(tags, function(category) { %>
<span class="label label-default label-info"><%= category %></span>
<% }); %>
</td>
<td>
Ações
</td>
</script>
// signal or event where I render the view
vent.on("search:seeAll", function(){
if (self.results) {
var businessDirectoryView = new BusinessDirectoryView();
self.layout.modals.show(businessDirectoryView);
var resultCollection = new Businesses(self.results, {renderInMap: false});
var businessTableView = new BusinessTableView({
collection: resultCollection
});
$("#businesses-container").html(businessTableView.render().$el);
}
});
I followed a tutorial somewhere, but they did not specified headers for the table.
A little help here to render this?
Thanks!

What is the Marionette JS version you are using?
Here is a fiddle using the version 2.4.1 (latest available) and it is working:
https://jsfiddle.net/aokrhw1w/2/
There are some changes in the composite view with previous versions, the 'itemView' was changed to 'childView'.
var BusinessTableView = Marionette.CompositeView.extend({
tagName: "table",
className: "table table-hover",
template: '#BusinessTableTpl',
childView: BusinessRowView,
childViewContainer: "tbody",
appendHtml: function (collectionView, itemView) {
console.log('append');
this.$("tbody").append(itemView.el);
}
});
Hope this solves your problem.

CollectionView renders one child view per model in a collection. It doesn't give you any way to add extra HTML like a header.
CompositeView renders collection items too, but you can also add extra HTML. Your example uses CompositeView, so you have made the right choice.
Because you can have custom HTML around the rendered collection, you need to tell Marionette where to render the collection item views. The childViewContainer property does that:
Marionette.CompositeView.extend({
tagName: "table",
className: "table table-hover",
template: BusinessTableTemplate,
itemView: BusinessRowView,
childViewContainer: 'tbody'
});
You don't need to use attachHtml for simple cases like this - it's for edge cases where you need to change Marionette's default behaviour.
See the CompositeView docs for more detail.

Marionette 3 deprecated the CompositeView class. Instead, a region can now overwrite its el with the rendered contents of the
inner View with the new replaceElement option.
See this example to render a table:
var RowView = Marionette.View.extend({
tagName: 'tr',
template: '#row-template'
});
var TableBody = Marionette.CollectionView.extend({
tagName: 'tbody',
childView: RowView
});
var TableView = Marionette.View.extend({
tagName: 'table',
className: 'table table-hover',
template: '#table',
regions: {
body: {
el: 'tbody',
replaceElement: true
}
},
onRender: function() {
this.showChildView('body', new TableBody({
collection: this.collection
}));
}
});
var list = new Backbone.Collection([
{id: 1, text: 'My text'},
{id: 2, text: 'Another Item'}
]);
var myTable = new TableView({
collection: list
});
myTable.render();
Note to overzealous moderators: I gave the same answer here. Both questions are not duplicates but do refer to the same deprecated class. It makes no sense to spend time to "tailor the answer to both questions"; it's just a waste of time since the only important part is: "This class is deprecated, use that one instead. Here's an example and link to the doc".

Related

How to produce a table using backbone js where my <tr> class for odd and even row are different?

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>
<% }); %>

Backbone + Underscore template binding (sync) issue

I've come across this backbone / underscore template binding issue that I can't get my head around.
The underscore template is being provided a collection of objects however when traversing the collection and building the resultant HTML not all the individual object attributes are being resolved.
I believe the template is receiving the collection correctly (asynchronous call to the server). I've debugged the received collection and its populated correctly.
I've "rendered" the raw collection items within the HTML to verify I'm dealing with the correct objects...all appears correct.
I've simplified the code here in the hopes of not blurring the issue. A click event is responsible for selecting a section(not included in code)
Here is the code:
//Underscore Template
<script id="articles-template2" type="text/html">
<table>
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Date</th>
<th>Data</th>
</tr>
</thead>
<tbody>
<% _.each(articles, function(a){ %>
<tr>
<td><%= JSON.stringify(a)%></td>
<td><%= a.title %></td>
<td><%= a.description%></td>
<td><%= new Date(a.date)%></td>
</tr>
<%});%>
</tbody>
</table>
</script>
// Backbone View ----------------
window.ArticlesView2 = Backbone.View.extend({
initialize: function () {
var self = this;
self.collection.on("reset", this.render, this);
},
template: _.template($('#articles-template2').html()),
render: function () {
var self = this;
$(self.el).html(self.template({ articles: self.collection.models }));
$('#section-list').append(self.el);
return self;
},
events: {
"click a": "clicked"
},
clicked: function (e) {
var self = this;
e.preventDefault();
var id = $(e.currentTarget).data("id");
var item = self.collection.get(id);
var name = item.get("title");
alert(name);
}
});
// Router ----------------
var AppRouter = Backbone.Router.extend(
{
routes: {
'section/:title': 'viewSection'
},
viewSection:function(section)
{
var self = this;
self.articles = new ArticleList({ selectedSection: section });
self.view = new ArticlesView2({ collection: self.articles });
self.articles.fetch({ reset: true });
}
}
);
// Initialize ----------------
var app = new AppRouter();
Backbone.history.start();
app.navigate('section/Home', { trigger: true });
The rendered HTML is as follows :
<table>
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Date</th>
<th>Data</th>
</tr>
</thead>
<tbody>
<tr>
<td>{"id":"527c61082241f813c09c7041","title":"title here","description":" test descript here","date":"2005-02-08T05:00:00.0000000Z"}</td>
<td></td>
<td></td>
<td>Invalid Date</td>
</tr>
</tbody>
</table>
I'm not sure why one can Stringfy() the object and get data but fail to interrogate its attributes successfully?????
Is this a event issue, man what am I missing?
Thanks
You're passing the raw array of your models to your template and a model is not a hash, you don't have direct access to the properties.
Either use collection.toJSON
self.template({ articles: self.collection.toJSON() })
or use model.get in your template
<%= a.get('title') %>
Note that in your example JSON.stringify will give you the expected representation of your data because
toJSON behavior
If an object being stringified has a property named toJSON whose value
is a function, then the toJSON method customizes JSON stringification
behavior: instead of the object being serialized, the value returned
by the toJSON method when called will be serialized.
and your models have a toJSON method.

Backbone View not creating "itself"

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});

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.

how to use includes/partials in Rails 3 Backbone ejs.jst files

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.

Resources