CompositeView not rendering ItemViews, doesn't even trigger appendHtml - javascript

I'm a little lost with my view rendering--first time trying to do this. I have my templates set up similar to this application.
So far I have it rendering the template for the CompositeView but it doesn't render any of the ItemViews. It doesn't even trigger the rendering method to try to debug so I'm not sure where I can log pieces to see where it's getting stuck... Here's the code:
This is my ItemView:
define([
'jquery',
'underscore',
'backbone',
'text!templates/service/item.ejs'
], function($, _, Backbone, template) {
ServiceItemView = Backbone.Marionette.ItemView.extend({
tagName: 'tr',
template: '#service-item-template'
});
}
);
This is my CompositeView: Update: Added in my requirejs code to show that ServiceItemView is loaded before ServiceTableView
define([
'jquery',
'underscore',
'backbone',
'views/service/item',
'text!templates/service/table.ejs'
], function($, _, Backbone, ServiceItemView, template) {
var ServiceTableView;
ServiceTableView = Backbone.Marionette.CompositeView.extend({
tagName: 'table',
id: 'service-table',
itemView: ServiceItemView,
itemViewContainer: 'tbody',
template: '#service-table-template',
appendHtml: function(collectionView, itemView){
console.log("here");
//collectionView.$("tbody").append(itemView.el);
}
});
}
);
Here's where I attempt to render it:
service_collection = new ServiceCollection([
new Service({
name: "Men's Cut",
length: 108000,
price: 2500
}),
new Service({
name: "Women's Cut",
length: 324000,
price: 5000
})
]);
service_table = new ServiceTableView({
collection: service_collection
});
App.main_region.show(service_table);
Update: Here are the two templates:
ServiceItemView Template:
<script type="text/html" id="service-item-template">
<td><%= name %></td>
<td><%= length %></td>
<td><%= price %></td>
<td class="actions">
<input type="button" class="icon" value="Delete" />
</td>
</script>
ServiceTableView Template:
<script type="text/html" id="service-table-template">
<thead>
<tr>
<th>Name</td>
<th>Time allotment</th>
<th>Pricing</th>
<th class="actions">Actions</th>
</tr>
</thead>
<tbody>
</tbody>
</script>
Again, the ServiceTableView template is rendered, but none of the Services are rendered underneath.
Any help is appreciated. Even pointers on where to stick log statements to get more information.
Thanks!

Turns out the Collections I had created were written:
Backbone.Model.extend instead of Backbone.Collection.extend. I must have copied the code from the model when creating the collection to speed up writing it.
Fixed now and working if anyone would like to use the above code for an example for their own projects.

Related

Rendering a table with Backbone

I need help with my code, I'm trying to learn Backbone for my Social Project. I'm trying to render a view from a collection that I got from an API (deployd API)
Here is the HTML code for the table:
<div class="container-fluid">
<table id= "teachers">
<thead>
<tr>
<th>Name</th>
<th>Last Name</th>
<th>Code</th>
<th>Last time online</th>
</tr>
</thead>
<tbody id="table-body"></tbody>
</table>
</div>
<script type="text/template" id="teacher-template">
<td><%= name %></td>
<td><%= lastname %></td>
<td><%= code %></td>
<td><%= lastactivity %></td>
</script>
Here is the JS code:
var TeacherModel = Backbone.Model.extend({
defaults: {
id:'',
name: '',
lastname: '',
code: '',
lastactivity: ''
}
});
var TeacherCollection = Backbone.Collection.extend({
url: "/teachers",
model: TeacherModel
});
var teachercollection = new TeacherCollection();
teachercollection.url = '/teachers';
teachercollection.fetch({
success: function(collection, response) {
console.log("Done!!");
}, error: function(collection, response) {
alert(response);
}
});
var TeachersView = Backbone.View.extend({
el: '#table-body',
initialize: function() {
this.render();
},
render: function() {
this.$el.html('');
teachercollection.each(function(model) {
var teacher = new TeacherView({
model: model
});
this.$el.append(teacher.render().el);
}.bind(this));
return this;
}
});
var TeacherView = Backbone.View.extend({
tagName: 'tr',
template: _.template($('#teacher-template').html()),
render: function() {
this.$el.html(this.template(this.model.attributes));
return this;
}
});
// Launch app
var app = new TeachersView;
So my question is, how I can pass a collection to a view, or a model of the collection to a view? I want to render the data in each row from the table. The browser gets the collection, as you can see here:
I've been trying for days, and I just can't understand the logic, I have read the documentation, and a little of the Addy Osmani's book but just can't get my head on it, can someone explain it to me? Been looking for answers in this site but some on them include some "add models" stuff, which confuse me more.
(The parameters of the model in the image, differ from the code. I'd translate to make it more easy to understand.)
how I can pass a collection to a view, or a model of the collection to a view?
You are already doing that in your code:
var teacher = new TeacherView({
model: model
});
Here you're passing a model to view's constructor using model option.
You can pass a collection to view via it's constructor like:
var app = new TeachersView({
collection:teachercollection
});
Which you can access inside the view via this.collection and this.model respectively.
var TeachersView = Backbone.View.extend({
el: '#table-body',
initialize: function() {
this.render();
},
render: function() {
this.$el.html('');
this.collection.each(function(model) {
this.$el.append(new TeacherView({
model: model
}).el);
},this);
return this;
}
});
Note that fetch() is asynchronous, so you'll need to wait till it succeeds before rendering the view.
See the suggestions in this answer regarding the changes I made to your render method.
this answer might help understanding a thing or two.

Backbone model.get('key') undefined after .fetch() call, even inside success callback

I'm new to Backbone.js, and just finished running through a basic tutorial to create a "user list" system (https://www.youtube.com/watch?v=FZSjvWtUxYk) where all the templates, scripts, etc are created inline. I got everything working pretty easily, so I decided to try and modularize things since I know that's the best practice. I'm following this guide to the AMD methodology (https://cdnjs.com/libraries/backbone.js/tutorials/organizing-backbone-using-modules) and have everything working properly except for one thing - when editing a user, the "current" data isn't being loaded into the form. All of the issues I've found on SO and other places so far have been solved by putting the template generating code inside the success: callback of the .fetch() call, but I'm already doing that.
Here's the code:
(I'm leaving out the main.js and app.js that handle the require.js configuration, router init, etc. They seem to be working just fine.)
// Filename: router.js
define([
'jquery',
'underscore',
'backbone',
'views/userList',
'views/editUser'
], function($, _, Backbone, UserListView, EditUserView){
var AppRouter = Backbone.Router.extend({
routes: {
'': 'home',
'new': 'editUser',
'edit/:id': 'editUser'
}
});
var initialize = function(){
var app_router = new AppRouter;
app_router.on('route:home', function(){
var userListView = new UserListView();
userListView.render();
});
app_router.on('route:editUser', function(id) {
var editUserView = new EditUserView();
editUserView.render({ id: id });
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
views/editUser.js
// Filename: views/editUser
define([
'jquery',
'underscore',
'backbone',
'models/user',
'text!/templates/editUser.html'
], function($, _, Backbone, UserModel, rawEditUserTemplate) {
var userListView = Backbone.View.extend({
// Element to use for this view
el: $('.page'),
// Function to call when this view is rendered
render: function(options) {
var that = this;
// If there is an ID, we are editing
if ( options.id ) {
// Create the user, passing the ID
that.editUser = new UserModel({ id: options.id });
// Fetch the user data
that.editUser.fetch({
// When the fetch is returned
success: function(userData) {
// Generate the template and pass the data in
var editUserTemplate = _.template( rawEditUserTemplate );
that.$el.html(editUserTemplate({ user: userData }));
}
})
}
else { // We are creating a new user
// Generate the template with an empty user
var editUserTemplate = _.template( rawEditUserTemplate );
this.$el.html(editUserTemplate({ user: null }));
}
},
events: {
'submit .edit-user-form': 'saveUser',
'click .delete': 'deleteUser'
},
saveUser: function(e) {
e.preventDefault();
// Get the details
var userDetails = $(e.currentTarget).serializeObject();
// Create a user model
var user = new UserModel();
// Save the user details
user.save(userDetails, {
success: function(user) {
Backbone.history.navigate('', { trigger: true });
}
});
},
deleteUser: function(e) {
e.preventDefault();
// Destroy the user we are editing
this.editUser.destroy({
// When the destroy is finished
success: function() {
// Back to home
Backbone.history.navigate('', { trigger: true });
}
});
}
});
// Our module now returns our view
return userListView;
});
templates/editUser.html
<form class="edit-user-form">
<legend><%= user ? 'Update' : 'Create' %> User</legend>
<div class="form-group">
<label for="firstname">First Name</label>
<input type="text" class="form-control" name="firstname" id="firstname" value="<%= user ? user.get('firstname') : '' %>" />
</div>
<div class="form-group">
<label for="lastname">Last Name</label>
<input type="text" class="form-control" name="lastname" id="lastname" value="<%= user ? user.get('lastname') : '' %>" />
</div>
<div class="form-group">
<label for="age">Age</label>
<input type="text" class="form-control" name="age" id="age" value="<%= user ? user.get('age') : '' %>" />
</div>
<hr />
<button class="btn btn-success" type="submit"><%= user ? 'Update' : 'Create' %></button>
<% if ( user ) { %>
<input type="hidden" name="id" id="id" value="<%= user.id %>" />
<button class="btn btn-danger delete">Delete</button>
<% }; %>
</form>
Using this code, I get a blank edit form regardless of whether or not I'm editing or creating, HOWEVER the "Create" vs "Update" text switch in the template is working properly. This means that a user object is in fact being passed, and when I add a console.log(user) into the template file, it is in fact showing me user data. When I log user.get('firstname') or any other attribute, however, it logs "undefined".
The issue was in my User model, which I didn't include above because I didn't understand at the time why it could be relevant.
I was defining it as:
var userModel = Backbone.Model.extend({
url: '/users'
});
When it should have been:
var userModel = Backbone.Model.extend({
urlRoot: '/users'
});
The wrong option was causing the API to return a collection rather than a model, so the .get() wasn't able to work properly.

How to make use of the data when calling fetch on it?

I am using require.js with backbone and backbone.localstorage and I am trying to figure out how to make use of the data after calling fetch, not sure how to go about it... I am trying to pass the data into my view and make use of it.
Here is the example of the data stored in localstorage:
[{"artist":"Hits 1 Entertainment 4-1-1","title":"Hear Katy's Perry's New Album!"}, ...]
So it is objects within an array.
This is my code for backbone...
var songz = new Songs();
songz.localStorage = new Backbone.LocalStorage("music");
songz.fetch({dataType: 'json'});
var songV = new SongV({collection: songz});
songV.render();
Songs is a collection, that looks like this in the collections file, SongV is the view for each song.
Here is the view with the code above included:
define([
'jquery',
'underscore',
'backbone',
'models/song',
'collections/songs',
'views/song',
'text!templates/page.html'
], function($, _, Backbone, Song, Songs, SongV, PageT){
var Page = Backbone.View.extend({
el: $("#page"),
render: function () {
this.$el.html( PageT );
var songz = new Songs();
songz.localStorage = new Backbone.LocalStorage("music");
songz.fetch({dataType: 'json'});
var songV = new SongV({collection: songz});
songV.render();
}
});
return Page;
});
Here is the collection file:
define([
'jquery',
'underscore',
'backbone',
'models/song',
], function($, _, Backbone, Song){
var Songs = Backbone.Collection.extend({
model: Song,
initialize: function () {
}
});
return Songs;
});
Here is the model file:
define([
'underscore',
'backbone'
], function(_, Backbone) {
var Song = Backbone.Model.extend({
});
return Song;
});
Here is the template file:
<tr>
<th> Number </th>
<th> Title </th>
<th> Artist </th>
<th> Date_Added </th>
<th> Video </th>
</tr>
<% _.each(songs, function(song){ %>
<tr>
<td> <%= song.get("number") %> </td>
<td> <%= song.get("title") %> </td>
<td> <%= song.get("artist") %> </td>
<td> <%= song.get("added_on") %> </td>
<td> <%= song.get("video") %> </td>
</tr>
<% }); %>
You need to fetch and then bind to the reset event on the collection to see when it was successfully pulled the data from the server.
Page = Backbone.View.extend
el: $('#page')
render: ->
songz = new Songs()
# Initialize view
songV = new SongV({collection: songz})
# Render view
songV.render()
# Fetch collection
songz.fetch()
SongV = Backbone.View.extend
initialize: ->
#listenTo #collection, "reset", #onReset
onReset: (collection) ->
# Use populated collection data
...
Songs = Backbone.Collection.extend
model: Song
localStorage: new Backbone.LocalStorage("music")
initialize: ->
This is how Backbone.LocalStorage stores the collections and models --> here
See the table at the bottom, the key for the chain block is you local storage name and then each model has a unique key.
So this means if you have data sitting in local storage that you have put there yourself, you should take it out with a cross-browser local storage device like store.js and then use it to populate your Backbone.Collection.
Alternatively, you could fetch from the server (recommended) and that will populate your collection. Or you could bootstrap the data on page load and reset your collection that way.

how and where to initialize jquery datatable in backbone view

My html template look like this:
<script type="text/template" id="players-template">
<table id="example" class="table table-striped table-bordered table-condensed table-hover">
<thead>
<tr>
<th>Name</th>
<th>group</th>
<th></th>
</tr>
</thead>
<tbody id="playersTable"></tbody>
</table>
</script>
<script type="text/template" id="player-list-item-template">
<td><#= name #></td>
<td>
<# _.each(hroups, function(group) { #>
<#= group.role #>
<# }); #>
</td>
</script>
My backbone view is as follows:
playerView = Backbone.View.extend({
template: _.template( $("#player-template").html() ),
initialize: function ()
if(this.collection){
this.collection.fetch();
},
render: function () {
this.$el.html( this.template );
this.collection.each(function(player) {
var itemView = new app.PlayerListItemView({ model: player });
itemView.render();
this.$el.find('#playersTable').append(itemView.$el);
},this
});
// view to generate each player for list of players
PlayerListItemView = Backbone.View.extend({
template: _.template($('#player-list-item-template').html()),
tagName: "tr",
render: function (eventName) {
this.$el.html( this.template(this.model.toJSON()) );
}
});
The above code works perfectly. Now, I want to use apply jquery datatable plugin wtih bootstrap support. You can find detail here :http://www.datatables.net/blog/Twitter_Bootstrap_2
So, I just added the line inside render as:
render: function () {
this.$el.html( this.template );
this.collection.each(function(player) {
var itemView = new app.PlayerListItemView({ model: player });
itemView.render();
this.$el.find('#playersTable').append(itemView.$el);
$('#example').dataTable( {
console.log('datatable');
"sDom": "<'row'<'span6'l><'span6'f>r>t<'row'<'span6'i> <'span6'p>>",
"sPaginationType": "bootstrap",
"oLanguage": {
"sLengthMenu": "_MENU_ records per page"
},
"aoColumnDefs": [
{ 'bSortable': false, 'aTargets': [ 2 ] }
]
} );
},this);
},
Now, the jquery datable is not initialized. They just diisplay normal table.
where should I intialized the table to apply jquery datatable?
they worked perfectly without backbone.
Most likely, the jQuery plugin needs the elements to be on the page to work. You don't show where you are calling render on that view, but I am going to assume you are doing something like this:
var view = new PlayerView();
$('#foo').html(view.render().el); // this renders, then adds to page
If this is true, then using the plugin inside render is too early, since the view's html is not yet added to the page.
You can try this:
var view = new PlayerView();
$('#foo').html(view.el); // add the view to page before rendering
view.render();
Or you can try this:
var view = new PlayerView();
$('#foo').html(view.render().el);
view.setupDataTable(); // setup the jQuery plugin after rendering and adding to page

Get Uncaught TypeError: Object #<Object> has no method 'get' when i try to display data in template

I am running into the issue of getting Uncaught TypeError: Object # has no method 'get' when i try to display data in a template here are the different backbone parts:
Template:
<script type="text/template" id="class-template">
<table class="table striped"></table>
<thead>
<tr>
<th>Picture</th>
<th>First Name</th>
<th>Last Name</th>
<th>Role</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<% _.each(users,function(user){ %>
<tr>
<td><%= user.get('picUrl') %></td>
<td><%= user.get('firstName') %></td>
<td><%= user.get('lastLame') %></td>
<td><%= user.get('role') %></td>
<td><%= user.get('email') %></td>
</tr>
<% }); %>
</tbody>
</table>
</script>
Data Models and Collection:
$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
options.url = 'http://localhost/' +options.url;
});
var Office = Backbone.Model.extend({
defaults: {
street : null,
city : null,
state : null,
country : null,
postal_code : null,
},
initialize: function(){
console.log("==> NEW LOCATION");
// you can add event handlers here...
}
});
var Person = Backbone.Model.extend({
defaults: {
picURL : null,
firstName : null,
lastName : null,
email : null,
role : null,
location : new Office()
},
initialize: function(){
console.log("==> NEW PERSON MODEL");
// you can add event handlers here...
}
});
var Users = Backbone.Collection.extend({
url:'loadData.php?list=16025,28477,28474,25513,16489,58911,04607',
model:Person
});
View:
var ShowClass = Backbone.View.extend({
el: '.page',
initialize: function() {
_.bindAll(this); //Make all methods in this class have `this` bound to this class
},
template: _.template($('#class-template').html()),
render: function() {
var users = new Users();
console.log('calling fetch');
users.fetch();
users.on("reset", function(users){
console.log('rendering with data:'+users.models[0].get('firstName'));
this.$el.html(this.template({users:users.models}));
console.log('finished');
}, this);
}
});
I am able to see the data that is returned from the fetch call, so i know that I am getting data back. It all seems to fall apart when i send it to the template. Thanks in advance for all of your help!
Instead of performing the get() on your script template, you should just pass the raw attributes as oppose to passing in the entire model.
I realize that you also have to change your template but abstracting your template this way and doing the loop outside the template itself would give you a better handle on your error. This will also make your code modular and easier to debug.
View:
users.on("reset", function(users){
_.each(users, function (user) {
var data = user.toJSON();
this.$el.html(this.template({
picUrl: data.picUrl,
firstName: data.firstName }));
}, this);
The template would simply be:
<td><%- picUrl %></td>
<td><%- firstName %></td>
...

Categories

Resources