I am trying to trigger behaviour on a collection by triggering an event elsewhere in my app. I am pretty new to backbone, so probably have all my syntax wrong, but this seems like it should work
fiddle
var Test = Backbone.Model.extend({
});
var Tests = Backbone.Collection.extend({
model: Test,
events: {
"testEvent":"testResponce"
},
testResponce: function(){
alert('hello world');
}
});
var myTest = new Test();
myTest.trigger('testEvent');
Can this be done? Where am I going wrong?
If you want to call a particular method of your collection using testEvent event then you can take this path also. Working Demo
var Test = Backbone.Model.extend({
initialize: function() {
this.on('testEvent', function() {
console.log(this.collection);
this.collection.testResponce();
});
}
});
var Tests = Backbone.Collection.extend({
model: Test,
testResponce: function(){
alert('hello world');
}
});
var myTest = new Test();
var testList = new Tests([myTest]);
myTest.trigger('testEvent');
The event that you are triggering will have to be caught inside the model.
If you want to catch an event inside collection you can use Backbone.on and Backbone.trigger OR collection.on and collection.trigger
Please check the fiddle below for an example
fiddle
var Test = Backbone.Model.extend({});
var Tests = Backbone.Collection.extend({
model: Test,
initialize: function () {
Backbone.on('testEvent', function () {
alert('event handled in collection');
});
}
});
var myCollection = new Tests();
var myTest = new Test();
Backbone.trigger('testEvent');
UPDATE
Collection has an initialize method which you can use to register events on. Later you can trigger these events from their instances.
Other way as NULL suggested is to do it like below.
var Test = Backbone.Model.extend({});
var Tests = Backbone.Collection.extend({
model: Test,
initialize: function () {
this.on('testEvent', function () {
alert('event handled in collection');
});
}
});
var myCollection = new Tests();
var myTest = new Test();
myCollection.trigger('testEvent');
Related
I want to display a list of artists based on the letter users can click on an alphabet. But somehow, even though the letter is passed, I get an error e.g. it says the letter is undefined despite the fact my console.log gives me the correct letter. Here is what I did sofar:
My artistsAll.js module:
var ArtistsAll = App.module();
var ArtistsAllView = Backbone.View.extend({
tagName : 'li',
template: 'artistItem',
serialize: function() {
return this.model.toJSON();
}
});
ArtistsAll.View = Backbone.View.extend({
tagName : 'ul',
className : 'artistList',
initialize: function() {
this.collection.on('sync', _.bind(this.render, this));
},
beforeRender: function() {
var self = this;
this.collection.each(function(item) {
self.insertView(new ArtistsAllView({model: item}))
})
}
});
ArtistsAll.ArtistsAllCollection = Backbone.Collection.extend({
url: function() {
return App.APIO + '/i/search/artist?name=' + this.letter;
}
});
return ArtistsAll;
Basically, there is an endpoint for each letter, for example /i/search/artist?name=b
Then, in my controller I have:
var ArtistsAllModule = require('modules/artistsAll');
ArtistController.prototype.initArtistLetter = function(letter) {
this.artistsAllCollection = new ArtistsAllModule.ArtistsAllCollection();
this.artistsAllCollection.letter = letter;
App.useLayout('artistLetter', 'artistLetter').setViews({
'.artistsDiv': new ArtistsAllModule.View({collection: this.artistsAllCollection});
}).render();
this.artistsAllCollection.fetch();
};
All I get, is empty page, no errors.... can someone tell me what the issue is here?
Try to modify your code like the following:
ArtistsAll.View = Backbone.View.extend({
tagName : 'ul',
className : 'artistList',
initialize: function() {
// here's the modification
this.listenTo(this.collection, 'sync', this.render, this); // but where's your render function!
},
My function getLink(rel) calls a function that does not exist in the testing environment.
getLink: function(rel) {
var schemaLink = null;
var schemaLinks = this.collection.getItemSchema().links;
schemaLinks.forEach(function(link) {
if(link.rel === rel) {
schemaLink = link;
return false;
}
});
return schemaLink;
},
this.collection does not exist and I wouldn't want to test it as I want to isolate the object I am currently testing. How would I spy this function (or stub it, whatever is the thing to do, but I think it's stub) with Jasmine 2.0?
You can call your function in context of spy object using call method. It will look something like this:
describe('getLink()', function(){
var result, fooLink, barLink, fakeContext;
beforeEach(function(){
fakeContext = {
collection: jasmine.createSpyObj('collection', ['getItemSchema']);
};
fooLink = {rel: 'foo'};
barLink = {rel: 'bar'};
fakeContext.collection.getItemSchema.andReturn([fooLink, barLink]);
});
desctibe('when schemaLink exists', function(){
beforeEach(function(){
result = getLink.call(fakeContext, 'foo')
});
it('calls getItemSchame on collection', function(){
expect(fakeContext.collection.getItemSchame).toHaveBeenCalledWith();
});
it('returns fooLink', function(){
expect(result).toBe(fooLink);
});
});
desctibe('when schemaLink does not exist', function(){
...
});
});
How can I modify a single model from different views?
Pseudo code:
var myModel = Backbone.Model.extend({url: 'rest'});
var myCollection = Backbone.Model.extend({});
var myView1 = Backbone.Views.extend({
initialize: function() {
// sets a data in model so it can be use by second and third view
myModel().fetch();
},
events: {
'event': 'callSecondView',
'event': 'callThirdView'
},
callSecondView: function() {
// second view will use the current records in myModel that
// was created during initialization or from the modification
// of 1st or third view
new myView2({model: myModel})
},
callThirdView: function() {
// third view will use the current records in myModel that
// was created during initialization
// of 1st or third view
new myView3({model: myModel})
}
});
var myView2 = Backbone.Views.extend({
events: {
'event': 'modifyMyModel'
},
modifyMyModel: function() {
this.model.set(etc)
// Modifying model here should affect the 1st and 3rd view's model
}
});
var myView3 = Backbone.Views.extend({
events: {
'event': 'modifyMyModel'
},
modifyMyModel: function() {
this.model.set(etc)
// Modifying model here should affect the 1st and 2nd view's model
}
});
Is this possible? or should I just combine all the three views in a single view?(this will result into a monster view)
It looks very ugly why don't you fetch the model first then create how many views with it as you want:
var MyModel = Backbone.Model.extend({url: '/api/data'});
var View1 = Backbone.View.extend({});
var View2 = Backbone.View.extend({});
var View3 = Backbone.View.extend({});
var myModel = new MyModel();
myModel.fetch({
success: function(){
window.myView1 = new View1({model:myModel});
window.myView2 = new View1({model:myModel});
window.myView3 = new View1({model:myModel});
}
});
// in console
myView1.model == myModel
true
I've spent the past two weeks trying to learn Backbone.js and then also modularizing the app with Require.js. But seems there are something that I'm not getting in the whole process of initialization and fetching.
I have two routes, one shows an entire collection while the other shows just an individual model. And I want to be able to start the app with any of both routes.
If I start loading the collections url and later on a single model url, everything works as expected. If I start the app with the url route to a single model I got the error: TypeError: this.model is undefined this.$el.html(tmpl(this.model.toJSON()));on the view.
If I set defaults for the model, it renders the view but doesn't fetch it with the real data. I've tried also to handle the success event in the fetch function of the model without any luck.
router.js
define(['jquery','underscore','backbone','models/offer','collections/offers','views/header','views/event','views/offer/list',
], function($, _, Backbone, OfferModel, OffersCollection, HeaderView, EventView, OfferListView){
var AppRouter = Backbone.Router.extend({
routes: {
'event/:id' : 'showEvent',
'*path': 'showOffers'
},
initialize : function() {
this.offersCollection = new OffersCollection();
this.offersCollection.fetch();
var headerView = new HeaderView();
$('#header').html(headerView.render().el);
},
showEvent : function(id) {
if (this.offersCollection) {
this.offerModel = this.offersCollection.get(id);
} else {
this.offerModel = new OfferModel({id: id});
this.offerModel.fetch();
}
var eventView = new EventView({model: this.offerModel});
$('#main').html(eventView.render().el);
},
showOffers : function(path) {
if (path === 'betting' || path === 'score') {
var offerListView = new OfferListView({collection: this.offersCollection, mainTemplate: path});
$('#main').html(offerListView.render().el) ;
}
},
});
var initialize = function(){
window.router = new AppRouter;
Backbone.history.start();
};
return {
initialize: initialize
};
});
views/event.js
define(['jquery','underscore','backbone','text!templates/event/main.html',
], function($, _, Backbone, eventMainTemplate){
var EventView = Backbone.View.extend({
initalize : function(options) {
this.model = options.model;
this.model.on("change", this.render);
},
render : function() {
var tmpl = _.template(eventMainTemplate);
this.$el.html(tmpl(this.model.toJSON()));
return this;
}
});
return EventView;
});
You are creating and fetching the OffersCollection in initialize method of the router, so the else block in showEvent will never be hit since this.offersCollection is always truthy.
After the comments, I think you need to do this:
showEvent : function(id) {
var that = this;
var show = function(){
var eventView = new EventView({model: that.offerModel});
$('#main').html(eventView.render().el);
};
// offersCollection is always defined, so check if it has the model
if (this.offersCollection && this.offersCollection.get(id)) {
this.offerModel = this.offersCollection.get(id);
show();
} else {
this.offerModel = new OfferModel({id: id});
this.offerModel.fetch().done(function(){
// model is fetched, show now to avoid your render problems.
show();
});
// alternatively, set the defaults in the model,
// so you don't need to wait for the fetch to complete.
}
}
Trying to get my head around backbone.js. This example is using Backbone Boilerplate and Backbone.localStorage and I'm coming up against a confusing problem; When quizes.create(...) is called I get this error:
backbone.js:570 - Uncaught TypeError: object is not a function
model = new this.model(attrs, {collection: this});
Quiz module code:
(function(Quiz) {
Quiz.Model = Backbone.Model.extend({ /* ... */ });
Quiz.Collection = Backbone.Collection.extend({
model: Quiz,
localStorage: new Store("quizes")
});
quizes = new Quiz.Collection;
Quiz.Router = Backbone.Router.extend({ /* ... */ });
Quiz.Views.Question = Backbone.View.extend({
template: "app/templates/quiz.html",
events: {
'click #save': 'saveForm'
},
initialize: function(){
_.bindAll(this);
this.counter = 0;
},
render: function(done) {
var view = this;
namespace.fetchTemplate(this.template, function(tmpl) {
view.el.innerHTML = tmpl();
done(view.el);
});
},
saveForm: function(data){
if (this.counter <= 0) {
$('#saved ul').html('');
}
this.counter++;
var titleField = $('#title').val();
console.log(quizes);
quizes.create({title: titleField});
}
});
})(namespace.module("quiz"));
In your Collection, you're naming model as your Quiz object, not the actual Quiz.Model. So, when you call new this.model(), you're actually calling Quiz() - which is an object, not a function. You need to change the code to:
Quiz.Collection = Backbone.Collection.extend({
model: Quiz.Model, // Change this to the actual model instance
localStorage: new Store("quizes")
});