Backbone call a model method from the view - backbone.js

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);

Related

Type Error on Backbone/Marionette Single Model Fetch

I am getting used to using Backbone and Marionette and run into a little snag that I am sure I am overlooking something. I am trying to populate my ItemView with a model from my API and I can see the request and data coming back ok but I get a Type Error:obj is undefined in what appears to be my listener:
TypeError: obj is undefined
var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
Here is my Model/View
var MyDetailView = Marionette.ItemView.extend({
template: '#my-item-detail',
initialize: function () {
_.bindAll(this, 'render');
// bind the model change to re-render this view
this.listenTo(this.model, 'change', this.render);
},
tagName: "div"
})
var MyModel= Backbone.Model.extend({ urlRoot: '/api/model', intialize: function () { } });
And my code to execute:
var m = new MyModel({ id: 123});
m.fetch({
success: function (model, response) {
var view = new MyDetailView (model);
layout.content.show(view);
}
});
You'll need to pass the model in as an options hash and not just the first parameter to MyDetailView like so:
var view = new MyDetailView({ model: model });
Also for future reference Marionette does _.bindAll with render in the Marionette.View constructor.

backbone .bind('change', _.bind(this.render, this)) not registering

I have a measure model, that is made up of two collections, a beats collections, and a measureRep[resentation] collection. Each collection is made of beat models and representation models respectively.
Whenever a measureRep[resentation] collection changes (by addition or subtraction of a representation model), I want the measureView (which has the measure model, and therefore the measureRep[resentation] collection) to re-render itself using the render function().
I am adding a new model in another View by the following function:
var representationModel = new RepresentationModel({representationType: newRepType});
StageCollection.get(cid).get('measures').models[0].get('measureRepresentations').add(representationModel);
I can see that before and after the addition that the measure and its measureRep collection are getting added correctly, however, the bind call on the measureView is not registering the change and calling the render function. I even put the bind on the model, to show that the backend is getting updated, however, it doesn't respond. This leads me to believe that the View and the models are decoupled, but that doesn't make sense, since it originally renders from the model. Here are the relevant files:
measureView.js [View]:
define([...], function(...){
return Backbone.View.extend({
initialize: function(options){
if (options) {
for (var key in options) {
this[key] = options[key];
}
this.el = '#measure-container-'+options.parent.cid;
}
window.css = this.collectionOfRepresentations; //I use these to attach it to the window to verify that the are getting updated correctly
window.csd = this.model; // Same
_.bindAll(this, 'render'); // I have optionally included this and it hasn't helped
this.collectionOfRepresentations.bind('change', _.bind(this.render, this));
this.render();
},
render: function(){
// Make a template for the measure and append the MeasureTemplate
var measureTemplateParameters = { ... };
var compiledMeasureTemplate = _.template( MeasureTemplate, measureTemplateParameters );
// If we are adding a rep, clear the current reps, then add the template
$(this.el).html('');
$(this.el).append( compiledMeasureTemplate )
// for each rep in the measuresCollection
_.each(this.collectionOfRepresentations.models, function(rep, repIndex) {
var measureRepViewParamaters = { ... };
new MeasureRepView(measureRepViewParamaters);
}, this);
return this;
},
...
});
});
measure.js [Model]:
define([ ... ], function(_, Backbone, BeatsCollection, RepresentationsCollection) {
var MeasureModel = Backbone.Model.extend({
defaults: {
beats: BeatsCollection,
measureRepresentations: RepresentationsCollection
},
initialize: function(){
var logg = function() { console.log('changed'); };
this.measureRepresentations.bind('change', logg);
this.bind('change', logg);
}
});
return MeasureModel;
});
representations.js [Collection]:
define([ ... ], function($, _, Backbone, RepresentationModel){
var RepresentationsCollection = Backbone.Collection.extend({
model: RepresentationModel,
initialize: function(){
}
});
return RepresentationsCollection;
});
I have also tried registering the bind on the measure model, and not its child collection, but neither work.
_.bindAll(this, 'render');
this.model.bind('change', _.bind(this.render, this));
see: https://stackoverflow.com/a/8175141/1449799
In order to detect additions of models to a collection, you need to listen for the add event (not the change event, which will fire when a model in the collection is changed http://documentcloud.github.io/backbone/#Events-catalog ).
so try:
this.measureRepresentations.bind('add', logg);

Module method is not being called with ()

For some reason I can't get the addOne method to fire in this view. The render method fires just fine.The collection is being passed in from another module that instantiates it. When I add the parentheses to the method call it will fire but then Underscore throws an error.
define(['backbone',
'jquery',
'underscore',
'views/user',
'models/user',
],
function(Backbone,$,_,UserView,UserModel){
var Users= Backbone.View.extend({
tagName:'ul',
className:'well',
initialize:function() {
_.bindAll(this);
},
render:function() {
this.collection.each( this.addOne, this );
return this;
},
addOne:function() {
console.log('inside of addOne');
var userView = new UserView({model:UserModel});
this.$el.append(userView.render().el);
}
});
return Users;
});
Your UserView is a model view. This means that you need to pass to it an instance of a model and not the definition of the model's module, which is what the "UserModel" holds in your case.
Example:
var um = new UserModel({name: 'foo', id: 1});
var uv = new UserView({model: um}); //um is an instance of the model 'UserModel'
In your case, since the model objects are coming from iterating over this.collection, it would be (as correctly noted above by mu is too short):
addOne: function(m) {
console.log('inside of addOne');
var userView = new UserView({model: m});
this.$el.append(userView.render().el);
}
Also be careful so that this.collection is not empty when you call render().

Backbone view event atacched to all views

I'm doing my first application in backbone and i get a strange thing happening trying to attach an event.
I got this code so far:
//View for #girl, EDIT action
GirlEditView = Backbone.View.extend({
initialize: function(el, attr) {
this.variables = attr;
console.log(attr);
this.render();
},
render: function() {
var template = _.template( $("#girl_edit").html(), this.variables );
$(this.el).html( template );
$("#edit_girl").modal('show');
}
});
//View for #girl
GirlView = Backbone.View.extend({
initialize: function(el, attr) {
this.variables = attr;
this.render();
},
render: function() {
var template = _.template( $("#girl_template").html(), this.variables );
$(this.el).html( $(this.el).html() + template );
},
events: {
"click p.modify": "modify"
},
modify: function() {
//calls to modify view
new GirlEditView({el : $("#edit_girl")}, this.variables);
}
});
//One girl from the list
Girl = Backbone.Model.extend({
initialize: function() {
this.view = new GirlView({el : $("#content")}, this.attributes );
}
});
//all the girls
Girls = Backbone.Collection.extend({
model: Girl,
});
//do magic!
$(document).ready(function() {
//Underscore template modification
_.templateSettings = {
escape : /\{\[([\s\S]+?)\]\}/g,
evaluate : /\{\[([\s\S]+?)\]\}/g,
interpolate : /\{\{([\s\S]+?)\}\}/g
}
//get initial data and fill the index
var list = [];
$.getJSON('girls.json', function(data) {
list = [];
$.each(data, function(key, val) {
list.push( new Girl(val) );
});
var myGirls = new Girls(list);
console.log( myGirls.models);
});
});
As you can see.
I'm using a collection to store all the girls and the data comes from a REST api in ruby.
Each girls create a new model instance and inside i attached a view instance.
I don't know if it's a good practice but i can't think a better way to do it.
Each view makes a content with a unique id. girl-1 girl-2 and go on.
Now, the template have a edit button.
My original idea is to attack the onclick event and trigger the edit view to get rendered.
That is working as expected.
The proble so far is:
When the events triggers, all the collection (girls) fire the edit view, not the one that "owns" the rendered view.
My question is what i'm doing wrong?
Thanks a lot
All the edit-views come up because all the GirlViews are using the same el:
this.view = new GirlView({el : $("#content")}, this.attributes );
and then you render be appending more HTML:
render: function() {
var template = _.template( $("#girl_template").html(), this.variables );
$(this.el).html( $(this.el).html() + template );
}
Backbone events are bound using delegate on the view's el. So, if multiple views share the same el, you'll have multiple delegates attached to the same DOM element and your events will be a mess of infighting.
You have things a little backwards: models do not own views, views watch models and collections and respond to their events. You'll see this right in the documentation:
constructor / initialize new View([options])
[...] There are several special options that, if passed, will be attached directly to the view: model, collection, [...]
Generally, you create a collection, c, and then create the view by handing it that collection:
var v = new View({ collection: c })
or you create a model, m, and then create a view wrapped around that model:
var v = new View({ model: m })
Then the view binds to events on the collection or model so that it can update its display as the underlying data changes. The view also acts as a controller in Backbone and forwards user actions to the model or collection.
Your initialization should look more like this:
$.getJSON('girls.json', function(data) {
$.each(data, function(key, val) {
list.push(new Girl(val));
});
var myGirls = new Girls(list);
var v = new GirlsView({ collection: myGirls });
});
and then GirlsView would spin through the collection and create separate GirlViews for each model:
var _this = this;
this.collection.each(function(girl) {
var v = new GirlView({ model: girl });
_this.$el.append(v.render().el);
});
Then, GirlView would render like this:
// This could go in initialize() if you're not certain that the
// DOM will be ready when the view is created.
template: _.template($('#girl_template').html()),
render: function() {
this.$el.html(this.template(this.model.toJSON());
return this;
}
The result is that each per-model view will have its own distinct el to localize the events. This also makes adding and removing a GirlView quite easy as everything is nicely wrapped up in its own el.

How to trigger the fetch method and how to set the url

I'am redesigning my backbone application based on the answer of #20100 to this question The best way to fetch and render a collection for a given object_id.
Please read the comment on the code because I think is more clear, and my question looks better in smaller sizes.
// My View
define([
"js/collections/myCollection",
"js/models/myFeed"
], function (MyCollection, MyModel) {
var MyView = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.collection = new MyCollection();
this.collection.on('add', this.onAddOne, this);
this.collection.on('reset', this.onAddAll, this);
// when I make myView = new MyView(_.extend( {el:this.$("#myView")} , this.options));
// myView.render is not called
// in order to trigger the render function I make the following… but probably there is a better way …
var that = this;
this.collection.fetch({
success: function () {
that.render();
}
});
}
});
return MyView;
});
// MyCollection
define([
"js/models/myModel"
], function (MyModel) {
var MyCollection = Backbone.MyCollection.extend({
model: MyModel, // add this
url: function () {
var url = "http://localhost/movies";
return url;
// if I look to the GET request the url is without idAttribute
// how can I attach the idAttribute to this url?
// should bb takes care of this?
}
});
return MyCollection;
});
//MyModel
define([
], function () {
var MyModel = Backbone.MyModel.extend({
idAttribute: 'object_id'
});
return MyModel
});
There's two paths you want to explore
Pre-populate your collection with your model data
In your example you're already doing this, but you're fetching a collection, the collection URL is http://localhost/movies, if you want an individual model take a look at the next point
Fetch each individual model only when you need it
In the assumption that you're trying to get an ID on a collection that is not pre-populated and are loading 1 model at a time, you will have to approach this a bit in a custom way by adding a method to your collection somewhat similarly to this
getOrFetch: function(id, options)
{
var model;
if (this.get(id))
{
model = this.get(id);
}
else
{
model = new this.model({
id: id
});
this.add(model);
model.fetch(options);
}
return model;
}
or add the function as Backbone.Collection.prototype.getOrFetch so you can use it on every Backbone Collection if you need it.

Resources