How to do nested models in Backbone - javascript

Possibly related: Nested Models in Backbone.js, how to approach
Code snippet:
define([
'jquery',
'backbone',
], function($, Backbone){
var GenreModel = Backbone.Model.extend({
defaults: {
currentGenre: null,
availableGenres: []
},
initialize: function() {}
});
var MusicModel = Backbone.Model.extend({
defaults: {
genre: new GenreModel()
},
initialize: function() {}
});
// Return the model for the module
return MusicModel;
});
Is the above practice acceptable? I only have one level of nested models MusicModel.get('genre'). The reason I am doing this is that while I want to listen to the changes in the genre model, I don't want to create another view for genre because it's too small. I'd appreciate any suggestions!

Related

Backbone bind event when reload template

I just started to lean Backbone, and underscore template, not sure if the structure suitable for it.
The question is, when I reload a template, how to re-bind event from Backbone which is re-run the events function.
The example is simply load an index page, insert main_option template into the page, and jump between main_option, and role_view template.
Here is the app.js which I put router in there:
define(['jquery', 'underscore', 'backbone', 'views/role_view', 'views/main_options'], function ($, _, Backbone, rolePage, mainOptions) {
var appRouter = Backbone.Router.extend({
$el: $('.container'),
initialize: function () {
this.mainOptionPage = mainOptions;
this.loginView = rolePage;
},
routes: {
"": "mainOption",
"views/role_view": "login"
},
mainOption: function () {
this.$el.html(this.mainOptionPage.render().$el);
},
login: function () {
this.$el.html(this.loginView.render().$el);
}
});
var router = new appRouter();
Backbone.history.start();
});
Here is the main_option.js
define(['jquery', 'underscore', 'backbone'], function($, _, Backbone){
var Person = Backbone.Model.extend({
defaults: {
name: 'Guest Worker',
age: 23,
occupation: 'worker'
}
});
var testView = Backbone.View.extend({
$el: $('#indexPage'),
initialize: function () {
var self = this;
$.get('/test/templates/mainOptions.html').success(function (data) {
self.template_loaded(data);
template = _.template(data, {name: "Test"});
}, 'html');
},
events: {
'click .signButton': 'pageToSign'
},
pageToSign: function (e) {
e.preventDefault();
Backbone.history.navigate("views/role_view", {trigger: true});
},
template_loaded: function (html) {
var template = _.template(html, {name: "Test"});
this.$el.html(template);
return this;
}
});
var person = new Person;
return new testView({model: person});
});
and final page is role_view.js
define(['jquery', 'underscore', 'backbone'], function($, _, Backbone){
var role = Backbone.View.extend({
initialize: function(){
var self = this;
$.get('/test/templates/chooseRole.html').success(function(html){
self.template_loaded(html);
});
},
events: {
'click .parentButton': 'parentClick'
},
template_loaded: function(html) {
var template = _.template(html, {name: "Test"});
this.$el.html(template);
return this;
},
parentClick: function(e) {
e.preventDefault();
Backbone.history.navigate("", {trigger: true});
}
});
return new role();
});
Thanks.
You real problem is that you're reusing views rather than destroying and creating them as needed. In your router, you have this:
mainOption: function () {
this.$el.html(this.mainOptionPage.render().$el);
},
login: function () {
this.$el.html(this.loginView.render().$el);
}
You call this.$el.html the first time, the view goes up, and everything seems to be okay. Then you switch views by calling this.$el.html and everything still seems to be okay. But the next time you switch views, your events are gone. This happens because of the way jQuery's html function works; from the fine manual:
When .html() is used to set an element's content, any content that was in that element is completely replaced by the new content. Additionally, jQuery removes other constructs such as data and event handlers from child elements before replacing those elements with the new content.
Emphasis mine. Calling this.$el.html will destroy the event bindings on the previous content (such as this.mainOptionsPage.el or this.loginView.el).
If you create and destroy views as needed:
define(['jquery', 'underscore', 'backbone'], function($, _, Backbone){
// Your Person model goes in its own file or possibly in the router file for now...
var TestView = Backbone.View.extend({
//...
});
return TestView; // Return the view "class", not an instance.
});
define(['jquery', 'underscore', 'backbone'], function($, _, Backbone){
var Role = Backbone.View.extend({
//...
});
return Role;
});
define(['jquery', 'underscore', 'backbone', 'views/role_view', 'views/main_options', 'models/person'], function ($, _, Backbone, Role, TestView, Person) {
var person = new Person; // The person model is managed here.
var appRouter = Backbone.Router.extend({
//...
initialize: function () {
// Don't need anything in here anymore.
},
//...
mainOption: function () {
// Create a new view when we need it.
this.switchTo(new TestView({ model: person }));
},
login: function() {
// Create a new view when we need it.
this.switchTo(new Role);
},
switchTo: function(view) {
// Destroy the old view since we don't need it anymore.
if(this.currentView)
this.currentView.remove();
// Keep track of the new current view so that we can
// kill it alter and avoid leaks.
this.currentView = view;
this.$el.html(this.currentView.render().el);
}
});
//...
});

Backbone.js - Pass variable from route to view/collection/model

So I'm building a mobile website and I have a directory called 'api' with various php files hat echo JSON formatted data from a remote API. I did this to avoid the cross-domain issue.
But one of the php files needs a GET parameter (i.e. id) so that I can echo the JSON data for a specific object based on it's id.
My collection will need to do this (assuming this will work):
define([
'backbone',
'models/tournaments/single'
], function(Backbone, singleModel) {
var TournamentCollection = Backbone.Collection.extend({
model: singleModel,
url: '/api/tournament.php?id=' + id,
parse: function(response) {
return response;
}
});
return TournamentCollection;
});
I have this in my router, but how do I pass the 'id' value to the view or collection:
define([
'jquery',
'underscore',
'backbone',
'views/home',
'views/tournament'
], function($, _, Backbone, HomeView, TournamentView) {
var AppRouter = Backbone.Router.extend({
routes: {
'': 'home',
'tournament/:id': 'tournament'
}
});
var initialize = function() {
var app_router = new AppRouter;
app_router.on('route:home', function() {
var homeView = new HomeView();
});
app_router.on('route:tournament', function(id) {
var tournamentView = new TournamentView({id: id});
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
Couple of things:
1) Your definition of the url property of the collection will not work as id is likely not defined when defining the TournamentCollection class. You can use a function rather than a property. TournamentCollection will become something like this:
define([
'backbone',
'models/tournaments/single'
], function(Backbone, singleModel) {
var TournamentCollection = Backbone.Collection.extend({
model: singleModel,
initialize: function (options) {
this.id = options.id;
},
url: function () {
return '/api/tournament.php?id=' + this.id
},
parse: function(response) {
return response;
}
});
return TournamentCollection;
});
This way you can initialize the object with an id, and later, when the url is fetched it will include the correct id.
2) I would probably initialize and fetch the collection from the router. Then from the initialize of the view, listen for that fetch to complete and ultimately re-render the view. Something like this:
define([
'jquery',
'underscore',
'backbone',
'views/home',
'views/tournament'
], function($, _, Backbone, HomeView, TournamentView) {
var AppRouter = Backbone.Router.extend({
routes: {
'': 'home',
'tournament/:id': 'tournament'
}
});
var initialize = function() {
var app_router = new AppRouter;
app_router.on('route:home', function() {
var homeView = new HomeView();
});
app_router.on('route:tournament', function(id) {
var tournaments = new TournamentCollection({ id: id });
tournaments.fetch();
var tournamentView = new TournamentView({ collection: tournaments });
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
// Tournament View define stuff
var TournamentView = Backbone.View.extend({
initialize: function () {
this.listenTo(this.collection, 'sync', this.render);
},
render: function () {
//...
}
});
return TournamentView
hope that helps. :)

Using backbone and passing a parameter to a model

I'm using backbone.marionette for view control.
My issue is "How do you pass a parameter to a model?"
This is what I have tried:
define([
'jquery',
'underscore',
'backbone',
'models/CampaginModel',
'collections/CampaignCollection',
'text!templates/includes/_campaign.html'
], function ($, _, Backbone, CampaginModel, CampaignCollection, campaignTemplate) {
var campaginView = Backbone.Marionette.ItemView.extend({
template: campaignTemplate,
initialize: function (options) {
this.campaign_id = options.id;
},
model: CampaginModel({id: this.campaign_id}),
onRender: function () {
}
}); // end campagin view
return campaginView;
});
I have noticed that my parameter get passed to the view init function I'm kinda stuck after this point. In standard backbone I just created a new model in the render function and passed the parameter to the model that way. However Marionette views have a 'model' attribute which I think should allow me to pass in it there, but it does not!
Model:
define([
'underscore',
'backbone',
'jquery'
], function (_, Backbone, jquery) {
var CampaginModel = Backbone.Model.extend({
urlRoot: '/api/v1/campaign/',
// Model Constructor
initialize: function () {
},
});
return CampaginModel;
});
I don't know what your file structure looks like.
But it should be like something like this.
define([
'jquery',
'underscore',
'backbone',
'models/CampaginModel',
'collections/CampaignCollection',
'text!templates/includes/_campaign.html'
], function ($, _, Backbone, CampaginModel, CampaignCollection, campaignTemplate) {
var campaginView = Backbone.Marionette.ItemView.extend({
template: campaignTemplate,
initialize: function (options) {
this.campaign_id = options.id;
this.model.set({id: this.campaign_id});
},
model: CampaginModel,
onRender: function () {
}
}); // end campagin view
return campaginView;
});
I haven't test the code yet.
If you need to set your parameters to model, you have to use backbone's model.set() function

Backbone.js Subview rendering

I am trying to get to grips with Backbone.js and have been following the 'modular' approach outlined by Thomas Davis here.
I seem to be stuck when trying to 'include' a view within another view as follows:
SettingsView.js
define([
'jquery',
'underscore',
'backbone',
'text!templates/settings/settingsTemplate.html'
], function($, _, Backbone, settingsTemplate){
var SettingsView = Backbone.View.extend({
el: $("#interface"),
render: function() {
this.$el.html(settingsTemplate);
}
});
return SettingsView;
});
ScriptView.js
define([
'jquery',
'underscore',
'backbone',
'views/settings/SettingsView',
'models/script/ScriptModel',
'collections/scripts/ScriptsCollection',
'text!templates/script/scriptTemplate.html'
],
function($, _, Backbone, SettingsView, ScriptModel, ScriptsCollection, scriptTemplate) {
var ScriptView = Backbone.View.extend({
el : $("#container"),
render : function() {
this.$el.html(scriptTemplate);
//add the settings view
var settingsView = new SettingsView();
settingsView.render();
}
});
return ScriptView;
});
UPDATED: I have managed to fix the error which I have just realised as to why that was happening (params were in the wrong order - DOH!!)
I no longer get the error but my 'SettingsView' is still not appearing. When I console log this inside 'ScriptView.js' I see that 'el' is undefined so my thinking is that this is where the issue may be...?
Thanks
I think you have to append the SettingsView to the el of the ScriptView:
var ScriptView = Backbone.View.extend({
el : $("#container"),
render : function() {
this.$el.html(scriptTemplate);
//add the settings view
var settingsView = new SettingsView();
settingsView.render();
this.$el.append(settingsView.el);
}
});
And a great post about subviews is this one.
I hope it helps!

Proper way to extend a Backbone Object using Require JS

I am using requireJS in combination with backbone:
define([
"jquery",
"underscore",
"backbone",
"models/modelA"
], function( $, _, Backbone, MyModel ) {
var viewA = Backbone.View.extend({
initialize: function() {
this.model = new MyModel();
}
});
return viewA;
});
I want to create a new View module, ViewB, that has all of the same methods as viewA, but that uses a different model in place of modelA. I know I can override the initialize function, but I am wondering if there is a more concise way, to avoid duplication of code...
define([
"jquery",
"underscore",
"backbone",
"views/viewA",
"models/modelB"
], function( $, _, Backbone, ViewA, myModel ) {
var viewB = ViewA.extend({
initialize: function() {
this.model = new MyModel();
}
});
return viewB;
});
If the two views are exactly the same, just pass in the instantiated model instead of creating it in initialize. Then you only need to define one view.
define([
"jquery",
"underscore",
"backbone"
// no model module needed here.
], function( $, _, Backbone ) {
var viewA = Backbone.View.extend({
initialize: function() {
}
});
return viewA;
});
Then in some other module which requires both models and the view:
var modelA = new ModelA();
var modelB = new ModelB();
// create 2 instances of ViewA with different model passed in
var viewA = new ViewA({model: modelA});
var viewB = new ViewA({model: modelB});
When created like this, each view will have this.model set to the instance you pass in.
define(['views/ViewA'], function() {
var ViewA = require('views/ViewA'), // require ViewA
ViewB = ViewA.extend({
// add all your ViewB specific methods/properties here
});
return ViewB; // return ViewB from the RequireJS module so it can be used elsewhere
});

Categories