MarionetteJS - cannot reference this in template function - backbone.js

I have a view which is a Marionette ItemView. I would like to access other parameters on this view using "this" inside the template function but I get it as undefined and I'm unsure why.
define(['jquery', 'hbs!templates/template', 'backbone'],
function ($, template, Backbone) {
"use strict";
return Backbone.Marionette.ItemView.extend({
name: "Depth",
el: ".card",
template: function(serializedModel){
var self = this; // self is undefined, so I can't reference this.name, which would be Depth
var data = {isDepth: true, cardTitle: self.name, injectHTML: template()};
.... do some stuff ...
return template();
}
});
}
);

You can use templateHelpers to access custom variables in the template:
templateHelpers:function(){
return {
card_title: this.name
}
}

You can use underscore's bindAll to bind template to your marionetteItem View's this whenever its called . So something like :
Backbone.Marionette.ItemView.extend({
initialize: function(){
_.bindAll(this, 'template');
},
template: function() {
//this refers to the parent object scope.
}
});

Related

RequireJs text plugin and UnderscoreJs templates

Alright people,
I am using the RequireJs text plugin to pull in underscore templates (.html) in my backbone application. Unfortunately my underscore code in my templates are being rendered as plain text.
define(['Backbone', 'text!Templates/BlogIndex.html', 'text!Templates/Elements/Blog/List.html'], function(Backbone, Template, ElementList){
var BlogPostIndexView = Backbone.View.extend({
initialize: function () {
this.template = _.template($(Template).html(), {posts : this.collection});
this.render();
},
render: function (Template) {
this.$el.html(this.template);
return this;
}
});
return BlogPostIndexView;
});
Here is my code for my view, you can see i am pulling-in two templates and setting them. However it getting rendered as ...
Globall Coach Blog Posts
<% _.each(posts, function(post){ %>
<%= _.escape(post.title) %>
<% }); %>
Has anybody ever had this issue?
Seems like you're not using the underscore template function correctly. Underscore compiles the string into a function in which you can pipe in data. so your code should look like this:
define(['Backbone', 'text!Templates/BlogIndex.html', 'text!Templates/Elements/Blog/List.html'], function(Backbone, Template, ElementList){
var BlogPostIndexView = Backbone.View.extend({
initialize: function () {
this.template = _.template($(Template).html())({posts : this.collection});
this.render();
},
render: function () {
this.$el.html(this.template);
return this;
}
});
return BlogPostIndexView;
But I would refactor this further because you usually want to dynamically re-render with the latest data so I would put the piping in the data to the template in the "render" method instead of "initialize".
So preferably I would do this:
define(['Backbone', 'text!Templates/BlogIndex.html', 'text!Templates/Elements/Blog/List.html'], function(Backbone, Template, ElementList){
var BlogPostIndexView = Backbone.View.extend({
initialize: function () {
this.template = _.template($(Template).html())
this.render();
},
render: function () {
this.$el.html(this.template({posts : this.collection}));
return this;
}
});
return BlogPostIndexView;
Turns #mu-is-to-short was correct, requireJs text module returns the the raw html.
Here it `define(['Backbone', 'text!Templates/BlogIndex.html', 'text!Templates/Elements/Blog/List.html'], function(Backbone, Template, ElementList){
var BlogPostIndexView = Backbone.View.extend({
initialize: function () {
this.template = _.template(Template);
},
render: function (Template) {
this.$el.html(this.template({posts : this.collection.toJSON()}));
return this;
}
});
return BlogPostIndexView;
});

Can't get Backbone Events to work

I'm using Backbone.js 1.1.2 with jQuery 1.11.
I think I'm missing something very simple.
I've set up a model: Contact and a collection: ContactList.
I have success rendering ContactView views with models obtained from ContactList.fetch()
But when I try to bind a change event in the view initializer:
this.model.on('change', this.render, this);
... I get:
backbone Uncaught TypeError: Object #<Object> has no method 'on'
So I go back to the documentation and read that .on() is part of Backbone.Events, and somehow I need to extend my model to use this (and why is this not part of the out-of-the-box functionality for models??)
I've tried declaring
_.extend(this, Backbone.Events);
... inside of the initialize function for ContactView before declaring the binding, but no dice.
What do I need to do to get the Backbone.Events functionality working??
update: all relevant code (sans template)
var Contact = Backbone.Model.extend({
defaults: {
FirstName: '',
...[etc]
},
initialize: function() {
}
});
var ContactList = Backbone.Collection.extend({
model: Contact,
url: '/api/Contacts/getall'
});
var ContactView = Backbone.View.extend({
initialize: function() {
this.render();
this.model.on('change', this.render, this);
},
render: function () {
var template = _.template($('#contact_template').html(), this.model);
this.$el.html(template);
}
});
var contactList = new ContactList();
contactList.fetch({
success: function (collection, response, options) {
contactList.models.forEach(function (m) {
var $el = $('<li />')
.addClass('adminItem clearfix')
.appendTo($('#contactList'));
new ContactView({ el: $el, model: m.attributes });
});
}
});
This line is the source of trouble
new ContactView({ el: $el, model: m.attributes });
m.attributes is a plain js object. You want to pass along the actual model object:
new ContactView({ el: $el, model: m});
And because the collection has underscore methods mixed in you should be able to simplify it a bit to this:
contactList.each(function (m) {
var $el = $('<li />')
.addClass('adminItem clearfix')
.appendTo($('#contactList'));
new ContactView({ el: $el, model: m});
});

Variable passed into backbone view coming up undefined

I am in the process of learning backbone / underscore, and I am finding that the more I break away from the really basic stuff in the tutorials, the more come to realize that the tutorials aren't teaching me much of anything.
My current problem is with passing a variable to a view. I have three different templates available, but they all render the same, so I was hoping to just pass which template to use into the view when it was being rendered from the collection. What I thought would work would be just adding a property to the call to the view, and then accessing it with this.options.Property, but this is throwing an error that the property is undefined.
I have tried a number of variant options, but nothing seems to work. What am I doing wrong?
Thanks is advance.
var ProjectListView = Backbone.View.extend({
el: '#projectList',
initialize: function() {
this.collection = masterProjectList;
this.render();
},
render: function() {
this.$el.html("");
this.collection.each(function(project) {
this.renderItem(project);
}, this);
},
renderItem: function(project) {
var projectView = new ProjectView({model: project, projectType: '#theatricalProjectTemplate' });
// Passing in the project type, which determines which template gets used
this.$el.append(projectView.render().el);
}
});
var ProjectView = Backbone.View.extend({
tagName: "div",
className: "project-wrap",
template: _.template($(this.options.projectType).html()),
// use this.options to access the value
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
When you're defining your ProjectView:
var ProjectView = Backbone.View.extend({
//...
template: _.template($(this.options.projectType).html()),
//...
});
you're executing some code (i.e. calling extend) and in this context, this will be the global object (AKA window in a browser) and that probably won't have an options property. If you want to use the projectType option that you pass to the ProjectView constructor, move the template assignment into initialize:
var ProjectView = Backbone.View.extend({
tagName: "div",
className: "project-wrap",
initialize: function() {
this.template = _.template($(this.options.projectType).html());
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
This assumes that projectType will be a valid jQuery selector, you might want to use '#' + this.options.projectType instead but I'm not sure what exactly will be in projectType.
mu is too short is correct and if you define template method as follows, you can share the template method with all instances of ProjectView:
var ProjectView = Backbone.View.extend({
tagName: "div",
className: "project-wrap",
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
template: function() {
return _.template($(this.options.projectType).html());
}
});

Is there a way to change Marionette ItemView template dynamically with RequireJS?

I'm trying to manipulate itemViews dynamically in a Marionette CollectionView. The collections have the same models, but i defined templateName argument inside the models.
The question is, can i manipulate the ItemView template by this argument?
ItemView:
define(['text!templates/ComponentItemViewTemplate.html','models/ComponentModel'], function (template, model) {
var ItemView = Backbone.Marionette.ItemView.extend({
template: _.template(template),
model: model
});
return ItemView;
});
CollectionView:
define(['views/ComponentItemView', 'views/LoadingView'], function(ItemView, LoadingView) {
var ComponentListView = Backbone.Marionette.CollectionView.extend({
emptyView : LoadingView,
id: "component-list",
itemView: ItemView,
events: {
'click .title span' : 'show'
},
appendHtml: function(collectionView, itemView, index){//i would like to render different templates, for different models.
itemView.$el.draggable({ helper: "clone", cancel: ".component .title span", connectToSortable: ".ui-sortable" });
collectionView.$el.append(itemView.el);
},
show: function(r) {
var target = $(r.target);
if( target.parent().hasClass('open') ){
target.parent().removeClass('open');
target.parent().next().slideDown('fast');
}else{
target.parent().addClass('open');
target.parent().next().slideUp('fast');
}
}
});
return ComponentListView;
});
Thanks!
You can override getTemplate function and write your custom logic there. The Marionette documentation recommends the following option:
MyView = Backbone.Marionette.ItemView.extend({
getTemplate: function(){
if (this.model.get("foo")){
return "#some-template";
} else {
return "#a-different-template";
}
}
});
I think gumballhead is on the right track. You can override the getTemplate function to do this.
MyCollectionView = Marionette.CollectionView.extend({
// ...
getItemView: function(item){
// get the template from the item... or wherever else it comes from
return new MyViewType({
template: item.get("the-template")
});
}
});
Hope that does what you need
First of all i'd like to thanks for everybody who tried to help me.
I resolved my own problem.
Here is the sollution, if somebody need it:
define(['models/ComponentModel'], function (model) {
var ItemView = Backbone.Marionette.ItemView.extend({
model: model,
render: function() {
var that = this;
var data = this.serializeData();
require(['text!templates/components/editor/' + that.model.get('editor_template') + '.html'], function(Template){
var html = _.template(Template, data);
that.$el.html(html);
});
}
});
return ItemView;
});
edited: (Better sollution)
Suggestions are welcome!

How to pass a template to a view in Backbone

I'm trying to pass a template to my view. I have several different templates I want to use and want to be able to switch them up in my router. I get no errors, but I get no results. It looks like the initialize method isn't being called in my second view. Here is my code:
(function() {
window.App = {
Models: {},
Collections: {},
Views: {},
Router: {}
};
window.template = function(id) {
return _.template( $('#' + id).html() );
};
var vent = _.extend({}, Backbone.Events);
_.templateSettings.interpolate = /\[\[(.+?)\]\]/g;
App.Router = Backbone.Router.extend({
routes: {
'' : 'index',
'send-message' : 'sendMessage',
'*other' : 'other'
},
index: function() {
t = new (App.Collections.Tables.extend({ url: 'main-contact'}))();
tables = new (App.Views.Tables.extend({
collection: t, template: template('mainContactTemplate')}))();
$('#web-leads').html(tables.el);
},
sendMessage: function() {
t = new (App.Collections.Tables.extend({ url: 'send-message'}))();
tables = new App.Views.Tables.extend({
collection: t, template: template('sendMessageTemplate')});
$('#web-leads').html(tables.el);
},
other: function() {
}
});
// Main Contact
App.Models.Table = Backbone.Model.extend({});
App.Collections.Tables = Backbone.Collection.extend({
model: App.Models.Table,
initialize: function(models, options) {
this.fetch({
success: function(data) {
//console.log(data.models);
}
});
if (options) {
this.url = this.url || options.url;
}
}
});
App.Views.Tables = Backbone.View.extend({
tagName: 'ul',
initialize: function() {
this.collection.on('reset', this.render, this);
},
render: function() {
return this.collection.each(this.addOne, this);
},
addOne: function(model) {
var t = new App.Views.Table({ model: model, template: template});
this.$el.append(t.render().el);
return this;
}
});
App.Views.Table = Backbone.View.extend({
tagName: 'li',
template: this.template,
initialize: function (attrs) {
this.options = attrs;
console.log(this.options);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
new App.Router();
Backbone.history.start();
})();
EDIT: I was missing some parenthesis. But now I get an error of an unrecognized expression. Initialize is now being called.
The way you are doing it in App.Views.Table is (as far as I can tell) the "standard" way of using templates with Backbone. There are of course several alternatives though, and none of them are "wrong" per say.
That being said, you do have a couple problems in your code. Let's start with:
template: this.template,
At the time that code runs you're not in an instance of App.Views.Tables, you're in the global space declaring a class that (later) will be used to make instances. At that moment though, this just refers to window. What you really want to do is set the template in your initialize, which leads me to:
initialize: function(options) {
this.template = options.template;
},
But then there's one last problem:
var t = new App.Views.Table({ model: model, template: template});
there is no template variable in that function, so you're really doing template: undefined. That should use a real template.
All that being said, you might want to just consider putting the template on the view directly, the way you sort of tried to:
template: Handlebars.compile('<span>{{test}}</span>'),
After all, any given view should always use the same template, right? Also, you might want to consider moving the:
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
in to a parent class, so that you can share it between all of your templated views, instead of having to repeat it.

Resources