Updating specific template in nested templates - backbone.js

I'm trying to render nested view.
Say I have views BigView and SmallView
<script type="text/template" id="big-template">
<% _.each(smallViews, function (smallView) { %>
<%= smallView.$el.html() %>
<% }); %>
</script>
<script type="text/template" id="small-template">
<div>
<%- name %>
</div>
</script>
I call render from the bigView and it renders everything fine, including its children smallViews.
How do I update (render) a smallView individually without rendering the bigView?

I suggest that you don't call view methods from your template code. I would move the render code for a smallview to the bigview render method.
Which would look like the following:
var BigView = Backbone.View.extend({
render:function() {
for (var i=0;i<this.collection.length;i++) {
var smallView = new SmallView({model:this.collection.at(i)});
this.$el.append(smallView.render().el);
}
return this;
}
});
Then your SmallView:
var SmallView = Backbone.View.extend({
template:_.template($('#smallTemplate').html()),
render:function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
});
Here is a fiddle that shows it in action with an event handler on smallView to re-render.

Just calling a properly defined render method of the smallView object should be enough. For example something like this.
// smallview file
var SmallView = Backbone.View.extend({
// some other methods
template: _.template($("#small-template").html()),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
// where you need to update the nested views
_.each(smallViews, function(smallView {
smallView.render();
}

Related

Marionette layout view -- why is a template necessary

In spite of reading the marionette docs several times over, I am still not able to fully comprehend some aspects of it correctly.
I am creating a layout view 'AppLayout' as below:
var AppLayoutView = Marionette.LayoutView.extend({
regions: {
headerRegion: "#ecp_header",
bodyRegion: "#ecp_layout_region"
},
...
The html snippet for my app is having the two dom nodes for above defined regions:
<div id="ecp_header"></div>
<div class="container" id="ecp_layout_region">
<div class="row" id="ecp_body">
...
in app.js, my calling code is like this..
ECPApp.on('start', function() {
require(['controller_cp', 'header_view'], function(ControllerCP, HeaderView) {
console.log("On start event executing...");
// create a event aggregator vent object and attach to app.
ECPApp.vent = new Backbone.Wreqr.EventAggregator();
var appLayoutView = new AppLayoutView();
appLayoutView.render();
//appLayoutView.showLayout();
//$('div.toolbar > ul > li:first > a').tab('show');
if (Backbone.history) Backbone.history.start();
});
This gives me error Cannot render the template since it is null or undefined.
I thought that the default render() behavior of layout always looks for a template, so I rolled out my own version of render, as below:
render: function() {
var $self = this;
/* if no session exists, show welcome page */
var promise = ECPApp.request('entities:session');
promise.done(function(data) {
if (data.result==0) {
console.log('Valid session exists. Showing home page...!');
$self.showHome();
} else {
console.log('No session exists. Showing welcome page...!');
$self.showWelcome();
}
}).fail(function(status) {
console.log('No session exists. Showing welcome page...!');
$self.showWelcome();
});
return $self;
},
showWelcome: function() {
var self = this;
require(['header_view', 'welcome_view'],
function(HeaderView, WelcomeView) {
var headerView = new HeaderView();
var welcomeView = new WelcomeView();
self.bodyRegion.show(welcomeView);
});
}
This time, I get another error saying, An "el" #ecp_layout_region must exist in DOM. However I am sure that the element is existing in the DOM, as I can see it by checking in the debug console window. Running $('#ecp_layout_region') shows a valid element.
Marionette layout view is pretty confusing. Going forward I need multiple nested views. I am stuck here.
How is your template located? Is your template wrapped by <script type = “text/template”> tag?
It may look like this:
Inside your html, in head section:
<script type = “text/template” id="yourLayout">
<div id="ecp_header"></div>
<div class="container" id="ecp_layout_region">...</div>
</script>
And in Layout definition:
var AppLayoutView = Marionette.LayoutView.extend({
template: '#yourLayout'
...
});

my backbone sample is not working and giving with this.el.html is not a function

I am new bee to backbone and i am trying with a below sample
http://jsfiddle.net/naveencgr/L3orucjm/
While loading i am getting this.el.html is not a function, let me know what is the cause of it.
HTML:
<div id="container"></div>
<script type="text/template" id="template">
<input type="text" name="Name" id="Name"></input>
<input type="button" name="Add" id="Add" value="Add"></input>
<div id="list"></div>
</script>
JavaScript:
NameModel = Backbone.Model.extend({
});
var nameModel = new NameModel();
nameModel.set('name','test');
alert(nameModel.get('name'));
NameView = Backbone.View.extend({
tagName:"li",
render: function(){
var template=_.template("<%=name%>", nameModel.toJSON());
return template;
}
});
var nameView = new NameView();
NameViewList = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
var template = _.template($("#template").html(), {});
this.el.html(template);
},
events : {
"click input#Add" :"addName",
},
addName : function() {
var name = $("#Name").val();
if (name.trim().length > 0) {
//nameModel.set("name", name);
$("#list").append(nameView.render());
}
}
});
var nameViewList = new NameViewList({el : $("div#container")});
You have numerous errors in your code. You know about one of them but not the others.
The one you know about is:
this.el.html(template);
A view's el is just a DOM node and that doesn't have an html function. The html function you're trying to use is part of jQuery so you'd want to call it on this.$el (which is just a cached version of $(this.el)):
this.$el.html(template);
Other problems:
Your fiddle is missing vars all over the place; don't say:
NameModel = ...
say:
var NameModel
to avoid accidental globals.
Your NameView is strange. It has tagName: 'li' so presumably it should be creating list elements but the render doesn't do anything with the view's el, it just returns a string of HTML that ends up inside a <div>. That <div> should be a <ul>:
<ul id="list"></ul>
A render function generally populates the view's el and, to allow chaining, returns this:
render: function() {
var template = _.template('<%= name %>');
this.$el.html(template(nameModel.toJSON()));
return this;
}
You are using Underscore's _.template incorrectly. You used to be able to say:
var h = _.template(template_source, data);
to compile the template and fill it in in one step but as of Underscore 1.7, the second argument to _.template is an options object. Now you need to compile and fill in the template in separate steps:
var t = _.template(template_source);
var h = t(data);
You'll see this change in the render above.
The way you're using your NameView is strange. Apparently you are trying to use one NameView to handle multiple names, this would work with your strange NameView#render implementation but it will fall apart once NameView has anything to do or once NameView is updated (as above) to be more conventional. You should create one NameView for each name you're displaying and each NameView should have its own NameModel as its model property. This would make NameView#render look like:
render: function() {
var template = _.template('<%= name %>');
this.$el.html(template(this.model.toJSON()));
return this;
}
and NameViewList#addName would look like:
addName: function() {
var name = this.$("#Name").val();
if(name.trim().length > 0) {
var nameView = new NameView({
model: new NameModel({ name: name })
});
this.$('#list').append(nameView.render().el);
}
}
You'll note that we're using NameView#render's new return value, this x.append(v.render().el) pattern is quite common and idiomatic in Backbone so it is a good practice. You should also notice that the search for #list is now limited to the view's el by using the view's this.$ function; this.$('#list') is equivalent to this.$el.find('#list') and doing things this way helps you keep your views self-contained.
In real life you'd probably put your new NameModel({ name: name }) instances in a collection somewhere and events on that collection would trigger the creation of new NameViews.
Applying all that to your fiddle gives you this functional and more idiomatic version:
http://jsfiddle.net/ambiguous/8x0ma9qo/

simple backbone js view not working

I just copy pasted my previous code and still my code is not working. I am newbie and all i do is copy and paste from examples. And still its not working.
index.html
<body>
<div id="date">Loading...</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js" type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.2/backbone-min.js" type="text/javascript"></script>
<script type="text/javascript" src="model.js"></script>
<script type="text/javascript" src="views.js"></script>
</body>
model.js
var model = Backbone.Model.extend({});
collection = Backbone.Collection.extend({
url:'date.json',
model:model,
parse:function(response){
return response;
}
});
date = new collection();
date.fetch();
view.js
var DateView = Backbone.View.extend({
el: $('#date'),
initialize: function(){
_.bindAll(this, 'render');
},
render: function(eventName) {
console.log(this.collection.length); //Throwing 0 as output
}
});
var dateview = new DateView({collection:date});
dateview.render();
date.json
[
{"date":"1288323623006"}
]
You do not have a binding for the collections changeor resetevent. Also your render method does not do anything to actually render the content into the html.
The date.fetch command (without any error handling!) is executed asynchronously, so it is likely to return when the render of the view has already been executed. Thus you will need an apropriate binding for the collections' events, like this:
view.js
var DateView = Backbone.View.extend({
el: $('#date'),
initialize: function(){
_.bindAll(this, 'render');
this.collection.on("change", this.render);
this.collection.on("reset", this.render);
},
render: function(eventName) {
console.log(this.collection.length); //Throwing 0 as output
return(this); // used for chaining
}
});
var dateview = new DateView({collection:date});
dateview.render();
In your flow, you have included model.js where it does a fetch
date.fetch();
This is an async method, which means it goes off to fetch the data, and javascript continue to run the next line of code.
Then in your view when you do this.collection.length, (which essentially is the date object), this collection may have not been retrieved from the server yet, meaning it is still empty. A proper way to render stuff after fetch is
1. bind the render to fetch, like Exinteris mentioned
date.fetch({reset:true}),
this will fire a "reset" event after the collection got response from server. In view.js
this.collection.on("reset",this.render);
meaning it will render after a reset event is fired.
do render in a promise manner. If you are new to this check here
promise
var self=this;
date.fetch().then(function(){
self.render();
});
Also, it is always a good practice to do fetch inside a view, not in model.js. For example:
var DateCollection=Backbone.Collection.extend(...);
var DateView = Backbone.View.extend({
el: $('#date'),
initialize: function(){
_.bindAll(this, 'render');
this.collection=new DateCollection();
this.collection.on('reset',this.render());
this.collection.fetch({reset:true});
},
render: function(eventName) {
console.log(this.collection.length); //Throwing 0 as output
}
});

passing model´s array to an underscore view

I'm trying to pass some models attibutes to my underscore view, however, for some reason I cannot find the right way to make it work, if some1 can point in the right direction, I would appreciated it.
App.appModel = new App.Models.AppModel({
"user" : data.user,
"acls" : data.acls //acls is an array, which I need to pass to the view
});
App.appLogged = new App.Views.App({
model : App.appModel
});
//My view
App.Views.App = Backbone.View.extend({
render : function() {
template = _.template( $('#Home').html(), {acls : this.model.toJSON }) ;
this.$el.html(template);
}
});
//so In my view... I need a li with each acl
<script type="text/template" id="Home">
<% _.each(acls, function(acl) { %>
<li><%= acl.get(0) %></li>
<% }); %>
</script>
It doens't throw any error... it just dont render it...
Thanks in advance.
Change your template compilation line:
//My view
App.Views.App = Backbone.View.extend({
render : function() {
template = _.template( $('#Home').html(), this.model.toJSON()) ;
this.$el.html(template);
}
});
model.toJSON will produce an object with keys corresponding to the model attributes. In this case, it will already contain the key acls. What you were producing is
{
acls: {
acls: [],
...
}
}
And what your template needs is:
{
acls: [xxxx]
}
Normally it's useful to make a call to console.log(this.model.toJSON()) on your render, to see what's going into your template.
Looks like missing parens in the call toJSON()?
//My view
App.Views.App = Backbone.View.extend({
render : function() {
template = _.template( $('#Home').html(), {acls : this.model.toJSON() }) ;
this.$el.html(template);
}
});

backbone structure / collections / views passing and returning data

So I am putting together a very simple app and have got a bit stuck.
So far I have my router
var AppRouter = Backbone.Router.extend({
routes:{
"":"home"
},
initialize:function () {
// Handle back button throughout the application
$('.back').live('click', function(event) {
window.history.back();
return false;
});
this.firstPage = true;
this.products = new Products();
},
home:function () {
var view = new HomeView({collection:this.products});
// render the view when the collection is loaded
this.products.on("renderCompleted:Products", function() {
//alert("ff");
view.render();
});
// fetch should trigger "reset" when complete
this.products.fetch();
}
my model
var Product=Backbone.Model.extend({
defaults:{
id:"",
name:'',
longName:'',
productID:''
}
});
return Product;
my collection
var Products=Backbone.Collection.extend({
// Book is the model of the collection
model:Product,
fetch:function(){
var self=this;
var tmpItem;
//fetch the data using ajax
var jqxhr = $.getJSON("data/product.json")
.success(function(data, status, xhr) {
$.each(data.data.productTypeList, function(i,item){
tmpItem=new Product({id:item.id,name:item.name,longName:item.longName, productID:i});
self.add(tmpItem);
});
self.trigger("fetchCompleted:Products");
})
}
});
return Products;
and my view
var HomeView = Backbone.View.extend({
template: _.template(homeViewTemplate),
render:function (eventName) {
//$(this.el).html(this.template());
this.$el.empty();
//compile template using the data fetched by collection
this.$el.append(this.template({data:this.collection.toJSON()}));
console.log("test" + this.collection.get('data'));
return this;
}
the homeViewTemplate call has this HTML
<ul >
<% for (var i = 0; i < data.length; i++) { %>
<% var item = data[i]; %>
<li>
<%= item.longName %>
</li>
<% } %>
</ul>
you can see from the router that on init this.Products is created of the collection
then when home is called it runs the view.
I dont think anything is passed from the collection to the view though and I am not sure how this is done? Is my collection setup wrong? - do I have to call fetch and pass that to the view?
Any help is appreciated
thanks
do I have to call fetch and pass that to the view?
You have to call fetch, and have its success callback trigger view.render. You could do that using the success option of the JQuery call; or using the reset event, which collection.fetch normally calls. I'd suggest putting collection.reset inside your custom fetch:
// get the data an an array of models
var models = data.data.productTypeList.map(function(item) {
return new Product({id:item.id,name:item.name,longName:item.longName, productID:i});
});
// populate the collection
self.reset(models);
And then in the "home" route, call fetch and then call render on the callback:
home:function () {
var view = new HomeView({collection:this.products});
// render the view when the collection is loaded
this.products.on("reset", function() {
view.render();
});
// fetch should trigger "reset" when complete
this.products.fetch();
}

Resources