Variable passed into backbone view coming up undefined - backbone.js

I am in the process of learning backbone / underscore, and I am finding that the more I break away from the really basic stuff in the tutorials, the more come to realize that the tutorials aren't teaching me much of anything.
My current problem is with passing a variable to a view. I have three different templates available, but they all render the same, so I was hoping to just pass which template to use into the view when it was being rendered from the collection. What I thought would work would be just adding a property to the call to the view, and then accessing it with this.options.Property, but this is throwing an error that the property is undefined.
I have tried a number of variant options, but nothing seems to work. What am I doing wrong?
Thanks is advance.
var ProjectListView = Backbone.View.extend({
el: '#projectList',
initialize: function() {
this.collection = masterProjectList;
this.render();
},
render: function() {
this.$el.html("");
this.collection.each(function(project) {
this.renderItem(project);
}, this);
},
renderItem: function(project) {
var projectView = new ProjectView({model: project, projectType: '#theatricalProjectTemplate' });
// Passing in the project type, which determines which template gets used
this.$el.append(projectView.render().el);
}
});
var ProjectView = Backbone.View.extend({
tagName: "div",
className: "project-wrap",
template: _.template($(this.options.projectType).html()),
// use this.options to access the value
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});

When you're defining your ProjectView:
var ProjectView = Backbone.View.extend({
//...
template: _.template($(this.options.projectType).html()),
//...
});
you're executing some code (i.e. calling extend) and in this context, this will be the global object (AKA window in a browser) and that probably won't have an options property. If you want to use the projectType option that you pass to the ProjectView constructor, move the template assignment into initialize:
var ProjectView = Backbone.View.extend({
tagName: "div",
className: "project-wrap",
initialize: function() {
this.template = _.template($(this.options.projectType).html());
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
This assumes that projectType will be a valid jQuery selector, you might want to use '#' + this.options.projectType instead but I'm not sure what exactly will be in projectType.

mu is too short is correct and if you define template method as follows, you can share the template method with all instances of ProjectView:
var ProjectView = Backbone.View.extend({
tagName: "div",
className: "project-wrap",
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
template: function() {
return _.template($(this.options.projectType).html());
}
});

Related

Rendering options with parameters in backbone.js

This should be simple but I've been stuck for about 3 hours on it, and can't seem to find a solution. I'm new to backbone so maybe I'm overlooking something.
I'm trying to render a view where the options all have values. It's coming out like this:
<select name="role" id="option-roles" class="form-control">
<option>All</option>
<option>Basic User</option>
<option>Basic Student</option>
...
</select>
But I want each option to have a value to look like this:
<option value='10'>Basic User</option>
In backbone my code looks like this.
app.RoleSelectView = Backbone.View.extend({
el: '#roles',
template: _.template($('#tmpl-roles').html()),
initialize: function() {
this.collection = new Backbone.Collection(app.mainView.results.roles, {
model: app.Role
});
this.render();
},
render: function() {
this.$el.html(this.template());
var frag = document.createDocumentFragment();
var view = new app.RoleOptionsView({
model: new app.Role(),
value: ''
});
frag.appendChild(view.render().el);
this.collection.each(function(role) {
var view = new app.RoleOptionsView({
model: role,
value: role.id.toString()
});
frag.appendChild(view.render().el);
}, this);
$('#option-roles').append(frag);
return this;
}
});
app.RoleOptionsView = Backbone.View.extend({
tagName: 'option',
template: _.template($('#tmpl-role-option').html()),
render: function() {
this.$el.html(this.template(this.model.attributes));
return this;
}
});
Any help would be appreciated.
From the fine manual:
el view.el
[...]
this.el can be resolved from a DOM selector string or an Element; otherwise it will be created from the view's tagName, className, id and attributes properties.
If you want to add an attribute (such as value) to your el then use attributes:
app.RoleOptionsView = Backbone.View.extend({
tagName: 'option',
attributes: function() { // <---------------------------------
return {
value: 'where ever your value comes from'
};
},
template: _.template($('#tmpl-role-option').html()),
render: function() {
this.$el.html(this.template(this.model.toJSON());
return this;
}
});
Also, it is generally preferred that you say this.model.toJSON() rather than directly accessing this.model.attributes.
Like most data-ish things in views, the attributes can either be a static object or a function which returns an object. In your case, the attributes presumably depend on something in the view's model so a function is needed so that you can work with the view instance and its model.

How to pass a template to a view in Backbone

I'm trying to pass a template to my view. I have several different templates I want to use and want to be able to switch them up in my router. I get no errors, but I get no results. It looks like the initialize method isn't being called in my second view. Here is my code:
(function() {
window.App = {
Models: {},
Collections: {},
Views: {},
Router: {}
};
window.template = function(id) {
return _.template( $('#' + id).html() );
};
var vent = _.extend({}, Backbone.Events);
_.templateSettings.interpolate = /\[\[(.+?)\]\]/g;
App.Router = Backbone.Router.extend({
routes: {
'' : 'index',
'send-message' : 'sendMessage',
'*other' : 'other'
},
index: function() {
t = new (App.Collections.Tables.extend({ url: 'main-contact'}))();
tables = new (App.Views.Tables.extend({
collection: t, template: template('mainContactTemplate')}))();
$('#web-leads').html(tables.el);
},
sendMessage: function() {
t = new (App.Collections.Tables.extend({ url: 'send-message'}))();
tables = new App.Views.Tables.extend({
collection: t, template: template('sendMessageTemplate')});
$('#web-leads').html(tables.el);
},
other: function() {
}
});
// Main Contact
App.Models.Table = Backbone.Model.extend({});
App.Collections.Tables = Backbone.Collection.extend({
model: App.Models.Table,
initialize: function(models, options) {
this.fetch({
success: function(data) {
//console.log(data.models);
}
});
if (options) {
this.url = this.url || options.url;
}
}
});
App.Views.Tables = Backbone.View.extend({
tagName: 'ul',
initialize: function() {
this.collection.on('reset', this.render, this);
},
render: function() {
return this.collection.each(this.addOne, this);
},
addOne: function(model) {
var t = new App.Views.Table({ model: model, template: template});
this.$el.append(t.render().el);
return this;
}
});
App.Views.Table = Backbone.View.extend({
tagName: 'li',
template: this.template,
initialize: function (attrs) {
this.options = attrs;
console.log(this.options);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
new App.Router();
Backbone.history.start();
})();
EDIT: I was missing some parenthesis. But now I get an error of an unrecognized expression. Initialize is now being called.
The way you are doing it in App.Views.Table is (as far as I can tell) the "standard" way of using templates with Backbone. There are of course several alternatives though, and none of them are "wrong" per say.
That being said, you do have a couple problems in your code. Let's start with:
template: this.template,
At the time that code runs you're not in an instance of App.Views.Tables, you're in the global space declaring a class that (later) will be used to make instances. At that moment though, this just refers to window. What you really want to do is set the template in your initialize, which leads me to:
initialize: function(options) {
this.template = options.template;
},
But then there's one last problem:
var t = new App.Views.Table({ model: model, template: template});
there is no template variable in that function, so you're really doing template: undefined. That should use a real template.
All that being said, you might want to just consider putting the template on the view directly, the way you sort of tried to:
template: Handlebars.compile('<span>{{test}}</span>'),
After all, any given view should always use the same template, right? Also, you might want to consider moving the:
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
in to a parent class, so that you can share it between all of your templated views, instead of having to repeat it.

Super simple-minimal Backbone script requested

Looking for the absolute minimum script to get Backbone working. Tried piecing various tutorials and sample together, but having problems getting views to work. Nothing fancy, I'll take raw json in the browser right now. Just a basic skeleton to help connect the dots and build on. I've tried various variations on the following:
(function ($) {
var model = Backbone.Model.extend({
idAttribute: 'custId'
});
var collection = Backbone.Collection.extend({
initialize: function(){
},
model: model,
url: '/cust'
});
var view = Backbone.View.extend({
initialize: function(){
_.bindAll(this, 'render'); // fixes loss of context for 'this' within methods
this.collection.bind("reset", this.render);
this.render();
},
el: $('#content'),
template: Handlebars.compile($("#contentTemplate").html()),
render: function(){
$(this.el).html( this.template(this.model.toJSON()));
},
tagName: "li"
});
var router = Backbone.Router.extend({
initialize: function(){
var newCollection = new collection;
newCollection.fetch();
},
route: {
"": "home"
},
home: function(){
this.view = new view({collection: newCollection});
$('#content').html(this.view.el);
}
});
var app = new router();
}(jQuery))
Thanx.
You are misusing the el attribute. $('#content').html(this.view.el) will result in copying the $('#content') element inside itself (because view.el is equal to $('#content')).
You should try removing the el attribute from the view object and let it generate itself. Then $('#content').html(this.view.el); should work.
One other possible problem is that you are rendering the entire collection inside a li element - was this what you are going for? The best way to go about this would be to have each model in the collection represent a li tag and the collection a ul tag.
Other issues:
the view element is receiving a collection but you are trying to render a model
in the router, newCollection is not accessible in the home method
You are not calling Backbone.history.start()
Here is how i would rewrite the code:
var model = Backbone.Model.extend({
idAttribute: 'custId'
});
var model_view = Backbone.View.extend({
template: Handlebars.compile($("#modelTemplate").html()),
tagName: 'li',
initialize: function() {
_.bindAll(this, 'render');
this.render();
this.on('change',this.render);
},
render: function() {
$(this.el).html( this.template(this.model.toJSON()) );
return this;
}
});
var collection = Backbone.Collection.extend({
initialize: function(){
},
model: model,
url: '/cust'
});
var collection_view = Backbone.View.extend({
tagName: "ul",
initialize: function(){
_.bindAll(this, 'render','renderModels');
this.render();
this.renderModels();
this.collection.bind("reset", this.render);
this.collection.bind("reset", this.renderModels);
},
render: function(){
// just create the 'ul' tag; we will populate it with model view elements; a collection template is no longer needed
return this;
},
renderModels: function() {
this.collection.each(function(obj){
var view = new model_view({
model: obj
});
$(this.el).append(view.el);
},this);
}
});
var router = Backbone.Router.extend({
initialize: function(){
this.newCollection = new collection();
this.newCollection.fetch();
},
route: {
"": "home"
},
home: function(){
this.view = new collection_view({collection: this.newCollection});
$('#content').html(this.view.el); // #content should not be a 'ul' tag, the 'ul' is generated by the collection_view
}
});
var app = new router();
Backbone.history.start();
Make sure you update your templates accordingly.
Please excuse possible errors, i had no means to test the code but i believe it points out the logic you should use.
Cheers!

Backbone.js not rendering

My collection is not rendering for some reason. Cannot find out why.
TreeItem = Backbone.Model.extend({
});
TreeList = Backbone.Collection.extend({
model: TreeItem,
url: "/get_tree_list"
});
window.tree_list = new TreeList();
// VIEW
window.TreeItemView = Backbone.View.extend({
tagName: 'li',
initialize: function(){
_.bindAll(this, 'render');
},
render: function(){
$(this.el).html('<span>'+this.model.get('title')+'</span>');
return this;
}
});
window.TreeListView = Backbone.View.extend({
el: "#tree-structure",
events: {
},
initialize: function() {
_.bindAll(this, 'appendItem', 'render');
tree_list.bind('add', this.appendItem);
tree_list.fetch();
this.render();
},
render: function() {
tree_list.each(this.appendItem);
return this;
},
appendItem: function(item){
var tree_item_view = new TreeItemView({
model: item
});
$(this.el).append(tree_item_view.render().el);
}
});
var tree_list_view = new TreeListView;
Backbone.js provides a lot to be interpreted that's where people new go wrong. Your mistake is fundamental in nature. You tie the View directly to the model
see initialize function where a instance of collection is rendered!!
Always and anywhere you create model, collection pass then as parameters to the constructor of views. Check my fiddle
Never call render inside model, view or collection. They must be inside application file
JsFiddle
http://jsfiddle.net/35QGM/

access function in one view from another in backbone.js

I have this structure of views:
window.templateLoaderView = Backbone.View.extend({});
window.PopupView = templateLoaderView.extend({
initialize: function () {
this.PopupModel = new PopupModel();
this.event_aggregator.bind("tasks_popup:show", this.loadTaskPopup);
},
render: function() {
template= _.template($('#'+this.PopupModel.templateName).html());
$(this.el).html(template(this.PopupModel.toJSON()));
$('#'+this.PopupModel.containerID).html(this.el);
},
loadTaskPopup: function() {
this.PopupModel.loadTemplate("popupTask_templateHolder", "/js/templates/popup_task.html", "1", "container_dialog");
this.render();
}
});
window.TaskbarView = templateLoaderView.extend({
initialize: function () {
this.TaskbarModel = new TaskbarModel();
this.PopupModel = new PopupModel();
},
loadTaskbarPopup: function() {
this.event_aggregator.trigger("tasks_popup:show");
}
});
So I would like to runf function in one view form another. As far as I understand, I need to bind them somehow. Is it possible to bind them in initialize function?
I saw here example: Backbone.js - Binding from one view to another? . They creating both objects and than somehow binding them.
Thanks in advance,
I am kind of a fan of using the "Event Aggregator" pattern. I make sure that every view is given a copy of the same event aggregator object and they can all talk to each other through it... kind of like a CB radio :)
Do this before you create any views:
Backbone.View.prototype.event_aggregator = _.extend({}, Backbone.Events);
Now, you can publish/subscribe from anywhere:
window.PopupView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, "loadTaskPopup");
this.model = new PopupModel();
this.event_aggregator.bind("tasks_popup:show", this.loadTaskPopup);
},
loadTaskPopup: function() {
// do something with this.model
}
});
window.TaskbarView = Backbone.View.extend({
loadTaskbarPopup: function() {
this.event_aggregator.trigger("tasks_popup:show")
}
});

Resources