BackboneJS: Can you change templates of a view on the fly? - javascript

Can you change the template of a backbone view by submitting a template option? Code below doesn't seem to work:
#company_location= new window.ArtistLocationForm({template: JST["templates/new_company_location"]})

Not out-of-the-box.
But all data passed in to the constructor is assigned to this.options on your view instance, so implementing it in the render function should be pretty easy, something like:
render: function(){
var templateFn = this.options.template || this.template;
this.$el.html(templateFn(this.model.attributes));
return this;
}
should work.

Related

How do JS frameworks bind model to a view?

I've been diving into the scary stuff recently.. :) scary stuff being the source of popular js frameworks like backbone.js, angular.js, vue.js and so on.
I'll take Backbone as an example here. I am trying to figure out how is a model attached to the view?
Here is the code and if someone could just point out the part where this is happening, would be awesome!
https://github.com/jashkenas/backbone/blob/master/backbone.js
Actually, the part I don't understand is that there is not innerHTML called anywhere, so how is the element being populated with the data?
Backbone is not Angular, it doesn't bind model to html for you, also it does not actually renders views for you, you have to implement render methods in your views and call them when you find appropriate. In fact, I think it might be confusing to developer coming from 2-way binding frameworks. Backbone is all about giving all the control to the developer, but you have to do all the work yourself.
Minimal model => view flow example would be something like
var MyModel = Backbone.Model.extend({
defaults: {
test: 'test',
},
initialize: function (options) {
console.log(this.get('test'));
},
});
var MyView = Backbone.View.extend({
el: '#your-el',
initialize: function (options) {
this.template = _.template('<div><%= test %></div>');
this.listenTo(this.model, 'change', this.render);
},
render: function () {
// rendering happens here, innerHTML, $.html(), whichever you prefer
// and you can pass model data as this.model.toJSON()
// or you can pass an object with data
// also, you probably will need a templating library or use
// bundled underscore _.template(html, data) method to render blocks of html
// for example, with underscore
this.$el.html(this.template(this.model.toJSON()));
return this; // for chaining
},
});
var myModel = new MyModel();
var myView = new MyView({
model: myModel,
});
myModel.set('test', 'newValue'); // view should render after this call
Check the list of built-in events at backbonejs.org.

Backbone - Passing variable template into view using require js

In my backbone app I have a view that needs to be rendered using different templates, dependant on a variable that is passed to the view. I am using require js and want to know if there is smart way of doing this.
Here is how I would call instantiate the new view:
var templateType = 'Template1'
var view = new DetailView({model:thisModel, 'templateType': templateType});
Here is an example of the view:
define(['backbone',
'text!templates/template1.html',
'text!templates/template2.html' ],
function(Backbone,
Template1,
Template2){
var DetailView = Backbone.View.extend({
tagName : 'li',
className: 'detail-tag',
initialize : function(){
this.detailTemplate = _.template( this.options.templateType );
this.render();
},
This does not give me the required result. How do I use the options value to link to the require variable - Template1? I realise I could probably set up a switch statement or something, but that seems a bit messy. Is there a neater way of doing it?
Thanks
I decided to pass the template to the view, instead of trying to match it up with a template included with require. I used this question as a model for my solution:
How to pass a template to a view in Backbone

Backbone.js render() never called

Simple view (reduced code):
My.View = Backbone.View.extend({
className: 'my-view',
initialize: function() { },
render: function() {
console.log('render');
return this;
}
});
I use it in another view as subview like this:
var myView = new My.View();
this.$el.append(myView.render().$el);
First instead of a className the view had a template and the view was rendering fine.
However when I removed the template and added className instead it is not rendering correctly.
It just renders the div and the correct class name however no logging in the render method is performed. AND when I add some html inside the render method to this.$el it never appears. Any ideas why?
UPDATE:
When I put my custom rendering code inside the afterRender method it works. Why is it not possible to overwrite the render method in my case?
this.$el.append(myView.render().el);
I would render the subview the following way.
var myView = new My.View();
this.$el.append(myView.el);
myView.render();

Does a Backbone View always require a Backbone Model?

I am learning Backbone.
I am wondering whether or not a Backbone View always requires a Backbone Model.
For example, let's say I have a panel that contains two child panels. The way I would structure this is with a parent view for the main panel, then two child views for the child panels...
var OuterPanel = Backbone.View.extend({
initialize: function() {
this.innerPanelA = new InnerPanelA(innerPanelAModel);
this.innerPanelB = new InnerPanelB(innerPanelBModel);
},
});
var outerPanel = new OuterPanel();
The parent view is really just a container. It may have some controls in it, but no data that needs to be persisted. Is this the proper way to do it? Or is this bad practice?
Thnx (in advance) for your help
As said in Backbone.View docs
Backbone views are almost more convention than they are code — they
don't determine anything about your HTML or CSS for you, and can be
used with any JavaScript templating library.
In other words, if you don't have a model, don't use a model. On the other hand, I would inject the children models as options to the outer view instance and not rely on global variables, something like this:
var OuterPanel = Backbone.View.extend({
initialize: function(options) {
this.innerPanelA = new InnerPanelA({model: options.modelA});
this.innerPanelB = new InnerPanelB({model: options.modelB});
}
});
var outerPanel = new OuterPanel({
modelA: innerPanelAModel,
modelB: innerPanelBModel
});

Backbone.js EL and Template in the View

So i'm very new to backbone.js and not so good at JavaScript in general, so I was wondering if someone could explain to me why
I cannot define my EL property, and Template property in my view, and then use this.template in my render. Instead I have to define the template and el in my render function.
var ProductView = Backbone.View.extend({
el: $('#product-list'),
initialize: function() {
this.el.html('<span style="color:white">loading...</span>');
}, // end initialize
render: function(collection) {
// // assign the template
this.template = $('#product_template');
// Where the template will be placed
this.el = $('#product-list');
// Add the collection to the main object
this.collection = collection;
// add tthe data to the html variable
var html = this.template.tmpl(this.collection.toJSON());
// place the html in the element.
this.el.html(html);
// not even sure what the hell this is.
return this;
} // end render
});
The problem isn't in the way you're defining el or template, it's in how you're setting the call back. In Workspace, your router, you're setting the callback for your collection refresh event like this:
// Bind the view and collection
// So when the collection is reset, the view executes the render method
Products.bind("reset", this.view.render);
The problem is, you're setting a method as a callback, but you're not providing a context object as the third argument to bind - so the method is called, but this in the method refers to the global object, not the view. So this.el is undefined, because it's not looking at the view instance at all. Try:
// Bind the view and collection
// So when the collection is reset, the view executes the render method
Products.bind("reset", this.view.render, this.view);
and see how that goes.
(I made a jsFiddle to demonstrate that the el and template were set properly under normal circumstances, though it doesn't actually include the fix above, which is hard to mock up without the server-side data: http://jsfiddle.net/nrabinowitz/QjgS9/)
You can't do this:
var ProductView = Backbone.View.extend({
el: $('#product-list'),
// ...
and get anything useful in el as #product-list probably isn't even present in the DOM when your ProductView is built; so trying to use $('#product-list') for el is simply the classic "I forgot to use $(document).ready()" problem dressed up in Backbone. Using $('#product-list') for el should work if #product-list is around when you define your ProductView though.
You can do this though:
var ProductView = Backbone.View.extend({
el: '#product-list',
// ...
and then say $(this.el) when you need to do things inside your view methods. Not only is $(this.el) the usual way of using el but it also works and that's sort of important.
The same issues apply to #product_template.
Looking at your code I see this:
// INstantiate the view
this.view = new ProductView();
// Bind the view and collection
// So when the collection is reset, the view executes the render method
Products.bind("reset", this.view.render);
Presumably the render is being triggered by the reset event. But, and this is a big but, the render method isn't bound to the right this anywhere so this won't be the ProductView when render is called and this won't have anything that you expected it to; hence your bizarre "undefined" error.
You could use _.bindAll in your initialize:
initialize: function() {
_.bindAll(this, 'render');
// ...
but usually you'd want to give the view a collection when you create it and the view would bind itself to the events so your structure will still be a bit odd.
You can also supply a context (AKA this) when you call bind:
collection.bind('reset', this.render, this);

Categories

Resources