Property accessible in initialize not accessible in template? - javascript

When instantiating a new view, I'm passing in a model. I have access to that model in the "initialize" property, but I can't reference it when I'm trying pass the model into a template. Any idea why?
var postView = Backbone.View.extend({
initialize : function() {
// returns model
console.log('the model we are interested in',this.model);
this.render();
},
el : "#blog-post",
template : function() {
// returns undefined
var model = this.model;
return _.template($('#postview').html(), {
post : model
});
},
render : function() {
var self = this;
this.$el.html(self.template);
}
});
I'm instantiating it using a method in another view:
readMore : function(e, index) {
var self = this;
var newView = new postView({
model : self.collection.models[index].toJSON()
});
}

You're passing a function to this.$el.html:
this.$el.html(self.template);
That's the same as saying:
var f = this.template;
this.$el.html(f);
So what does html do when you pass it a function? Well, from the fine manual:
.html( function )
A function returning the HTML content to set. Receives the index
position of the element in the set and the old HTML value as
arguments. jQuery empties the element before calling the function; use
the oldhtml argument to reference the previous content. Within the
function, this refers to the current element in the set.
When you pass html a function, it will call that function but this won't be what you think it is, this inside the function will be the DOM element that is having its HTML set.
I think you want to call this.template yourself and hand its return value to html:
this.$el.html(this.template());
// ------------------------^^
That way template will have the view as its this as you expect it to.

Best guess would be this no longer refers to the context of the view. if you log this within the function what does it show.
EDIT-
Actually not sure if this will give the expected results, i normally use handlebars but i think the setup for _.template and hanblebars is pretty similar. Your template template normally wants a plain java object passed to it other wise you would have to access the variables you want in yours like post.attributes.name, however if you just pass the toJSON version of your model you can access the attributes without the need for post.attributes.
Also you can compile your template once then just refer to it, no need place it as a function and have it grab from the DOM every time (assuming it never changes). Below is an example of what i mean. In your template you would then have <%= name %> etc to grab you model attributes.
var postView = Backbone.View.extend({
initialize : function() {
// returns model
console.log('the model we are interested in',this.model);
this.render();
},
el : "#blog-post",
template : _.template($('#postview').html()),
render : function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
Oh also the convention normally is that render returns 'this' so if you want to call it from some where else and attach it to a new part of the page you can do it calling postView.render().el

You may be passing the model, but you're not receiving it in your view. Try:
initialize: function(model) { ...

Related

Backbone template method. Why are we passing in a model?

I can't figure out why we are passing in a model.toJSON() into this template:
app.TodoView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#item-template').html()),
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this; // enable chained calls
}
});
The example comes from this tutorial.
this.template(this.model.toJSON()) is the confusing part to me. The template method doesn't seem to take in an argument right? What is going on?
Underscore _.template function takes a template string as argument (and optionally a settings object) and returns a new pre-compiled template function which takes an object as an argument.
This object is the data used within the template:
// creates a template function
var templateFunc = _.template("<span><%= name %></span>");
// render the template using the passed data
templateFunc({ name: "Émile" }); // <span>Émile</span>
By default, template places the values from your data in the local
scope via the with statement. However, you can specify a single
variable name with the variable setting.
_.template("Using 'with': <%= data.answer %>", {variable: 'data'})({answer: 'no'});
model.toJSON() returns a shallow copy or the attributes hash of the model.
To achieve the equivalent of the above example:
var model = new Backbone.Model({ name: "Émile" });
templateFunc(model.toJSON()); // <span>Émile</span>
For Underscore.js before v1.7, the template function signature was a little different:
_.template(templateString, [data], [settings])
If a data object was passed, it didn't returned a function, but returned the rendered template string directly.
_.template('This is <%= val %>.', { val: "deprecated" });
// This is deprecated.

Backbone View instance not working as expected

I think I am missing something very trivial here. I have created a Backbone View as follows(without extending Backbone.View):
var PlayersView = new Backbone.View({
initialize: function() {
this.render();
},
render: function() {
console.log("hello World");
}
});
But it doesn't log anything when I run this code. It doesn't work when I explicitly do: PlayersView.render(); as well.
But the following code works :
var PlayersView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
console.log("hello World");
}
});
var playersview = new PlayersView();
The View constructor does not accept properties to add to the constructed object. It accepts a few special options like 'model', 'tagName', and so on. But the initialize(...) and render(...) properties in your first code snippet are effectively ignored.
The proper way to provide initialize, render, is to use Backbone.View.extend({...}).
From http://backbonejs.org/#View-extend
extend
Backbone.View.extend(properties, [classProperties]) Get started with
views by creating a custom view class. You'll want to override the
render function, specify your declarative events, and perhaps the
tagName, className, or id of the View's root element.
In other words, your first view's render method was not overridden by your custom render/initialize function.
When using extend you actually let Backbone understand you wish to use your own methods instead of the "default" ones.

Backbone Marionette ItemView return html

I want to render marionette ItemView and append result html() to my div.
My code:
var ProjectItemView = Backbone.Marionette.ItemView.extend({
template: "#ProjectItem",
tagName: 'div',
initialize: function () {
this.model.on('change', this.life, this);
this.model.on('change', this.render, this);
},
life: function () {
alert(JSON.stringify(this.model));
}
});
var tmp = new Project({project_id: 1});
tmp.fetch();
$('#container').append(new ProjectItemView({model: tmp}).$el);
alert in life: function shows model right. It means fetch works fine.
The question is - how to get html as result of view.
I also tried $('#container').append(new ProjectItemView({model: tmp}).render().el);
The problem was with the REST service that I use to populate collections/models. It returns array that contains one element - not plain object directly.
You have to react to render event from marionette.
...
onRender : function() {
$('#container').append(this.$el);
}
...
new ProjectItemView({model: tmp});
tmp.fetch();
If you want to get uncoupled, fire a distinct app event from within your view handler to the outside world. Radio might be worth considering if you're not already.
I think your problem is only the order of operations. Try this:
$('#container').append((new ProjectItemView({model: tmp})).render().el);
The way you had it before, you were invoking .render() on the constructor. With the extra parenthesis above, .render() is called on the instance.
pass element to view:
new ProjectItemView({model: tmp, el:'#container'}).render();

backbone.js model attributes not available at runtime

I am at the end of my mental leash with this one...
I am attempting to render a view to the screen that contains a model object attribute. I am able to get the html to render but the model attribute is not inserted into the div as expected but is instead rendering as undefined.
The thing that makes this so frustrating is that when I log the view object to the console I am able to inspect it and see that the correct model is associated with it and that the user's attributes are indeed present via this > model > attributes. However, if try to access the attributes directly in my code and then log that to the console I get undefined.
router show action
show: function(customer_id){
var customer = new backbone_data.Models.Customer({id: customer_id});
customer.fetch();
var customerView = new backbone_data.Views.CustomerView({model: customer});
$("#app_container").append(customerView.render().el);
}
render function in view class -- both of these do not work
render: function(){
this.$el.html("<h3>Hello, my age is "+this.model.get('age')+"</h3>");
return this;
}
template: _.template("<h3>Hello, my age is <%= age %></h3>"),
render: function(){
this.$el.html(this.template(this.model.attributes));
return this;
}
console image showing object attributes
I am logging these from inside the view's render function like so:
render: function(){
console.log(this);
console.log(this.model.attributes.age);
this.$el.html("<h3>Hello, my age is "+this.model.get('age')+"</h3>");
return this;
}
You can see from the screenshot that while the age attribute is accessible when logging the view object in the console, it is undefined when logging it directly in my code and rendering as undefined in the view.
http://imgur.com/P453dKL
Thanks for the help!!!!!!!
customer.fetch() performs an asynchronous request to populate the model's data. Your code creates the view and passes it the model immediately after you fetch(). Try this:
customer.fetch({
success: function(data)
{
var customerView = new backbone_data.Views.CustomerView({model: data});
$("#app_container").append(customerView.render().el);
}
});
That should wait until the fetch() is completed and ensure that the data you want has been retrieved.

how to render a collection using backbone?

I have this backbone that is working fine, i just want to render the collection is been fetched!
code:
var SearchView = Backbone.View.extend({
events: {
"keyup": "handleChange"
},
initialize: function(){
this.collection.bind("reset", this.updateView, this);
},
render: function(){
// this is where i need help
this.$el.next('#suggestions').append(// iterate over this.collection and apppend here) //
},
handleChange: function(){
var term = this.$el.val();
this.collection.search(term);
},
updateView: function() {
this.render();
}
});
I just want to iterate over this.collection and display the "text" attribute thats inside each collection and append it to the ('#suggestions') el. thanks
Focusing on your reset, render over collections issue, this is how I would do it. This way assumes that when you create your view, the $el is already present and you're passing it in through the View constructor so it's ready to go.
var SearchView = Backbone.View.extend({
template: _.template('<span><%= term %></span>');
initialize: function(){
this.collection.bind("reset", this.render, this);
},
render: function(){
this.addAllTerms();
return this;
},
addAllTerms: function() {
var self = this;
this.collection.each(function(model) {
self.addTerm(model);
});
},
addTerm: function(someModel) {
this.$el.next('#suggestions').append(this.template(someModel.toJSON()));
}
});
It's a bit different from your approach in a few ways. First, we utilize Underscore's template function. This could be anything from span to li to div whatever. We use the <%= %> to indicate that we're going to interpolate some value (which will come from our model.term attribute).
Instead of going to the handler then render, I just bind the collection to render.
The render() assumes we're always going to refresh the whole thing, build from scratch. addAllTerms() simply cycles through the collection. You can use forEach() or just each() which is the same thing except forEach() is 3 characters too long for my lazy bum. ;-)
Finally, the addTerm() function takes a model and uses it for the value that it will append to the #suggestions. Basically, we're saying "append the template with interpolated value". We defined the template function up above as this View object property to clearly separate the template from data. Although you could have skipped this part and just append('<span>' + someModel.get('term') + '</span>') or what not. The _.template() uses our template, and also takes any sort of object with the property that lines up with the one in our template. In this case, 'term'.
It's just a different way to do what you're doing. I think this code is a little more manageable. For example, maybe you want to add a new term without refreshing the whole collection. The addTerm() function can stand on its own.
EDIT:
Not that important but something I utilize with templates that I found useful and I didn't see it when I first started out. When you're passing the model.toJSON() into the template function, we're essentially passing all the model attributes in. So if the model is like this:
defaults: {
term: 'someTerm',
timestamp: '12345'
}
In our previous example, the attribute timestamp is also passed in. It isn't used, only <%= term %> is used. But we could easily interpolate it as well by adding it to the template. What I want to get at is that you don't have to limit yourself to data from one model. A complex template might have data from several models.
I don't know if it's the best way, but what I do is have something like this:
makeHash: function() {
var hash = {};
hash.term = this.model.get('term');
hash.category = anotherModel.get('category');
var date = new Date();
hash.dateAccessed = date.getTime();
return hash;
}
So you can easily build your own custom hash to throw into a template, aggregating all the data you want to interpolate into a single object to be passed into a template.
// Instead of .toJSON() we just pass in the `makeHash()` function that returns
// a customized data object
this.$el.next('#suggestions').append(this.template(this.makeHash()));
You can also easily pass in whole objects.
makeHash: function() {
var hash = {};
hash.term = this.model.get('term');
var animal = {
name: 'aardvark',
numLegs: 4
};
hash.animal = animal;
return hash;
}
And pass this into our template that looks like this:
template: _.template('<span><%= term %></span>
<span><%= animal.name %></span>
<span><%= animal.numLegs %></span>')
I'm not sure if this is the best way but it helped me understand exactly what data is going into my templates. Maybe obvious but it wasn't for me when I was starting out.
I found the solution to the problem, im gonna put it here for people that might want to know:
render: function(){
this.collection.forEach(function(item){
alert(item.get("text"));
});
}

Categories