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.
Related
I'm using Backbone.js 1.1.2 with jQuery 1.11.
I think I'm missing something very simple.
I've set up a model: Contact and a collection: ContactList.
I have success rendering ContactView views with models obtained from ContactList.fetch()
But when I try to bind a change event in the view initializer:
this.model.on('change', this.render, this);
... I get:
backbone Uncaught TypeError: Object #<Object> has no method 'on'
So I go back to the documentation and read that .on() is part of Backbone.Events, and somehow I need to extend my model to use this (and why is this not part of the out-of-the-box functionality for models??)
I've tried declaring
_.extend(this, Backbone.Events);
... inside of the initialize function for ContactView before declaring the binding, but no dice.
What do I need to do to get the Backbone.Events functionality working??
update: all relevant code (sans template)
var Contact = Backbone.Model.extend({
defaults: {
FirstName: '',
...[etc]
},
initialize: function() {
}
});
var ContactList = Backbone.Collection.extend({
model: Contact,
url: '/api/Contacts/getall'
});
var ContactView = Backbone.View.extend({
initialize: function() {
this.render();
this.model.on('change', this.render, this);
},
render: function () {
var template = _.template($('#contact_template').html(), this.model);
this.$el.html(template);
}
});
var contactList = new ContactList();
contactList.fetch({
success: function (collection, response, options) {
contactList.models.forEach(function (m) {
var $el = $('<li />')
.addClass('adminItem clearfix')
.appendTo($('#contactList'));
new ContactView({ el: $el, model: m.attributes });
});
}
});
This line is the source of trouble
new ContactView({ el: $el, model: m.attributes });
m.attributes is a plain js object. You want to pass along the actual model object:
new ContactView({ el: $el, model: m});
And because the collection has underscore methods mixed in you should be able to simplify it a bit to this:
contactList.each(function (m) {
var $el = $('<li />')
.addClass('adminItem clearfix')
.appendTo($('#contactList'));
new ContactView({ el: $el, model: m});
});
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());
}
});
I have a collection, and I'm trying to share information concerning the current model being selected between two different views
-->TO PUT IT SIMPLY, I WANT TO BE ABLE TO ACCESS THE MODEL SELECTED IN ONE VIEW FROM ANOTHER VIEW AND CHANGE/ASSIGN THE ATTRIBUTES OF THE MODEL
The first view is defined with:
App.Views.Person = Backbone.View.extend({
tagName: 'a',
template: template('personTemplate'),
initialize: function(){
this.model.on('change', this.render, this);
this.model.on('destroy', this.remove, this);
},
render: function() {
this.$el.html( this.template(this.model.toJSON()) );
this.input = this.$('.view');
return this;
},
the second view is defined with:
App.Views.App = Backbone.View.extend({
el: 'html',
initialize: function(){
_.bindAll(this,"render");
},
render: function() {
return this;
},
and I created my views with the following
addPersonView = new App.Views.AddPerson({ collection: peopleCollection });
appView = new App.Views.App({ model: person, collection: peopleCollection });
How do I make it so that the model selected in the 2nd view is the same as the model in the first view as pulled from my collection --> for example, when I type something into the input box on my bottom view, I want to be able to use: this.set.model({name:title}) and for this to set the model attribute for the element (associated with a model) that is selected in my top view, but using this.set.modelis not choosing the correct model that is selected in my first view
for further reference and confusion: my models are being added to my PeopleView with the following code which i'm loading from an array;
App.Views.People = Backbone.View.extend({
// tagName: '',
initialize: function() {
var i = 1;
while(i < size)
{
var person = new App.Models.Person({ url: jsArray[i] });// creating a new person object..
this.collection.add(person);
i++
}
this.collection.on('add', this.addOne, this);
console.log(jsArray[1]);
// listeners/anouncers for the collection on add..
},
// refactored render method...
render: function() {
this.collection.each(this.addOne, this);
return this;
},
// called from render method of collection view..
addOne: function(person) {
var personView = new App.Views.Person({ model: person, vent: vent });
this.$el.append(personView.render().el);
}
});
I resolved this by using 'vents' so to speak --> (awesome link) the vents provide ways for views to communicate etc
http://lostechies.com/derickbailey/2012/04/03/revisiting-the-backbone-event-aggregator-lessons-learned/, or an application level aggregator like so:
MyApp.vent = _.extend({}, Backbone.Events);
MyApp.vent.on("some:event", function(){
alert("some event was fired!");
});
MyApp.vent.trigger("some:event");
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.
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/