I have a view named DatesView and a subview named DateView. I would like to execute a function in DatesView when an instance of DateView is clicked.
var Date = Backbone.Model.extend({});
var Dates = Backbone.Collection.extend({
model: Date
});
var DateView = Backbone.View.extend({
events: {
'click': 'clicked'
},
render: function () {
this.$el.text(this.model.get('date'));
return this;
}
})
var DatesView = Backbone.View.extend({
render: function () {
this.collection.each(function (date) {
var dateView = new DateView({model: date});
this.$el.append(dateView.render().el)
}, this);
return this;
},
clicked: function() {
console.log('clicked');
}
})
var dates = new Dates([
{date: '01'},
{date: '02'},
{date: '03'}
])
var datesView = new DatesView({collection: dates});
$('#container').html(datesView.render().el);
JSFiddle
As you can see in the example the DateView has an event setup for click, which I would like to register in the DatesView. The way it is written now does not work.
I quite like using Backbone events. You can use it as follows:
Trigger the event in your DateView
clicked: function () {
Backbone.trigger('date:clicked');
}
Listen for the event in the DatesView
initialize: function () {
this.listenTo(Backbone, 'date:clicked', this.clicked);
}
The simplest way to do this is to accept a "clicked" option in your sub view:
var DateView = Backbone.View.extend({
events: {
'click': 'clicked'
},
initialize: function (options) {
// allow parent to pass in a click handler
this.clicked = options.clicked;
}
// snip...
})
var DatesView = Backbone.View.extend({
render: function () {
this.collection.each(function (date) {
// pass parentView's click method to child,
// binding context to parent
var dateView = new DateView({
model: date,
clicked: this.clicked.bind(this)
});
this.$el.append(dateView.render().el)
}, this);
return this;
},
// snip...
jsfiddle
Related
I'm generating a drop down list from Backbone.View.
After attaching it to the DOM, change event is not fired. The delegateEvents doesn't fixes it. Can somebody show me where the blind spot is?
Model and collection:
App.Models.DictionaryItem = Backbone.Model.extend({
default: {
key: '',
value: '', id: 0
}
});
App.Collections.Dictionary = Backbone.Collection.extend({
model: App.Models.DictionaryItem,
initialize: function (models, options) {
},
parse: function (data) {
_.each(data, function (item) {
// if(item){
var m = new App.Models.DictionaryItem({ key: item.code, value: item.name });
this.add(m);
// }
}, this);
}
});
Views:
App.Views.ItemView = Backbone.View.extend({
tagName: 'option',
attributes: function () {
return {
value: this.model.get('key')
}
},
initialize: function () {
this.template = _.template(this.model.get('value'));
},
render: function () {
this.$el.html(this.template());
return this;
}
});
App.Views.CollectionView = Backbone.View.extend({
tagName: 'select',
attributes: {
'class': 'rangesList'
},
events: {
'change .rangesList': 'onRangeChanged'
},
initialize: function (coll) {
this.collection = coll;
},
render: function () {
_.each(this.collection.models, function (item) {
this.$el.append(new App.Views.ItemView({ model: item }).render().el);
}, this);
// this.delegateEvents(this.events);
return this;
},
selected: function () {
return this.$el.val();
},
onRangeChanged: function () {
alert('changed');
}
});
Rendering:
var coll = new App.Collections.Dictionary(someData, { parse: true });
var v= new App.Views.CollectionView(coll);
var vv=v.render().el;
// new App.Views.CollectionView(coll).render().el;
$('body').append(vv)
The tagName and attributes on CollectionView:
tagName: 'select',
attributes: {
'class': 'rangesList'
},
say that the el will be <select class="rangesList">. But your events:
events: {
'change .rangesList': 'onRangeChanged'
},
are listening to 'change' events from a .rangesList inside the view's el. From the fine manual:
Events are written in the format {"event selector": "callback"}. [...] Omitting the selector causes the event to be bound to the view's root element (this.el).
So you're trying to listen for events from something that doesn't exist. If you want to listen for events directly from the view's el then leave out the selector:
events: {
'change': 'onRangeChanged'
}
I'm making a simple list of people with option when clicking on person's name the Router will take a name as a parameter 'student/:name' and find a right person's object in a collection. I instantiate collection in a GroupView class by fetching it from the server. And that's where the Error appears: to get the access to collection (so I can find right object) in my viewStudent() method in Router class, I'm making one more instance of GroupView(), and console shows an error and that's right, 'cause there're no objects in collection.
I cannot wrap my head around this, why in GroupView() I receive data from the server and my collection just works fine, but second time I instantiate GroupView() in a Router - there's no collection? Maybe there's any other way I can get access to the collection in my Router? Any help would be greatly appreciated.
var StudentModel = Backbone.Model.extend({
defaults: {
name: 'Volodya',
lastName: 'Peterson',
age: 22,
gender: 'male'
}
});
var StudentsCollection = Backbone.Collection.extend({
model: StudentModel,
url: '/students.json'
});
var StudentView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#studentTpl').html()),
events: {
'click': function () {
eventAggregator.trigger('student:selected', this.model);
}
},
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var GroupView = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.collection = new StudentsCollection();
this.collection.on('update', this.render, this);
this.collection.fetch();
},
render: function () {
var self = this;
this.collection.each(function (student) {
var studentView = new StudentView({
model: student
});
self.$el.append(studentView.render().el);
});
$('body').html(this.$el);
}
});
var RouterView = Backbone.View.extend({
tagName: 'ul',
render: function () {
var self = this;
_.each(this.model.toJSON(), function (value) {
self.$el.append('<li>' + value + '</li>');
});
return this;
}
});
var GroupController = function () {
this.start = function () {
var groupView = new GroupView();
};
};
var Router = Backbone.Router.extend({
routes: {
'': 'index',
'student/:name': 'viewStudent'
},
index: function () {
groupController.start();
},
viewStudent: function (name) {
var groupView = new GroupView();
var selectedStudent = groupView.collection.find(function (student) {
return student.get('name') === name;
});
$('body').append((new RouterView({ model : selectedStudent})).render().el);
}
});
var eventAggregator= _.extend({}, Backbone.Events),
groupController;
$(function () {
var router = new Router();
groupController = new GroupController();
Backbone.history.start();
eventAggregator.on('student:selected', function (student) {
var urlpath= 'student/'+ student.get('name');
router.navigate(urlpath, {trigger: true});
});
});
I've created 2 separate views, 1 to render the template and the other one is where I bind the events, then I tried merging them into one in which case it causes an Uncaught TypeError: Object [object Object] has no method 'template'. It renders the template and the events are working as well, but I get the error.
edit.js, this is the combined view, which I think it has something to do with their el where the error is coming from
window.EditView = Backbone.View.extend ({
events: {
"click #btn-save" : "submit"
},
initialize: function() {
this.render();
},
render: function() {
$(this.el).html(this.template());
return this;
},
submit: function () {
console.log('editing');
$.ajax({ ... });
return false;
}
});
var editView = new EditView();
signin.js, this is the view that I can't merge because of the el being used by the ajax call and in SigninView's $(this.el) which causes the rendering of the templates faulty
window.toSigninView = Backbone.View.extend ({
el: '#signin-container',
events: {
"click #btn-signin" : "submit"
},
initialize: function() {
console.log('Signin View');
},
submit: function() {
$.ajax({ ... });
return false;
}
});
var toSignin = new toSigninView();
window.SigninView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
$(this.el).html(this.template());
return this;
}
});
and I use utils.js to call my templates
window.utils = {
loadTpl: function(views, callback) {
var deferreds = [];
$.each(views, function(index, view) {
if (window[view]) {
deferreds.push($.get('templates/' + view + '.html', function(data) {
window[view].prototype.template = _.template(data);
}));
} else {
alert(view + " not found");
}
});
$.when.apply(null, deferreds).done(callback);
}
};
In my Router.js, this is how I call the rendering of templates
editProfile: function() {
if (!this.editView) {
this.editView = new EditView();
}
$('#global-container').html(this.editView.el);
},
utils.loadTpl (['SigninView', 'EditView'],
function() {
appRouter = new AppRouter();
Backbone.history.start();
});
I think that I figured out your problem.
First merge your views and delete the line var toSignin = new toSigninView();
Second modify your utils.js code like this :
window[view].prototype.template = _.template(data);
new window[view]();
I am trying to understand Backbone and tried to create some small REST API fronted, but can not get View to work.
fetch() returns valid JSON array of three elements. And this.collection.models is not empty (typeof object - []) - it has tree child object elements in it. But each iteration doesn't fire.
When check if collection.models exists with console.log(this.collection.models); it looks like all is right:
I would be thankful for any advice!
var Account = Backbone.Model.extend({});
var AccountsCollection = Backbone.Collection.extend({
model: Account,
url: 'api/v1/accounts',
initialize: function () {
this.fetch();
}
});
var AccountsView = Backbone.View.extend({
render: function() {
// Check if there is something
console.log(this.collection.models);
// This doesn't work
_.each(this.collection.models, function(model) {
console.log(model);
});
// Neither this
this.collection.each(function(model) {
console.log(model);
});
}
});
var a = new AccountsView({collection: new AccountsCollection()});
a.render();
First option
var AccountsCollection = Backbone.Collection.extend({
model: Account,
url: 'api/v1/accounts'
});
var AccountsView = Backbone.View.extend({
render: function() { /**/ }
});
var collection = new AccountsCollection();
var view = new AccountsView({collection:collection});
collection.fetch().done(function () {
// collection has been fetched
view.render();
});
Second option
var AccountsCollection = Backbone.Collection.extend({
model: Account,
url: 'api/v1/accounts'
});
var AccountsView = Backbone.View.extend({
initialize: function() {
this.listenTo(this.collection, 'sync', this.render);
},
render: function() { /**/ }
});
var collection = new AccountsCollection();
var view = new AccountsView({collection:collection});
Third option
var AccountsCollection = Backbone.Collection.extend({
model: Account,
url: 'api/v1/accounts',
initialize: function () {
this.deferred = this.fetch();
}
});
var AccountsView = Backbone.View.extend({
initialize: function() {
this.collection.deferred.done(_.bind(this.render, this));
},
render: function() { /**/ }
});
var collection = new AccountsCollection();
var view = new AccountsView({collection:collection});
I've manage to put together the below code through various examples, which seems to work okay, but it doesn't seem to preload my data, can anyone please tell me what I'm missing?
App = (function(Backbone, _){
var Note = Backbone.Model.extend(
{
defaults:
{
part1: 'hello',
part2: 'world'
}
});
var TableList = Backbone.Collection.extend({
model: Note
});
var ListRow = Backbone.View.extend(
{
tagName: 'li',
initialize: function()
{
_.bindAll(this, 'render');
},
render: function()
{
$(this.el).html('<span>'+this.model.get('part1')+' '+this.model.get('part2')+'</span>');
return this;
}
});
var ListView = Backbone.View.extend(
{
el: $('#layout_content'),
events:
{
'click button#add': 'addItem'
},
initialize: function()
{
_.bindAll(this, 'render', 'addItem', 'appendItem');
this.collection = new TableList();
this.collection.bind('add', this.appendItem);
this.counter = 0;
this.render();
},
render: function()
{
var self = this;
$(this.el).append("<button id='add'>Add list item</button>");
$(this.el).append("<ul></ul>");
_(this.collection.models).each(function(item){ // in case collection is not empty
self.appendItem(item);
}, this);
},
addItem: function()
{
this.counter++;
var note = new Note();
note.set({part2: note.get('part2') + this.counter});
this.collection.add(note);
},
appendItem: function(item)
{
var listRow = new ListRow({
model: item
});
$('ul', this.el).append(listRow.render().el);
}
});
var app = function(initialModels)
{
this.start = function()
{
this.tableList = new TableList();
this.listView = new ListView({collection: this.tableList});
this.tableList.reset(initialModels);
};
};
return app;
})(Backbone, _);
then init the app with:
<script language="javascript">
var app = new App([{"id":"95","note_title":"can we find the title"},{"id":"93","note_title":"some title"}]);
app.start();
</script>
okay, there are a few issues with your code,
there are 2 issues in your start method,
a) you throw away your collection
this.start = function()
{
this.tableList = new TableList();
this.listView = new ListView({collection: this.tableList});
this.tableList.reset(initialModels);
};
and then in intialize is where you overwrite the collection you pass along
initialize: function()
{
_.bindAll(this, 'render', 'addItem', 'appendItem');
this.collection = new TableList(); // this one gets overwritten, remove this line
}
b) you trigger a collection reset with the models you want to populate it with, but don't listen to an event, either add a listener like this:
this.collection.bind('reset', this.appendAllItems, this);
or create your collection like this:
this.start = function()
{
this.tableList = new TableList(initialModels);
this.listView = new ListView({collection: this.tableList});
};