I am building my first Backbone app with similar structure to this Todo MVC example with Require.js and also using Backbone LocalStorage. Problem is when I run TweetsCollection.fetch() in HomeView, firebug gives me error: TypeError: options is undefined var method = options.update ? 'update' : 'reset';
TweetsCollection:
define([
'underscore',
'backbone',
'backboneLocalStorage',
'models/TweetModel'
], function(_, Backbone, Store, TweetModel) {
'use strict';
var TweetsCollection = Backbone.Collection.extend({
model: TweetModel,
localStorage: new Store('tweets-storage'),
initialize: function() {
console.log('Collection init...');
}
});
return new TweetsCollection();
});
HomeView init:
initialize: function() {
this.listenTo(TweetsCollection, 'add', this.addOne);
this.listenTo(TweetsCollection, 'reset', this.addAll);
this.listenTo(TweetsCollection, 'all', this.render);
TweetsCollection.fetch(); // <- Error here
},
I try to follow the example above, but I'm really lost with this.
The line of code where the error occurs is in Backbone's success callback that gets executed by Backbone.sync. Here's what that method looks like in Backbone 0.9.10:
options.success = function(collection, resp, options) {
var method = options.update ? 'update' : 'reset';
collection[method](resp, options);
if (success) success(collection, resp, options);
};
Prior to version 0.9.10, Backbone callback signature was:
options.success = function(resp, status, xhr) { ...
The Backbone.localStorage plugin, which you are evidently using, executes the callback method as follows (line 146):
if (options && options.success)
options.success(resp);
As you can see, it doesn't pass the arguments in correct order, and is missing the options argument altogether, which is where you are seeing the error.
So it would seem that the Backbone.localStorage plugin is currently incompatible with the newest Backbone version.
Edit: I went to report this issue to the author of the localStorage plugin, but looks like there is already a GitHub issue and pull request to fix this. It's not merged yet, so in the meantime you can use phoey's fork or downgrade to Backbone 0.9.9
Related
I am using a model to fetch location data from a service using POST.
However I am struggling to get the view to listen to that model when it finally receives a response.
Note I have overriden some model methods to fit the service I am using.
Model code
define([
'underscore',
'backbone',
], function (_, Backbone)
{
'use strict';
//model class declaration
var LocationsModel = Backbone.Model.extend({
locations: null, //this attribute stores the json response
//url for http post to get location data
url: '/testserver/getLocationData',
/**
#name constructor
#method initialise
**/
initialize: function ()
{
console.log("Location Model - Initialize");
this.fetchLocationData();
},
/**
#name fetchLocationData
#method fetchLocationData
#summary retrieves bulding/location data from service, overriden fetch function to use POST
**/
fetchLocationData: function ()
{
console.log("Location Model - Fetching Building/Location data from EAS");
this.fetch({
data: JSON.stringify({ "lastRefreshedDateTime": "2015-04-01T08:18:06.000+00:00", "uid": "" }), //refactor this - string literals which are static currently
type: "POST",
contentType: "application/json",
async : false, //this is bad but it is the only way I can get it to work
at the moment
reset: true //tried adding reset parameter but no luck
});
},
/**
#name parse
#method parse
#summary parse is a method inside backbone that can be overridden, in this override we set a model attribute to the JSOn response
**/
parse: function (response, xhr)
{
console.log("Location Model - Parsing response from EAS");
this.attributes = { "true": true }; //tried adding an attribute value to trigger "change:true"
this.locations = response;
//do other stuff
},
});
return LocationsModel;
});
In the view initialiser I have tried the following binds and listen to on the model however they don't seem to trigger after a response.
View code
model : new LocationModel(),
initialize: function ()
{
console.log("Filter View - Initialize");
this.render();
this.listenTo(this.model, 'change', this.render); //this only fires at the start when the model is initialised
this.model.on('change', this.render, this); //same as above
this.listenTo(this.model, 'reset', this.render); //not fired at all
},
For a collection it was fairly simple to listen to any changes that happened, however it seems to be a different ball game for Backbone models.
TLDR, how can I get the view to listen to a model successfull fetch request?
The sync event is the one you want to listenTo. It gets fired once a fetch has completed successfully. This line in the Backbone source is the culprit: Backbone source, line 578.
Your code should now look something like this:
View code
model : new LocationModel(),
initialize: function ()
{
console.log("Filter View - Initialize");
this.render();
this.listenTo(this.model, "sync", this.render);
},
Here's a fiddle which shows your code working. I used httpbin to mock the POST request. You can also remove the async: false parameter in the fetch request now :) (I've removed it in the fiddle example).
I will explain my problem with an example. I can make this piece of code work without any problem (using MarionetteJS v1.6.2):
http://codepen.io/jackocnr/pen/tvqHa
But when I try to use it with requireJs and I put it on the initialize method of a Marionette Controller, I'm Getting the following error:
Uncaught TypeError: undefined is not a function backbone.marionette.js:2089
The Error comes when I define the collection view:
var userListView = new UserListView({
collection: userList
});
I Can't figure out what is happening (this is the same code of the link above, but inside the controller initialize method)
define([
'jquery',
'underscore',
'backbone',
'marionette'
], function($,_,Backbone,Marionette){
var Controller = Backbone.Marionette.Controller.extend({
initialize: function(){
var User = Backbone.Model.extend({});
var UserList = Backbone.Collection.extend({
model: User
});
var UserView = Backbone.Marionette.ItemView.extend({
template: _.template($("#user-template").html())
});
var UserListView = Backbone.Marionette.CollectionView.extend({
tagName: "ul",
itemView: UserView,
initialize: function() {
this.listenTo(this.collection, "add", this.render);
}
});
// instances
var jack = new User({name: "Jack"});
var userList = new UserList(jack);
var userListView = new UserListView({
collection: userList
});
// add to page
$("#user-list").append(userListView.render().el);
$("#add-user").click(function() {
var andy = new User({name: "Andy"});
userList.add(andy);
});
},
});
return Controller;
});
instead of using Backbone.Marionette in main.js shim : { use Marionette
marionette: {
exports: 'Marionette',
deps: ['backbone']
},
Thus while declaring any marionette inheritance juste use Marionette instead of Backbone.Marionette as such
var Controller = Marionette.Controller.extend
var UserView = Marionette.ItemView.extend
var UserListView = Marionette.CollectionView.extend
For some reason the newer version or Marionette.js behave this way. I guest it produce less code.
I have replaced the Marionette 1.6.2 version with the 1.5, and now it works as it does the version without requireJs. So I think it's a release bug or something like that.
Seems to be working fine for me. I made a simple project here.
I am writing a backbone (with require) application and need to search through a collection to pull out the first model (I'm using a unique id so there will only be one).
The issue I'm having is that I'm getting an error:
Uncaught TypeError: Object [object Object] has no method 'findWhere'
When it get to the line with the findWhere command.
The view initialization is:
initialize: function (models) {
this.locationCollection = new LocationCollection();
this.locationCollection.fetch();
this.render();
},
I then access the locationCollection later in another method, the first line of the method is where the error occurs:
createCrate: function (eventname) {
var setLocationList = this.locationCollection.findWhere({ Name: $('#location').val() });
console.log($('#location').val());
console.log(setLocationList);
},
Here is the declaration code the LocationCollection:
define([
'jquery',
'underscore',
'backbone',
'model/LocationModel'
], function ($, _, Backbone, LocationModel) {
var LocationCollection = Backbone.Collection.extend({
model: LocationModel,
url: "/api/locations"
});
return LocationCollection;
});
I can access the items in this.localCollection elsewhere in the view and output them into a backbone typeahead extension, so the collection has been populated.
Any idea why this collection cannot call findWhere?
_.findWhere was introduced in Underscore 1.4.4
and Backbone proxied it in Backbone 1.0
Make sure you have the adequate versions and your error should go away.
I have this collection view
define([
'jquery',
'underscore',
'backbone',
'views/project',
'collections/project-collection',
'templates'
], function ($, _, Backbone, ProjectView, ProjectCollection, JST) {
'use strict';
var ProjectListView = Backbone.View.extend({
template: JST['app/scripts/templates/projectList.ejs'],
el: $('#content'),
render: function() {
var projectCollection = new ProjectCollection();
projectCollection.fetch();
projectCollection.each(this.addOne(),this);
return this;
},
addOne: function(project) {
console.log('addOne function');
var projectView = new ProjectView({model: project});
this.$el.html( projectView.render().el);
}
});
return ProjectListView;
});
No matter what I try the model never gets passed through to the addOne function so the in the view that is instantiated by this method the call to
this.model.toJSON()
results in the old 'cannot call method .toJSON of undefined' error. I tried to inject the collection when this collection view was instantiated and that didn't work either. Obviously here it is in the dependency array and that doesn't work either. The model is definitely there as I can log projectCollection.model to the console inside the render function. I'm stumped.
I see two problems with your render: one you know about and one you don't.
The first problem is right here:
projectCollection.each(this.addOne(), this);
The parentheses on this.addOne() call the addOne method right there rather than passing the this.addOne function to each as a callback. You want this:
projectCollection.each(this.addOne, this);
The second problem is that you have to wait for the collection's fetch to return before anything will be in the collection. You can use the fetch's callbacks:
var _this = this;
projectCollection.fetch({
success: function() {
projectCollection.each(_this.addOne, _this);
}
});
or you can use the various events that fetch will fire, see the fetch documentation for details.
In this simple Require/Backbone app
https://github.com/thisishardcoded/require-prob
Why does app.js see Router but TestView.js not?
Here is the first line of app.js
define(['router'],function (Router) {
and here is the first line of TestView.js
define(['backbone','router'],function(Backbone,Router){
Check out the repo for full details, download, run and check console log if you feel so inclined
Thanks!
Jim
More: Ok, the answer is - because of the order in which it is loaded and even if that were altered I have a circular dependency don't I? TestView needs Router, Router needs TestView.
In which case the solution might be
var r=require('router);
r.navigate or whatever
but, that seems like a shame that Router is not directly accessible everywhere and, is the above method good practice anyway?
Surely it happens because of circular dependency. To resolve it, you either pass router to view's constructor and remove router dependency from view's module, or use require('router') in your view.
1st option, router.js:
app_router.on('route:test', function () {
var testView = new TestView({router: app_router});
testView.render();
})
1st option, view.js:
define(['backbone'], function(Backbone){
var TestView=Backbone.View.extend({
el: '#test',
initialize: function() {
// get router from constructior options
this.router = this.options.router
},
render: function(){
this.$el.html('<p>Foo!</p>');
console.log("TestView.js does not find 'Router',", this.router);
}
});
return TestView;
});
2nd option, view.js:
define(['backbone','router'], function(Backbone, router){
// at this point router module is not loaded yet so router is undefined
var TestView=Backbone.View.extend({
el: '#test',
initialize: function() {
// at this point router module is loaded and you may access it with require call
this.router = require('router');
},
render: function(){
this.$el.html('<p>Foo!</p>');
console.log("TestView.js does not find 'Router',", this.router);
}
});
return TestView;
});
2nd option is also described here: http://requirejs.org/docs/api.html#circular
You should define baseUrl property in your main.js file that contains RequireJS config.
In this way all paths to modules in your application will be relative to that baseUrl.
See:
http://requirejs.org/docs/api.html#jsfiles
http://requirejs.org/docs/api.html#config-baseUrl
I downloaded and inspected your code. Following could be the issues:
require.js only works with AMDs. Since backbone no longer supports AMD. You will need to use AMD enabled version of Backbone. You can get it here
TestView is the dependency in you Router. So it loads before the Router is loaded.
You might want to improve the coding pattern. Here is my suggestion:
App.js
define([
'backbone',
'router',
], function(Backbone, MainRouter){
'use strict';
var AppView = Backbone.View.extend({
initialize: function(){
App.router = new MainRouter();
Backbone.history.start();
}
});
return AppView;
});
Router.js
define([
'backbone',
'view/TestView'
], function(Backbone, TestView){
var Main = Backbone.Router.extend({
routes: {
'test': 'test'
},
test: function(){
new TestView({
// pass model or collection to the view
// model: new TestModel // remember to require
});
}
});
return Main;
});
EDIT
Listening to events:
// in main.js
var window.Vent = {};
_.extend(window.Vent, Backbone.Events);
// now in any view you can trigger a event
$('something').on('click', function(){
window.Vent.trigger('somethinghappened', this);
// this is reference to current object
});
// now in other view you can do
window.Vent.on('somethinghappened', this.run, this);
// this in the end is the reference we passed when event was triggered
run: function(obj){
//this function will run when the event is triggered
// obj is the object who triggered the event
}
PS: why do you want to use router in view?? I have built quite a few backbone apps. Never needed to do so.
You can use available Backbone.history.navigate to achieve your goal easier, because Router.navigate is a simple wrapper for it. Consider this part of Backbone source:
navigate: function(fragment, options) {
Backbone.history.navigate(fragment, options);
return this;
},