How to stub require.js module in Backbone initialize method - backbone.js

Hello I want to be able to stub requirejs modules that get called in the initialize of a Backbone object. For example:
define(function(require){
var Backbone = require("backbone"),
BalanceModule = require("BalanceModule");
return Backbone.View.extend({
initialize: function(){
this.balance = BalanceModule.requestBalance();
}
});
})
I want to be able to stub the BalanceModule but because is in the initialize method I am not able to.
I could stub the initialize method but then it means that I have the code to be tested in the tests.
I tried to require.undef() the module and load another one with no luck. It seems that once you have the object initialised it has an internal reference not able to override.
Any ideas?

You can modify your BalanceModule like this:
define({
var BalanceModule = function(){
}
return BalanceModule;
});
Instead of:
define({
var BalanceModule = function(){
}
return new BalanceModule();
});
So you can use like this:
define(function(require){
var Backbone = require("backbone"),
BalanceModule = require("BalanceModule");
return Backbone.View.extend({
initialize: function(){
this.balance = new BalanceModule().requestBalance();
}
});
})

Related

Add to backbone collection in response to event

I'm getting started with backbone.js but have got stuck at this point: I want to get data from a websocket and add it to a collection.
I've started with the code from http://adrianmejia.com/blog/2012/09/11/backbone-dot-js-for-absolute-beginners-getting-started/ but when I try to add in the event handling I cant figure out how to access the collection from within the event.
var AppView = Backbone.View.extend({
// el - stands for element. Every view has a element associate in with HTML content will be rendered.
el: '#container',
template: _.template("<h3>Hello <%= who %></h3>"),
// It's the first function called when this view it's instantiated.
initialize: function(){
console.log(this.collection) //<-- this.collection is OK here
MyPubSub.on('message', function (evt) {
this.collection.add(evt.data) //<-- TypeError: this.collection is undefined
});
this.render();
},
// $el - it's a cached jQuery object (el), in which you can use jQuery functions to push content. Like the Hello World in this case.
render: function(){
this.$el.html(this.template({who: 'planet!'}));
}
});
function init()
{
var websocket = new WebSocket("ws://example.com:8088");
MyPubSub = $.extend({}, Backbone.Events);
websocket.onmessage = function(evt) {
console.log("message")
MyPubSub.trigger('message',evt)
};
var appView = new AppView({collection:new AlertCollection()});
}
window.addEventListener("load", init, false);
I'm a complete newbie at this so I suspect I've made some kind of basic error in scoping. Also open to other approaches to read a websocket steam into a backbone app.
In the initialize function, add var myCollection = this.collection; and use myCollection within the MyPubSub.on(....

backbone handlebars and requirejs: handlebars returns a function instead of html

This is my simple BackboneView when I try to load the widget.tpl.
However the template var contains a function()
What do I do wrong ?
define(['hbs!templates/widget'],function (template) {
var template = Handlebars.compile( template );
console.log(template);
MyView = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
// Compile the template using Handlebars
console.log(template);
//this.el.html( template );
}
});
return MyView;
});
widget.tpl has
<div>helloworld</div>
Careful not to redefine the template variable. The hbs plugin returns the compiled Handlebars function, as you can see in the docs. It would probably be better like this:
define(['hbs!templates/widget'],function (template) {
console.log(template); // -> logs compiled Handlebars template function
var MyView = Backbone.View.extend({
render: function(){
// Compile the template using Handlebars
this.$el.append(template());
return this;
}
});
return MyView;
});
Also, you probably don't want to render the view in initialize. Initialize should only set up your view; rendering within initialize introduces unwanted side effects when creating an instance. Somewhere else in your code you'll have this:
var MyView = require('MyView');
var view = new MyView();
$('body').append(view.render().el);

Backbone call a model method from the view

I have a backbone app with require where I want to add a collection inside a collection with a method inside model.
I have tried to insert the method in the collection but I can't add elements.
I'd want to make a collection of app when I click an element outside the app I want add inside my app other app in a collection.
This is my app:
Model:
define(['backbone', 'collections/element'],function(Backbone, ElementCollection){
var DesignModel = Backbone.Model.extend({
initialize:function(){
console.log('Initialized Design model');
_.defaults(this, {
elements: new ElementCollection()
});
},
addElement: function(elements, options) {
return this.elements.add(elements, options);
}
});
return DesignModel;
});
Collection:
define(['backbone', 'models/design'], function(Backbone, DesignModel){
var designCollection = Backbone.Collection.extend({
model: DesignModel,
});
return designCollection;
});
View
define(['jquery' , 'backbone', 'models/design', 'collections/design', 'views/element'],
function($, Backbone, DesignModel, DesignCollection, ElementView){
var DesignView = Backbone.View.extend({
el:$('#page'),
initialize: function(){
console.log('initialize DesignView');
this.collection = new DesignCollection();
var here = this;
$('#insert-dynamic-element').click(function(){
var element = new ElementView();
here.collection.models.addElement(element);
});
},
render: function(){
}
})
return DesignView;
});
I have tried to call the function addElement in this way:
here.collection.models.addElement(element);
and
here.collection.addElement(element);
But always with error that Object has no method addElement
How can I solve this? I want to call the method addElement from the view to add an app inside another app in a collection.
Thanks
The safest way to call the method is to add the method to the collection instead of the Model. Currently the method is available on the Model instance .
So this.collection.models.addElement will not cut it
Collection
define(['backbone', 'models/design'], function(Backbone, DesignModel){
var designCollection = Backbone.Collection.extend({
model: DesignModel,
addElement: function(elements, options) {
return this.add(elements, options);
}
});
return designCollection;
});
View
define(['jquery' , 'backbone', 'models/design', 'collections/design', 'views/element'],
function($, Backbone, DesignModel, DesignCollection, ElementView){
var DesignView = Backbone.View.extend({
el:$('#page'),
initialize: function(){
console.log('initialize DesignView');
this.collection = new DesignCollection();
var here = this;
$('#insert-dynamic-element').click(function(){
var element = new ElementView();
here.collection.addElement(element);
});
},
render: function(){
}
})
return DesignView;
});
If you do not want to move the method from the current model. Then you might have to call a specific model using the index
here.collection.at(0).addElement(element);
But there might be a case when there are no model in the collection and this might lead to a error condition..
here.collection.at(0) && here.collection.at(0).addElement(element);
Well, you need to get a specific model, not the array of them. This seems like an error since you'll be picking a specific model essentially arbitrarily (unless you application has semantics that support this), but this would work:
here.collection.at(0).addElement(element);

Strange behaviour of this in backbone.js Controller

Yes I am new to JS and also in backbonejs.
Lets dig into the problem now.
I am having a very strange behaviour of this in backbonejs Controller.
Here is the code of my controller
var controller = Backbone.Controller.extend( {
_index: null,
routes: {
"": "index"
},
initialize: function(options){
var self = this;
if (this._index === null){
$.getJSON('data/album1.json',function(data) {
//this line is working self._index is being set
self._index = new sphinx.views.IndexView({model: self._photos});
});
Backbone.history.loadUrl();
}
},
index: function() {
//this line is not working
//here THIS is pointing to a different object
//rather than it was available through THIS in initialize method
this._index.render();
}
});
Here is the lines at the end of the file to initiate controller.
removeFallbacks();
gallery = new controller;
Backbone.history.start();
Now , i am missing something. But what ???
If this is the wrong way what is the right way??
I need to access the properties i set from the initialize method from index method.
It looks like the caller function of index method is changing it's scope.
I need to preserve the scope of that.
You have to specify the route action into a Backbone Route not into a Controller. Inside the router is where you are going to initialize your controller and views.
Also, there is no method Backbone.history.loadURL(). I think you should use instead Backbone.history.start() and then call the navigate in the router instance e.g. router.navigate('state or URL');
var myApp = Backbone.Router.extend( {
_index: null,
routes: {
"": "index"
},
initialize: function(options){
//Initialize your app here
this.myApp = new myApp();
//Initialize your views here
this.myAppViews = new myAppView(/* args */);
var self = this;
if (this._index === null){
$.getJSON('data/album1.json',function(data) {
//this line is working self._index is being set
self._index = new sphinx.views.IndexView({model: self._photos});
});
Backbone.history.loadUrl(); //Change this to Backbone.history.start();
}
},
// Route actions
index: function() {
this._index.render();
}
});

Failing to pass models correctly from collection view?

I've been staring at this for a while and trying various tweaks, to no avail.
Why am I getting a "this.model is undefined" error at
$(function(){
window.Sentence = Backbone.Model.extend({
initialize: function() {
console.log(this.toJSON())
}
});
window.Text = Backbone.Collection.extend({
model : Sentence,
initialize: function(models, options){
this.url = options.url;
}
});
window.SentenceView = Backbone.View.extend({
initialize : function(){
_.bindAll(this, 'render');
this.template = _.template($('#sentence_template').html());
},
render : function(){
var rendered = this.template(this.model.toJSON());
$(this.el).html(rendered);
return this;
}
})
window.TextView = Backbone.View.extend({
el : $('#notebook') ,
initialize : function(){
_.bindAll(this, 'render');
},
render : function(){
this.collection.each(function(sentence){
if (sentence === undefined){
console.log('sentence was undefined');
};
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
});
return this;
}
});
function Notebook(params){
this.text = new Text(
// models
{},
// params
{
url: params.url
}
);
this.start = function(){
this.text.fetch();
this.textView = new TextView({
collection: this.text
});
$('body').append(this.textView.render().el);
};
}
window.notebook = new Notebook(
{ 'url': 'js/mandarin.js' }
);
window.notebook.start();
})
There's an online version wher eyou can see the error in a console at:
http://lotsofwords.org/languages/chinese/notebook/
The whole repo is at:
https://github.com/amundo/notebook/
The offending line appears to be at:
https://github.com/amundo/notebook/blob/master/js/notebook.js#L31
I find this perplexing because as far as I can tell the iteration in TextView.render has the right _.each syntax, I just can't figure out why the Sentence models aren't showing up as they should.
var view = new SentenceView({model: sentence});
I'm pretty sure when you pass data to a backbone view constructor, the data is added to the Backbone.View.options property.
Change this line
var rendered = this.template(this.model.toJSON());
to this
var rendered = this.template(this.options.model.toJSON());
and see if it works
UPDATE:
From the doco:
When creating a new View, the options you pass are attached to the view as this.options, for future reference. There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, and tagName
So, disregard the above advice - the model should by default be attached directly to the object
Things to check next when debugging:
confirm from within the render() method that this is actually the SentenceView object
confirm that you are not passing in an undefined sentence here:
var view = new SentenceView({model: sentence});
UPDATE2:
It looks like the collection is borked then:
this.textView = new TextView({
collection: this.text
});
To debug it further you'll need to examine it and work out what's going on. When I looked in firebug, the collection property didn't look right to me.
You could have a timing issue too. I thought the fetch was asynchronous, so you probably don't want to assign the collection to the TextView until you are sure it has completed.
Backbone surfaces underscore.js collection methods for you so you can do this. See if this works for you:
this.collection.each(function(sentence) {
// YOUR CODE HERE
});
I think the problem is on line 48 of notebook.js shown below:
render : function(){
_(this.collection).each(function(sentence){
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
});
The problem is you are wrapping the collection and you don't have to. Change it to
this.collection.each(function(sentence){ ...
hope that fixes it
EDIT:
OK i'm going to take another crack at it now that you mentioned timing in one of your comments
take a look at where you are fetching and change it to this:
this.start = function(){
this.text.fetch({
success: _.bind( function() {
this.textView = new TextView({
collection: this.text
});
$('body').append(this.textView.render().el);
}, this)
);
};
I typed this manually so there may be mismatching parentheses. The key is that fetch is async.
Hope this fixes it
try using _.each
_.each(this.collection, function(sentence){
if (sentence === undefined){
console.log('sentence was undefined');
};
var view = new SentenceView({model: sentence});
this.$('ol#sentences').append(view.render().el);
},this);

Resources