Adding dynamic bootstrap tab before the existing 'addition' tab - backbone.js

I'm working with backbone.js and have following listView for <ul> element and a separate tabView for dynamic <li> element. In the render method of listView, I'm creating a new tabView and appending that el to listView el.
var listView = Backbone.View.extend({
//<ul> element for tabs
el: '.nav-tabs',
render: function(model) {
var tabView = new TabView({ model: model });
tabView.render();
$(this.el).append(tabView.el);
}
var TabView = Backbone.View.extend({
//create <li> element to hold each tab
tagName: "li",
className: "currentTab ",
render() {
var html = this.template(this.model.attributes);
$(this.el).append(html);
//creates new div for tab content
var tabContent = new TabContentView({ model: this.model });
tabContent.render();
}
This is fine and works as expected.
To add a new tab dynamically, I have a single li item at the start, so when the user clicks on that li item only new tab creation happens.
Now what I need is to add the newly creating tab before li + element. Currently all the new tabs are getting added only after this + element.
Following is the html of the <ul> tag for reference.
<div id="test">
<ul class="nav nav-tabs ">
<li>+</li>
</ul>
</div>
I tried out changing listView render method like below, but that doesn't work. Rather it just adds the new tab on top of (+) li element itself.
tabView.render();
$(this.el).find(".add-newTab").before(tabView.el);
Any idea how this can be done?

jQuery offers prepend or before methods depending on what you really want.
prepend
<ul class="nav nav-tabs ">
<li>prepending adds element here</li>
<li></li>
<li class="plus">+</li>
</ul>
before
<ul class="nav nav-tabs ">
<li></li>
<li>before adds element here when used on $('.plus')</li>
<li class="plus">+</li>
</ul>
Here's a simplified implementation of your list and tabs:
var TabView = Backbone.View.extend({
//create <li> element to hold each tab
tagName: "li",
className: "currentTab", // why? all tabs will have "currentTab"
initialize: function() {
//creates new div for tab content
this.tabContent = new TabContentView({
model: this.model
});
},
// render should only renders, and should be idempotent.
render: function() {
this.$el.empty().append(tabContent.render().el);
// returning "this" is the default in Backbone, which enables
// chaining method calls.
return this;
}
});
var ListView = Backbone.View.extend({
//<ul> element for tabs
el: '.nav-tabs',
template: '<li class="plus">+</li>',
events: {
"click .add-newTab": "onAddTab",
},
render: function() {
this.$el.empty().append(this.template);
// cache the '+' li element.
this.$plus = this.$('.plus');
return this;
},
onAddTab: function(e) {
var tabView = new TabView({ model: this.model });
// the magic happens here.
// if you want the tab at the beginning of the ul:
this.$el.prepend(tabView.render().el);
// or if you want it at the end, but before the + :
this.$plus.before(tabView.render().el);
},
});
You don't need to use the global jQuery to select elements, Backbone views have their own element pre-scoped and cached accessible through this.$el.
If you really need to find an element inside the view's el, you can select it using this.$('.element-you-want') which is a shortcut for:
$(this.el).find('.element-you-want')

Related

onClick event of backbone view not returning current view/model

I have a view in Backbone where 'el' is the 'sample div of below. And in the render function of this view I'm creating dynamic <li> elements and appending those to the ul element.
var tabView = Backbone.View.extend({
el : '#sample',
$ul : $("#sample").find("ul"),
...( Other code)
render: function () {
var htmContent = this.template(this.model.attributes);
this.$ul.append(htmContent);
return this.$el;
}
<div id="sample>
<ul class="list">
<!-- <li class="series"> elements will be appended here'-->
</ul>
</div>
Now what I need to do is, at the click of an <li> element, I need to get the corresponding view of that element to get the model and call methods. So I have registered a hash event in this tabView and calling a method when the <li> is clicked. However below 'this' always returns the last created <li> elements view/model only.
events:{
'click .series':'clickOnContent'
}
clickOnContent: function(e){
e.preventDefault();
var val = this.model.attributes;
}
How can I get the currently clicked <li> elements view/model instead?
Your seem to be trying to use a model and its attributes to hold a set of list items. This is approach is incorrect. Instead you will need to create a sub-view for the list which is backed by a "collection" and each of the li elements should then be added to the list collection. Read one of the plentiful tutorials on Backbone Collections.

BackboneJS + HandlebarsJS - How to avoid HTML to be rendered multiple times

I have a Backbone application where I trigger a View through an event-click and the Views HTML gets rendered with HandlebarsJS and displays data from a collection. So far it works except that my HTML gets repeated for each entry
My HTML looks like this:
<header>
<span class="blackdot" aria-hidden="true" />
<h1>Overview</h1>
</header>
<div>
<ul>
<li>
{{brand}}
<p>{{country}}</p>
</li>
</ul>
</div>
Right now, it duplicates the whole HTML code-block, including <header> and <h1>-tag for each entry but what I want to achieve is that my HTML would look like this:
<header>
<span class="blackdot" aria-hidden="true" />
<h1>Overview</h1>
</header>
<div>
<ul>
<li>
Audi
<p>Germany</p>
</li>
<li>
Hyundai
<p>South Korea</p>
</li>
<li>
Fiat
<p>Italy</p>
</li>
</ul>
</div>
My backbone View looks like this:
define(['backbone','handlebars', 'text!templates/Cars.html'],
function(Backbone,Handlebars, Template) {
'use strict';
var CarsView = Backbone.View.extend({
template: Handlebars.compile(Template),
events: {
},
initialize: function () {
_.bindAll(this, 'render');
},
render: function() {
var self = this;
self.collection.each(function(model){
self.$el.append(self.template({
brand:model.get('brand'),
country:model.get('country')
})
);
});
return this;
}
});
return CarsView;
}
);
Of course i could define the most of the HTML on the index page, wrap it in a DIV and do display:none and only write the <li>-tag in the Handlebars HTML template, but I want to avoid that and since the data what is returned are strings, I cant do the {{#each}}-thing... so, is there any solution for this?
To add onto what guzmonne said, the reason you are seeing this is because you are looping over the entire template. What you should be doing is taking the <li>'s and making a new template specifically for them. I've modified your existing code to show how something like what you are trying to do can be accomplished.
The CarsView Handlebars template:
<header>
<span class="blackdot" aria-hidden="true" />
<h1>Overview</h1>
</header>
<div>
<ul id="cars_list" />
</div>
The CarsView Backbone View:
var CarsView = Backbone.View.extend({
template: Handlebars.compile(Template),
events: {},
initialize: function () {
_.bindAll(this, 'render');
},
render: function() {
this.$el.html(this.template()); // this now only renders once
this.addAll();
return this;
},
addOne: function(car) {
var view = new CarView({model: car});
this.$("#cars_list").append(view.render().el);
},
addAll: function() {
this.collection.each(this.addOne, this);
}
});
The main differences between what I have done and your original CarsView is that firstly, the template itself doesn't contain any <li>'s. Instead, there is a placeholder ID which I have titled "cars_list". This container will give us an entry point to loop through the cars collection and add each item. Secondly, we aren't looping through the collection and re-rendering the CarsView. Instead, we take what CarsView dropped into the DOM and manually apppend to it from there.
Normally when dealing with collections, you can leverage Backbone's this.listenTo() function, which can take an event such as "reset" or "add" and tie it to a function. Since it would appear that the collection has already been fetched, we simply do this.addAll() after the CarsView template has been rendered. It is in here that the collection is looped and added.
To accomplish this, you will need another Backbone view, and another template...
The CarView Handlebars template:
{{brand}}
<p>{{country}}</p>
The CarView Backbone View:
var CarView = Backbone.View.extend({
tagName: "li",
template: Handlebars.compile(Template),
events: {},
initialize: function () {
// model event listeners (in case these list items can be edited/removed)
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
The CarView view is short and simple, but takes care of a lot for you. As you can see, this view will generate an <li> tag and take the contents of the model and send it to the handlebars template. No more having to deal with manually fetching model attributes, which is an added bonus.

How to render a backbone collection into two views

I am using twitter bootstrap link. When the user clicks the link a bootstrap modal appears.
Now because of some bootstrap technical difficulties in modal rendering i need to seperate the link and put the modal out the navbar div.
So consider i have two separate div
<div id="linkDiv">
</div>
and
<div id="modalDiv">
</div>
Now i have only one View which makes a call to the server to get the collection
app.View.FriendRequestListView = Backbone.View.extend( {
templateModalLink: _.template($('#link').html()),
templateModal: _.template($('#modal').html()),
tagName: 'div',
initialize: function(){
this.friendRequestCollection = new app.Collection.FriendRequestCollection();
this.friendRequestCollection.bind("reset", this.render, this);
this.friendRequestCollection.fetch();
},
render: function() {
$(this.el).html(this.templateModalLink({
friendRequestCollection: this.friendRequestCollection}));
return $(this.el);
},
});
Than i can render only one div like following
var list = new app.View.FriendRequestListView();
$('#linkDiv').html(list.$el);
My question is , Is it possible to render two templates at the same time and add the two templates to different DIV like for example in my case i want to get update
templateModalLink template to linkDiv and templateModal template to modalDiv with the collection I am getting from the server.
You have to instantiate the collection before app.View.FriendRequestListView(s) and pass app.View.FriendRequestListView(s) the collection:
var friendRequests = new app.Collection.FriendRequestCollection();
friendRequests.fetch(
success: function(collection, response, options) {
var list1 = new app.View.FriendRequestListView({collection: collection});
var list2 = new app.View.FriendRequestListView({collection: collection});
$('#linkDiv').html(list1.$el);
$('#modalDiv').html(list2.$el);
}
);

Backbone View not appending on Collection change

I have a text input which I use to collect a keyword from the user, and then send an ajax request (which is getting successful results). I would like to append the <ul> with <li> items, each representing a result.
Here is the which includes the search input and should get the appended <li> results.
<div class="search_group pull-right">
<h5>Find Other Groups<span class="caret"></span></h5>
<ul>
<li class="search-input">
<input type="text" class="search" name="search">
</li>
</ul>
</div>
I have the following underscore template which would ideally represent each result in the collection.
<script type="text/template" class="template" id="template-search-result">
<% _.each(results,function(result){ %>
<li class="result">
<a href="/surnames/<%=URL%>">
<%=result.name%>
<span><%=result.members%> – Members</span>
<span><%=result.ancestors%> – Ancestors</span>
</a>
</li>
<% }); %>
</script>
And now, my Backbone code.
I fetch my Results collection after the user enters something in the input field, and when that collection returns results from my server (which I can see successfully in the console), I would expect the ResultView to append the ul with additional li results.
window.Results = Backbone.Collection.extend({
url: function() {
return '/ajax/groups/search?q=' + query;
}
});
var results = new Results();
window.SearchInput = Backbone.View.extend({
el: ".search-input input",
events: {
"change": "getResults",
"keyup": "getResults",
"keydown": "getResults",
"paste": "getResults"
},
getResults: function() {
query = $('input').val();
results.fetch();
}
});
var searchInput = new SearchInput({ collection: results });
window.ResultView = Backbone.View.extend({
el: ".search-group ul",
initialize: function() {
_.bindAll(this, 'render');
this.collection.bind('change', this.render);
this.template = _.template($("#template-search-result").html());
},
render: function() {
this.$el.append( this.template( {results: this.collection.toJSON()} ));
return this;
}
});
var resultView = new ResultView({ collection: results });
You're either looking for the add or reset events, depending how you're loading your collection.
If you're doing a fetch, you'll want to listen to reset. If you are adding models to the collection manually, or calling fetch({update:true}), you'll get the add events.
The collection change event only fires when the one of the collection's models fires a change of its own.
Here's a list of all the built-in Backbone events for reference.
Instead of defining the "el" in my ResultView as:
el: ".search-group ul",
I changed it to an id and modified my markup accordingly:
el: "#result-list",
and:
<ul id="result-list">
<li class="search-input">
<input type="text" class="search" name="search">
</li>
</ul>

How can I create a Backbone View with two or more liked nodes?

Take a HTML tabbar as example. Usually you have a ul and a list of div's. All the Backbone examples that I have found, link the View with only one node by the 'el', 'tagName', etc...
HTML TabBar:
<div class=".tabbar">
<ul class=".tabbar-header">
<li>Cars</li>
<li>Houses</li>
</ul>
<div id="tab-cars" class=".tabbar-item">...</div>
<div id="tab-houses" class=".tabbar-item">...</div>
</div>
Backbone Code:
window.TabBarView = Backbone.View.extend({
el: ???,
tabs: [],
render:function (eventName) {
// Render all tabs in this.tabs
_.each(this.tabs, function (item, position) {
// Render each tab with item.render()
}, this);
return this;
}
});
window.TabBarItemView = Backbone.View.extend({
el: ???,
initialize:function () {
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
render:function (eventName) {
// Render the tab header and tab content
return this;
}
});
I wish to add several TabBarItemView's to the TabBarView and each one creates itself the li node inside the ul.tabbar-header and the div.tabbar-item as content.
I've written an article that addresses this issue: http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/
It will show you how you can either use a single view to do what you want, or a parent/child setup with a collection view and item view like you're showing in your sample code
you can go as far as to make a separate navigation view, and have the navigation add an item through the render method of your tab-item-view.
when you render the tab item view, you do something like navigation.add(new nav item);
and also add a way to remove the navigation item.
or you can keep the navigation in pure html and append a <li> item with jquery / javascript when you are rendering a tab below.
can't give you a fully working example though, if you really need it i can probably make one tonight,.

Resources