onClick event of backbone view not returning current view/model - backbone.js

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.

Related

Adding dynamic bootstrap tab before the existing 'addition' tab

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')

get original element from ng-click

I have a list of items in my view with ng-click attached to them:
<ul id="team-filters">
<li ng-click="foo($event, team)" ng-repeat="team in teams">
<img src="{{team.logoSmall}}" alt="{{team.name}}" title="{{team.name}}">
</li>
</ul>
I'm handling the click events in the foo function in my directive, passing $event as a reference to the object that's been clicked, but I'm getting a reference to the img tag, rather than the li tag. I then have to do stuff like this to get the li:
$scope.foo = function($event, team) {
var el = (function(){
if ($event.target.nodeName === 'IMG') {
return angular.element($event.target).parent(); // get li
} else {
return angular.element($event.target); // is li
}
})();
Is there a simple way to get the reference to the element that ng-click is bound to, without doing DOM operations in my directive?
You need $event.currentTarget instead of $event.target.
Not a direct answer to this question but rather to the "issue" of $event.currentTarget apparently be set to null.
This is due to the fact that console.log shows deep mutable objects at the last state of execution, not at the state when console.log was called.
You can check this for more information: Consecutive calls to console.log produce inconsistent results

Trying to append to class in current view but results in appending to other views

I am rendering a view for each item in a collection, each of those items contain a list that I am trying to append to an ul but they are being appended to the ul of other views.
View:
initialize: function(){
},
render: function(){
var current = this.model.toJSON();
this.$el.html(_.template(cTemplate, current));
var items = this.model.toJSON().items;
items.forEach(function(item){
this.$('.list').append('<li>'+item.attributes.price+'</li>');
});
return this;
}
Template:
<ul class="list"></ul>
I am trying to append to this.$('.list') which I thought would mean I am appending the price of each item only to the class of my template in the current view. It appears I cannot reference it like this because the prices are appended to all list classes in views rendered before the current one.
I am wondering how I prevent appending to all of the same classes on the same page and just to the one from the template of my current view.
If ul is the tagName of this view class, you can simply append to this.
items.forEach(function(item){
this.$el.append('<li>'+item.attributes.price+'</li>');
});
or, ul.list is a child element, you can do this
items.forEach(function(item){
this.$el.find('ul.list').append('<li>'+item.attributes.price+'</li>');
});
Note: This pattern may not be maintainable in future when complexity increases. You can considering creating individual view for each item if those items has interactions.

How to set .el to div which is in underscore.js template?

It is pretty basic thing but I still can't find the answer. I need to set View's el: to div which is in underscore.js template.
<script id="product" type="text/template">
<div class="productInfo"></div>
<div class="prices"></div>
<div class="photos"></div>
</script>
The first view renders the product template. I need to render other Views to divs in this template. I don't know how to set el:, because el: '.prices' just don't work with divs in template.
This Views structure is similar to How to handle initializing and rendering subviews in Backbone.js?. But I use template instead of rendering to existing divs.
So the problem is using a CSS selector string for this.el won't find anything if the matching <div> is not attached to the page's DOM. In the case of you <script> tag template, the contents of the <script> tag are not attached DOM nodes, there are just a single text node.
One option given your HTML would be to just forget about the <script> tag and put your empty <div> tags straight into the HTML. They are empty and thus should be harmless and invisible until you actual render some content within them. If you do that, el: '.productInfo:first' should work fine.
Other than that, you'll need to put logic into your parent view along these lines:
Render the template into a detached DOM node
Search that detached DOM node for subview divs
Map the subview div to the corresponding backbone view subclass
instantiate and render the subview, then use something like this.$el.find('.productInfo').replaceWith(productInfoView.el) to put the rendered HTML into the parent view at the right location
My general comment is that views should render to detached DOM nodes and leave it to other components such as the router or layout managers to decide where in the real DOM they get attached. I think this makes the views more reusable and testable.
Render the parent view, then assign the '.prices' as the el of the child:
var ParentView = Backbone.View.extend({
render : function() {
var html='<h1>Parent View</h1><div class="productInfo"></div><div class="prices"></div><div class="photos"></div>';
this.$el.html(html);
var prices = new PricesView({
el : this.$('.prices')
});
prices.render();
}
});
var PricesView = Backbone.View.extend({
render : function() {
var html='<h2>Prices View</h1>';
this.$el.html(html);
}
});
// Create a parent view instance and render it
var parent = new ParentView({
el : $('.parent')
});
parent.render();
​
Working example on jsfiddle: http://jsfiddle.net/JpeJs/
from version 0.9.9, backbone alaws you to define the el property of a view as a function :
When declaring a View, options, el and tagName may now be defined as functions, if you want their values to be determined at runtime.
Assuming that the subviews are created after the main view, this might help you if your subviews are aware of their parent :
SubView = Backbone.View.extend({
el : function(){
return this.parent.$('.prices');
}
});
var subViewInstance = new SubView({parent : theParentView});

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