Passing Backbone Model attribute to Handlebars Helper - javascript

The Backbone model has the attribute "selectedYear". I need to pass this "selectedYear" attribute to Handlebars Custom Helper.
var sq2SelectCarYearModel = Backbone.Model.extend({
urlRoot: "api/caryears",
selectedYear: "0"
});
Here is what I have tried:
This is the handlebars templating:
template: Handlebars.compile("{{#times 27 this.model.attributes.selectedYear}}{{/times}}")
Handlebars Helper declaration:
Handlebars.registerHelper('times', function(n, selectedYear, block) {
// I need to use "this.model.attributes.selectedYear" here
});
PS: "times" is the name of the custom helper, "n" is the number of times a loop will run.
I also tried this:
template: Handlebars.compile("{{#times 27 selectedYear}}{{/times}}")
but it still doesnt work.

selectedYear is not an attribute on your model, but a property.
You can set it as a default by doing:
var CarModel = Backbone.Model.extend({
urlRoot: 'api/caryears',
defaults: {
selectedYear: 0
}
});
var model = new CarModel()
// model.get('selectedYear') -> 0
you can also pass it in on instantiation
var model2 = new Model({ selectedYear: 2 });
// model2.get('selectedYear') -> 2
or you can set it after instantiation:
var model3 = new Model();
model3.set('selectedYear', 3);
// model3.get('selectedYear') -> 3
EDIT
To use the model attributes in a Backbone View I'd suggest doing something like the following
var MyView = Backbone.View.extend({
initialize: function() {
this.model = new Model({
selectedYear: 1
});
},
render: function() {
var template = Handlebars.compile("{{#times 27 selectedYear}}{{/times}}");
this.$el.html(template(this.model.toJSON));
}
});
Backbone.Marionette does a lot of this for you - if you give a Marionette View a template, it will automatically pass in the models attributes so you don't need to provide a render method.
eg:
var MyView = Marionette.ItemView.extend({
template: Handlebars.compile('your template here')
});
var model = new Model({selectedYear: 1});
var view = new view({model: model});
view.render();
// or region.show(view) which will automatically render the view.

You should set the selectedYear in model's defaults:
var sq2SelectCarYearModel = Backbone.Model.extend({
urlRoot: "api/caryears",
defaults: {
selectedYear: "0"
}
});
So that it'll be added to models attributes property. For setting it after creation, you should use set() method which will add the properties to models attribute hash.
right now, it's added as a direct property in model, this.model.selectedYear might work, but not the correct way to do it. Data should be added in models attributes hash so that other things like events work properly

Related

Backbone js passing arguments

I'm new at reading Backbone js and I have some serious problems with passing arguments in Backbone js.
var Song = Backbone.Model.extend();
var Songs = Backbone.Collection.extend({
model: Song
});
var SongView = Backbone.View.extend({
el: "li",
render: function() {
this.$el.html(this.model.get("title"));
return this;
}
});
var SongsView = Backbone.View.extend({
el: "ul",
initialize: function() {
this.model.on("add", this.onSongAdded, this);
},
onSongAdded: function(song) { // when object is added to a collection add event is triggerd
// the handler for this event get an argument which is the object that was just added
//in this case it refers to a song model so we simply pass it to our songView which is responsible for rendering a song an then we use jquery append method
// to append it to our list
var songView = new SongView({
model: Song
});
this.$el.append(songView.render().$el);
},
render: function() {
var self = this;
this.model.each(function(song) { //
var songView = new SongView({
model: Song
});
self.$el.append(songView.render().$el);
});
}
});
var songs = new Songs([
new Song({
title: "1"
}),
new Song({
title: "2"
}),
new Song({
title: "3"
})
]);
var song_1 = new Song({
title: "hello"
});
var songsView = new SongsView({
el: "#songs",
model: Songs
});
songsView.render();
as you can see I have this function: onSongAdded
we have some built-in events such as add that get 3 arguments like this:
add(collection, model , options)
how can I use these arguments in my code?
can you help me?
el option is for pointing the view to an element already existing in DOM. Your item views should be creating new <li> elements so you should be using tagName option instead.
In your collection view constructor you've defined el option and you're passing a different el option while instantiating it. If #songs is a <uL> in DOM, then no use in defining el: "ul", in constructor.
Also, there is no need to manually instantiate models, you can just pass objects into collections and collection will do it internally. And don't pass collection as model, pass it as collection.

Backbone console.log attributes and pass them to the View

I am not sure if I am using Models and Collections correctly. If I'm not I would really appreciate any guidance or advice into what I am doing wrong.
I have set up a Model and a Collection. The Collection has a url which is executed using the .fetch() method. I pass the Collection to the View where I log the results to the console. When I console.log(this.model) in the View I see the attributes nested a few levels deep. I would like to see the attributes in the console.log. The .toJSON() method doe not seem to work.
Here's a Fiddle to my current code: http://jsfiddle.net/Gacgc/
Here is the JS:
(function () {
var DimensionsModel = Backbone.Model.extend();
var setHeader = function (xhr) {
xhr.setRequestHeader('JsonStub-User-Key', '0bb5822a-58f7-41cc-b8a7-17b4a30cd9d7');
xhr.setRequestHeader('JsonStub-Project-Key', '9e508c89-b7ac-400d-b414-b7d0dd35a42a');
};
var DimensionsCollection = Backbone.Collection.extend({
model: DimensionsModel,
url: 'http://jsonstub.com/calltestdata'
});
var dimensionsCollection = new DimensionsCollection();
var DimensionsView = Backbone.View.extend({
el: '.js-container',
initialize: function (options) {
this.model.fetch({beforeSend: setHeader});
_.bindAll(this, 'render');
this.model.bind('reset', this.render());
return this;
},
template: _.template( $('#dimensions-template').html() ),
render: function () {
console.log( this.model.toJSON() ); //Why does this return an empty array???
return this;
}
});
var myView = new DimensionsView({model: dimensionsCollection});
}());
Is this what you're looking for?
If you're passing a collection to the view you should assign it to the collection property:
// It's a collection. Backbone views have a collection
// property. We should totally use that!
var myView = new DimensionsView({collection: dimensionsCollection});
When you attempt to bind the reset event to your view's render function, you're actually invoking the function immediately (by including the braces):
// Omit the braces to assign the function definition rather than invoke
// it directly (and immediately)
this.model.bind('reset', this.render);
But that's beside the point, because backbone's collection doesn't trigger a reset event (see documentation). One approach would be to assign the view's render function to the success parameter of the options object you pass to your collection:
var self = this;
this.collection.fetch({
beforeSend: setHeader,
success: function() {
self.render();
}
});
Finally, you need a parse function in your collection to pull the dimensions array out of the JSON you're loading:
var DimensionsCollection = Backbone.Collection.extend({
model: DimensionsModel,
url: 'http://jsonstub.com/calltestdata',
parse: function(response) {
return response.dimensions;
}
});

Is it an anti-pattern to instantiate models in views in Backbone.js?

When developing Backbone applications, I often find myself instantiating models in views when dealing with nested data. Here's some example data:
{
name: Alfred,
age 27,
skills: [
{
name: 'Web development',
level: 'Mediocre'
},
{
name: 'Eating pizza',
level: 'Expert'
]
}
Say I have some view PersonView that takes a person object PersonModel as its model (where Alfred would be an instance). And let's say I want to render the person's skills as subviews. At this point I create a new view and a new model for dealing with the nested skills data. And this is where I suspect I'm doing something wrong, or at least something suboptimal.
var SkillModel = Backbone.Model.extend();
var SkillView = Backbone.View.extend({
tagName: 'li',
initialize: function() {
this.render();
},
render: function() {
this.$el.html(someTemplate(this.model));
}
});
var PersonView = Backbone.View.extend({
el: ...
initialize: ...
renderSkills: function() {
_.each(this.model.get('skills'), function(skill) {
var skillModel = new SkillModel(skill);
var skillView = new SkillView({ model: skillModel });
self.$el.append(skillView.el);
})
}
});
Should I really be doing this or is there some better pattern, that does not involve third-party libraries, that solves the problem? I feel like I'm not really decoupling the views and models in the best way. If you want a concrete question it's: Is this an anti-pattern?
Views are for view logic, Models for data logic.
Consider to have a Model like this:
var SkillModel = Backbone.Model.extend({
});
var SkillsCollection = Backbone.Collection.extend({
model : SkillModel
});
var PersonModel = Backbone.Model.extend({
/*
** Your current model stuff
*/
//new functionality
skills : null,
initialize : function(){
//set a property into the model that stores a collection for the skills
this.skills = new SkillsCollection;
//listen to the model attribute skills to change
this.on('change:skills', this.setSkills);
},
setSkills : function(){
//set the skills of the model into skills collection
this.skills.reset( this.get('skills') );
}
});
So in your view renderSkills would be something like this:
var PersonView = Backbone.View.extend({
renderSkills: function() {
//this.model.skills <- collection of skills
_.each(this.model.skills, function(skill) {
var skillView = new SkillView({ model: skillModel });
self.$el.append(skillView.el);
})
}
});
I did my pseudo code trying to adapt to your sample code. But if you get the point, basically you could have a nested model or collection into your Model, so in the view there is not needed data interaction / set, everything is in the model. Also I answer as your requested, handling things without a third party. However don't mind taking a look to the source of this kind of plugins also:
https://github.com/afeld/backbone-nested
Update: per comments I provided a setup of the Model example:
var m = new PersonModel();
//nested collection is empty
console.log(m.skills.length);
m.set({skills : [{skill1:true}, {skill1:true}]});
//nested collection now contains two children
console.log(m.skills.length);
As you can see the Model usage is as "always" just a set of attributes, and the model is the one handling it. Your view should use skills as how views use any other collection. You could do a .each iterations or worth better to listen for events like add, reset, etc. on that collection(but that is other topic out of the scope of this question).
Example:
http://jsfiddle.net/9nF7R/24/

Retrieve Backbone Collection from Model and keep it as a Collection

I'm currently fooling around with backbone.js and came across some wierd behaviour trying to create some relationships between models and collections.
So here is my Model/Collection
Element = Backbone.Model.extend({
name: 'element'
});
Elements = Backbone.Collection.extend({
name: 'elements',
model: Element
});
Application = Backbone.Model.extend({
name: 'app',
initialize: function() {
this.elements = new Elements(this.get('elements'));
}
});
When I retrieve the elements via application.get('elements') I get a 'false' asking if this new Object is an instanceof Backbone.Collection.
var gotElements = application.get('elements');
var isCollection = gotElements instanceof Backbone.Collection;
So am I doing something wrong, or do I have to create a new Collection-Instance and fill it up with the Collection I receive from the application?
In your initialize function doing this.elements sets a property called 'elements' directly on your model (so model.elements will be your Backbone collection). The problem is when you try to retrieve this.elements by calling application.get('elements'), you will see it returns undefined (which is why it is not an instance of a Backbone collection).
In order for a model attribute to be retrievable using model.get it needs be set with model.set(attribute). If you examine the model in the console you will see the difference. model.set(myData) adds your data to the model's attributes hash. model.myData = myData adds a 'myData' property directly to the model.
To get this to work you can do the following:
Element = Backbone.Model.extend({
name: 'element'
});
Elements = Backbone.Collection.extend({
name: 'elements',
model: Element
});
Application = Backbone.Model.extend({
name: 'app',
elements: null
});
var application = new Application({
elements: new Elements()
});
var myElements = application.get('elements');
myElements should now be an empty Backbone Collection
Instead of putting your collection into another model, you should put it in the according view:
var ListView = new Backbone.View.extend({
initialize:function(){
this.Elements= new Elements();
// more stuff to go
},
render:function(){
_.forEach(this.Elements, function(){
//Do rendering stuff of indivdual elements and get HTML
});
//put HTML in the DOM
}
});
There is no need of a model containing a collection containing several models.
Edit: And instead of putting your app into a Model, you should put it into a view.

backbone views not working with fetched json

I'm having issues syncing JSON data received from the server with my views after a fetch.
I do not have a collection of "mainmodel", because I'm only working with one "mainmodel" at a time but numerous "mymodel", anyhow, the structure follows:
var mymodel = Backbone.Model.extend({
defaults: {k1:"",
k2:"",
k3:""}
});
var collection = Backbone.Collection.extend({model:mymodel,});
var mainmodel = Backbone.Model.extend({
defaults: {v1:"",
v2:"",
v3:"",
v4:new collection()
});
I create the nested views for "mymodel" from a render function in a parent view. This works..., only when I'm working with a new model.
// My ParentView render function
render: function() {
for (var i = 0; i < this.model.v4.length;i++) {
var view = new MyModelView({model:this.model.v4.at(i)});
this.$el.append($(view.render().el));
}
this.$el.append(this.template(this.model.toJSON()));
return this;
},
// The MyModelView render function below
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
Now, the above works if I open my application and create models from there. However, If I open my app and supply an id, I make a fetch to the server, retrieve the data, and create a new ParentView I end up getting an error that says "this.model.v4.at not a function". Ugh.
So now, if I change the FIRST render function to be, changing the at(i) to [i]
var view = new MyModelView({model:this.model.v4[i]});
And change the second render function, removing toJSON, to be:
this.$el.html(this.template(this.model));
It renders. But I still can't move around views without errors. No surprise.
I have used console.log(JSON.stringify(this.model)); as they arrive into the parentView and MyModelView. The JSON returned looks like this, whether fetched or created.
{"v1":"val1",
"v2":"val2,
"v3":"val3",
"v4":[{"k1":"key1","k2":"key2","k3","key"}, { ... }, { ... }]
}
The JSON data structures appear to be identical. I thought the JSON format was incorrect, so I tried using JSON.parse before handing the model to the view, but that didn't work. Maybe I'm way off, but I originally thought I had a JSON formatting issue, but now I don't know. The server is returning content as 'application/json'.
Edit: The JSON values for v1,v2,v3 render correctly.
Any ideas?
You have two problems: one you know about and one you don't.
The problem you know about is that your mainmodel won't automatically convert your v4 JSON to a collection so you end up with an array where you're expecting a collection. You can fix this by adding a parse to your mainmodel:
parse: function(response) {
if(response.v4)
response.v4 = new collection(response.v4);
return response;
}
The problem you don't know about is that your defaults in mainmodel has a hidden reference sharing problem:
var mainmodel = Backbone.Model.extend({
defaults: {
//...
v4: new collection()
}
});
Anything you define in the Backbone.Model.extend object ends up on your model's prototype so the entire defaults object is shared by all instances of your model. Also, Backbone will do a shallow copy of defaults into your new models. So if you m1 = new mainmodel() and m2 = new mainmodel(), then m1 and m2 will have exactly the same v4 attribute. You can solve this by using a function for defaults:
var mainmodel = Backbone.Model.extend({
defaults: function() {
return {
v1: '',
v2: '',
v3: '',
v4: new collection()
};
}
});

Categories