I am new to BackboneJS and I am stuck with nested relations using Backbone-relational Model with RequireJS -I think I runned into circular issues. Any help will be highly appreciated!
I have the following model and collection:
/* Module Model at models/module*/
define([
'jquery',
'underscore',
'backbone',
'backboneRelational',
], function($, _, Backbone) {
var ModuleModel = Backbone.RelationalModel.extend({
urlRoot: 'api/module',
_radius: 50,
relations: [{
type: Backbone.HasMany,
key: 'children',
relatedModel: 'ModuleModel',
collectionType: 'ModuleCollection',
reverseRelation: {
key: 'parent_id',
includeInJSON: 'id'
}
}],
url: function() {
return this.id? 'api/module/' + this.id : 'api/module';
}
});
return ModuleModel;
});
/* Module Collection */
define([
'jquery',
'underscore',
'backbone',
'models/module'
], function($, _, Backbone, ModuleModel) {
var ModuleCollection = Backbone.Collection.extend({
model: ModuleModel,
url: 'api/modules'
});
return ModuleCollection;
});
When I initialize the object ModuleModel, it throws the following error:
Relation=child; no model, key or relatedModel (function (){ parent.apply(this, arguments); }, "children", undefined)
Could you point me to the right direction?
This looks like a scoping issue. During initialization of ModuleModel it wants to create a hasMany relation with itself, but it can't find itself and it will give you grief in form of said error:
http://jsfiddle.net/yNLbq
Once the object is reachable from the current scope things start to work out:
http://jsfiddle.net/jDw5e
A possible solution would be to give models and collection a namespace for themselves which can be reached from the current scope.
Hope this helps.
I came across this problem from here:
RequireJS + BackboneRelational + Self-Referential. He seems to have inherited some of his problems from this thread so I thought I might add my dime.
First, since you're using RequireJS, there are no global variables. You can't simply supply the name of the object, you need to supply actual object references for relatedModel and collectionType.
Your trickiest issue is that ModuleModel's relatedModel is actually ModuleModel itself, which won't be defined when you assign it to relatedModel (using the AMD model). You have to defer assignment until after ModuleModel is assigned.
Finally, you need to resolve the circular reference. dokkaebi is on the right track when he suggests using exports, but his implementation actually misuses exports. When exporting, attach the object directly to exports as he suggests, but when you import it you need to reference the module to use it, not exports.
This should work:
ModuleModel.js
define(['exports', 'ModuleCollection'], function (exports, Module) {
'use strict';
var ModuleModel = Backbone.RelationalModel.extend({
urlRoot: 'api/module',
_radius: 50,
relations: [{
type: Backbone.HasMany,
key: 'children',
// ModuleModel is undefined; this line is useless
// relatedModel: ModuleModel,
// no globals in require; must use imported obj ref
collectionType: Module.Collection,
reverseRelation: {
key: 'parent_id',
includeInJSON: 'id'
}
}],
url: function() {
return this.id? 'api/module/' + this.id : 'api/module';
}
});
// Now that `ModuleModel` is defined, we can supply a valid object reference:
ModuleModel.prototype.relations[0].relatedModel = ModuleModel;
// Attach `Model` to `exports` so an obj ref can be obtained elsewhere
exports.Model = ModuleModel;
});
ModuleCollection.js
define(['exports', 'ModuleModel'], function(exports, Module) {
'use strict';
var ModuleCollection = Backbone.Collection.extend({
// must reference the imported Model
model: Module.Model,
url: 'data.php' // <-- or wherever
});
// Attach `Collection` to `exports` so an obj ref can be obtained elsewhere
exports.Collection = ModuleCollection;
});
Main.js
define(['ModuleCollection'], function(Module) {
'use strict';
var modules = new Module.Collection();
modules.fetch().done(function() {
modules.each(function(model) {
console.log(model);
});
});
});
From a comment in backbone-relational.js v0.5.0 (line 375):
// 'exports' should be the global object where 'relatedModel' can be found on if given as a string.
If you require the special 'exports' value as a dependency in your define call, and then place your module onto the exports object before you return, then you can reference that module as a string or as a member of exports.
in ModuleModel.js:
define(['exports', 'use!backbone', 'use!backbone-relational'], function(exports, Backbone) {
var ModuleModel = Backbone.RelationalModel.extend({
relations: [
{
type: Backbone.HasMany,
key: 'groups',
relatedModel: 'ModuleModel',
collectionType: 'ModuleCollection'
}
]
});
exports.ModuleModel = ModuleModel;
return ModuleModel;
});
and in ModuleCollection.js:
define(['exports', 'use!backbone'], function(exports, Backbone) {
var ModuleCollection = Backbone.RelationalModel.extend({
url: '/api/v1/module/';
model: exports.ModuleModel;
});
exports.ModuleCollection = ModuleCollection;
return ModuleCollection;
});
I ran into the same problem a while back and followed the approach used by Andrew Ferk for his question: Backbone-relational submodels with RequireJS. The problem arises because you're defining models as Require modules so they don't exist on the global scope, where backbone-relational can look for them. Instead of using global scope (beats the purpose of Require) or exports (bit tricky with the relations), you can define a scope for your models and tell backbone-relational to look for models in it, with addModelScope().
//modelStore.js - A scope in which backbone-relational will search for models
//Defined separately so you can access 'modelStore' directly for your models instead of requiring 'app' every time.
define(['app'], function(app) {
app.modelStore = {};
Backbone.Relational.store.addModelScope(app.modelStore);
return app.modelStore;
}
By the way you should shim your Backbone dependency and not need to require jQuery and Underscore for it.
//ModuleModel (The ModuleModel module. Nice.)
define(['modelStore', 'backbone', 'backboneRelational'], function(modelStore, Backbone {
modelStore.ModuleModel = Backbone.RelationalModel.extend({
relations: [
{
type: Backbone.HasMany,
key: 'groups',
relatedModel: 'ModuleModel', //Not modelStore.ModuleModel
collectionType: 'ModuleCollection'
}
]
});
return modelStore.ModuleModel;
});
Kinda late for this now but hope it helps others.
Related
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 found this example on here of how to use the HBS plug-in to manage templates. It seems like a great solution. #machineghost suggests using RequireJS to include templates like this:
define(['template!path/to/someTemplate'], function(someTemplate) {
var MyNewView = BaseView.extend({template: someTemplate});
$('body').append(new MyNewView().render().el);
}
This is great, except I need to dynamically switch templates. Here is an example of one of my views:
define([
'jquery',
'underscore',
'backbone',
'models/tableModel',
'collections/tablesCollection',
'views/tablesView'
], function($, _, Backbone, tableModel, tablesCollection, tablesView) {
var t = new tablesCollection(null, {url: 'applications-lab'});
return new tablesView({ collection: t, template: 'applications-lab-template', url: 'applications-lab'});
});
As you can seem, I'm passing in the template when the view is rendered. What I'm wondering is can I pass in a variable to the define statement that would tell Backbone which template path to use? I'm a newbie to Backbone and especially RequireJS, and am not sure. Suggestions anyone?
Preliminary notes:
require.js does not allow parameters in a module definition, define accepts a dependency array and a definition function :
define(['dep1', 'dep2', ...], function(dep1, dep2) {
})
I would not define a view, instantiate it and inject its el in the same module but feel free to mix and match to your taste
Let's start with a module defining a simple view with a default template, let's say views/templated.js
define(['backbone', 'hbs!path/to/defaultTemplate'],
function(Backbone, defaultTemplate) {
var MyNewView = Backbone.View.extend({
template: defaultTemplate,
initialize: function(opts) {
opts = opts || {};
// use the template defined in the options or on the prototype
this.template = opts.template || this.template;
}
});
return MyNewView;
});
Now you just have to pull you view definition and an optional template with require:
require(['views/templated', 'hbs!path/to/anotherTemplate'],
function(MyNewView, anotherTemplate) {
// a view with the default template
var v1 = new MyNewView();
// a view with a new template
var v2 = new MyNewView({
template: anotherTemplate
});
});
To create a new class with an overridden default template, you would define a new module (views/override.js)
define(['views/templated', 'hbs!path/to/anotherTemplate'],
function(MyNewView, anotherTemplate) {
var AnotherNewView = MyNewView.extend({
template: anotherTemplate
});
return AnotherNewView;
});
Finally, you can always change the template on a given instance by directly assigning a new value.
var v = new MyNewView();
v.template = tpl;
A Fiddle simulating the views hierarchy : http://jsfiddle.net/nikoshr/URddR/
Coming back to your code, your blocks could look like
require(['models/tableModel', 'collections/tablesCollection', 'views/templated', 'applications-lab-template'],
function(tableModel, tablesCollection, tablesView, tpl) {
var t = new tablesCollection(null, {url: 'applications-lab'});
var v = new tablesView({
collection: t,
template: tpl
url: 'applications-lab'
});
// or, if you prefer and you don't render in initialize
v.template = tpl;
});
I would like to use RelationalModel using requireJs.
Here my code(*)
When I run my module, I get the following warning message:
Relation=d;
no model, key or relatedModel (function (){a.apply(this,arguments)},
"tasks",
undefined).
My questions are:
1) what does the warning message means?
2) relatedModel and collectionType are well defined in my relations or should I export the model and the collection in define call?
(*)
define([
'backbone',
'relationalModel'
], function (Backbone) {
"use strict";
var User = Backbone.RelationalModel.extend({
relations: [{
type: Backbone.HasMany,
key: 'tasks',
relatedModel: 'Task',
collectionType: 'TaskCollection',
reverseRelation: {
key: 'hasUser',
includeInJSON: 'id'
// 'relatedModel' is automatically set to 'User'; the 'relationType' to 'HasOne'.
}
}]
});
return User;
});
Look at this question: Creating nested models with backboneJS + backbone-relational + requireJS
By the way, exports.ModuleModel = ModuleModel; doesn't work for me. Instead, I use window.ModuleModel = ModuleModel. Yes, it's a bit ugly, but it works.
I built file structure for javascript game using RequireJs and Backbone. Now I have problem to combine it with Box2DWeb.
// Filename: game/controller/arena.js
define([
'jquery',
'underscore',
'backbone',
'_69_',
'game/controller/object',
'game/model/arena',
'game/view/arena',
'box2d'
], function($, _, Backbone, _69_, Object, Model, View, Box2D){
var ArenaController = Object.extend ({
init : function (){
this._super(Model, View);
this.world = new b2World(
new b2Vec2(0, 10) //gravity
, true //allow sleep
);
this.appView;
this.loops=0;
},
start : function (){
_69_.l('start')
},
update :function (){
_69_.l('update')
},
stop : function (){
_69_.l('stop')
}
});
return new ArenaController;
});
But in console I get that b2World is not defined. What i should do to get it work?
I dont know box2d but box2world does indeed not exist in that context unless it is a global object.
I presume here that you will need its namespace, if it is included in the box2d reference you defined in the required.js dependencies you might use
new Box2D.b2World(...);
you should try to including "box2D" in the require of your main.js file. Box2D will load as a global object which you can use later.
require([
'app',
'box2d'
], function(app){
app.init();
});
You shouldn't mention it in the function or you could list it as "ignore":
require([
'app',
'box2d'
], function(app, ignore){
app.init();
});
In the Chrome console, you could type Box2D and you should see it pop up as a global object. Once you know its global, you can start using it.
Then in your ArenaController you can list "box2d" in the define array but not in the function:
define([
'jquery',
'underscore',
'backbone',
'_69_',
'game/controller/object',
'game/model/arena',
'game/view/arena',
'box2d'
], function($, _, Backbone, _69_, Object, Model, View) {
var ArenaController = Object.extend ({
init : function (){
this._super(Model, View);
var b2World = Box2D.Dynamics.b2World; //Box2D should be available as a global
this.world = new b2World(
new b2Vec2(0, 10) //gravity
, true //allow sleep
);
this.appView;
}
});
return new ArenaController;
});
I'm in the process of creating a Backbone.js app using Require.js. Each view file corresponds to one resource (e.g. 'News'). Within each view file, I declare a backbone
view for each action ('index', 'new', etc). At the bottom of the view file I receive
the necessary info from the router and then decide which view to instantiate (based on the info passed in from the router).
This all works well, but it requires lots of code and doesn't seem to be the 'backbone.js way'. For one thing, I'm rellying on the url to manage state. For another, I'm not using _.bind which pops up in a lot of backbone.js examples. In other words, I don't think I'm doing it right, and my code base smells... Any thoughts on how to structure my app better?
router.js
define([
'jquery',
'underscore',
'backbone',
'views/news'],
function($, _, Backbone, newsView){
var AppRouter = Backbone.Router.extend({
routes:{
'news':'news',
'news/:action':'news',
'news/:action/:id':'news'
},
news: function(action, id){
newsView(this, action, id).render();
}
});
var intialize = function(){
new AppRouter;
Backbone.history.start()
};
return{
initialize: initialize;
};
}
news.js ('views/news')
define([
'jquery',
'underscore',
'backbone',
'collections/news',
'text!templates/news/index.html',
'text!templates/news/form.html'
], function($, _, Backbone, newsCollection, newsIndexTemplate, newsFormTemplate){
var indexNewsView = Backbone.View.extend({
el: $("#content"),
initialize: function(router){
...
},
render: function(){
...
}
});
var newNewsView = Backbone.View.extend({
el: $("#modal"),
render: function(){
...
}
});
...
/*
* SUB ROUTER ACTIONS
*/
var defaultAction = function(router){
return new newsIndexView(router);
}
var subRouter = {
undefined: function(router){return defaultAction(router);},
'index': function(router){ return defaultAction(router);},
'new': function(){
return new newNewsView()
},
'create': function(router){
unsavedModel = {
title : $(".modal-body form input[name=title]").val(),
body : $(".modal-body form textarea").val()
};
return new createNewsView(router, unsavedModel);
},
'edit': function(router, id){
return new editNewsView(router, id);
},
'update': function(router, id){
unsavedModel = {
title : $(".modal-body form input[name=title]").val(),
body : $(".modal-body form textarea").val()
};
return new updateNewsView(router, id, unsavedModel);
},
}
return function(router, action, id){
var re = /^(index)$|^(edit)$|^(update)$|^(new)$|^(create)$/
if(action != undefined && !re.test(action)){
router.navigate('/news',true);
}
return subRouter[action](router, id);
}
});
While I feel like it's important to emphasize that there isn't really a "Backbone.js way", it does seem like you're replicating work Backbone should be doing for you.
I agree that it makes sense to have a specialized Router for each independent section of your application. But it looks at first glance like what you're doing in your "sub-router" section is just recreating the Backbone.Router functionality. Your AppRouter doesn't need to deal with /news URLs at all; you can just initialize a NewsRouter with news-specific routes, and it will deal with news-related URLs:
var NewsRouter = Backbone.Router.extend({
routes:{
'news': 'index',
'news/create': 'create',
'news/update/:id': 'update',
'news/edit/:id': 'edit'
},
index: function() { ... },
create: function() { ... },
// etc
});
As long as this is initialized before you call Backbone.history.start(), it will capture URL requests for its routes, and you never have to deal with the AppRouter. You also don't need to deal with the ugly bit of code at the bottom of your view - that's basically just doing what the core Backbone.Router does for you.
I'm using require.js and backbone as well I think the main difference that i'd suggest is that each file should return just one view, model, router or collection.
so my main html page requires my main router. That router is a module that requires a few views based on each of it's routes, and a bootstrapped model. Each router method passes the relevant bootstrapped model piece to the relevant view.
From there it stays really clean as long as each file is just 1 backbone thing (model, collection, view, router) and requires just the elements it uses. This makes for a lot of js files (I have about 100 for my current project) but that's where require.js optimization comes into play.
I hope that helps.
Why don't you structure your routes like this:
routes:{
'news':'news',
'news/edit/:id':'editNews',
'news/new':'newNews',
...
}