Backbone events and Twitter bootstrap popover - backbone.js

Insert backbone rendered view in twitter bootstrap popover like below. Problem is that when insert throught content option backbone events for that view don`t fire. I inserted view in div for test with $(selector).html attendanceShow.render().el events work without problem. Thank you in advance
attendance = new Attendance()
attendance.url = "#{attendanceUrl}/#{attendanceId}"
attendance.fetch
success: ->
attendanceShow = new ExamAttendanceShow({model: attendance })
currentTarget.popover
html : true
content: ->
attendanceShow.render().el
Best regards,
Georgi.

From what I understand, based on your code and your description you are only creating an instance of the popover but never showing it. I have a live demo working but not with CoffeeScript (I personally hate CoffeeScript), you can see the code below and at this jsfiddle.
data1.json
{"content": "lorem ipsum dolor sit amet"}
index.html
<div class="container">
<div class="row">
<button class="btn" data-target="popover">Popover</button>
</div>
<div class="row"> </div>
<div class="row">
<button class="btn" data-action="change-content">Change Content</button>
</div>
</div>
main.js
var Main = Backbone.View.extend({
model: null,
item: null,
popover: false,
events: {
'click .btn[data-target]': 'button_click',
'click .btn[data-action="change-content"]': 'change_content'
},
initialize: function() {
_.bindAll(this);
this.model = new PopoverModel();
this.model.view = new PopoverContentView({model: this.model});
this.item = this.$('.btn[data-target]');
this.item.popover({
html: true,
content: this.model.view.render().el
});
},
button_click: function(event) {
if (!this.popover) {
this.model.url = 'js/data1.json';
this.model.fetch({
success: this.model_fetched
});
} else {
this.popover = false;
}
},
model_fetched: function() {
if (!this.popover) {
this.item.popover('show');
} else {
this.item.popover('hide');
}
this.popover = !this.popover;
},
change_content: function(event) {
this.model.set('content', 'Some random content... ' + parseInt(Math.random() * 10));
}
});
var PopoverModel = Backbone.Model.extend({
defaults: {
content: ''
}
});
var PopoverContentView = Backbone.View.extend({
initialize: function() {
_.bindAll(this);
this.listenTo(this.model, 'change', this.render);
},
render: function() {
this.$el.html(_.template('<%= content %>', this.model.toJSON()));
return this;
}
});
var main = new Main({
el: '.container'
});

I have a similar issue, to expand on Georgi's answer, please try this:
Place a button or a link in your popup (instead of the dynamic text you place) and handle an event, say click event on it.

Related

Understanding the el in backbone

I don't quite understand how the el works in backbone.
I was under the assumption that el defaulted to body when it wasn't specified. I created a fiddle to illustrate my misunderstanding.
When I specify the el everything works fine. Unspecified returns nothing though.
http://jsfiddle.net/9R9zU/70/
HTML:
<div class="foo">
<p>Foo</p>
</div>
<div class="bar">
</div>
<script id="indexTemplate" type="text/template">
Bar?
</script>
JS:
app = {};
app.Router = Backbone.Router.extend({
routes: {
"" : "index"
},
index: function() {
if (!this.indexView) {
this.indexView = new app.IndexView();
this.indexView.render();
} else {
this.indexView.refresh();
}
}
});
app.IndexView = Backbone.View.extend({
// el: $('.bar'),
template : _.template( $('#indexTemplate').html() ),
render: function() {
this.$el.html(this.template());
return this;
},
refresh: function() {
console.log('we\'ve already been here hombre.')
}
});
var router = new app.Router();
Backbone.history.start();
If you do not specify element in the Backbone view, it will create an html node in memory, render the view into it and bind all event handlers based on that node. Then you will need to manually append it to the dom like this:
$('body').append(this.indexView.render().el);

Magnific popup and Backbone events: the view disappears when I add the magnific popup code

I'm trying to create an event to a view which opens a light box when I click in the tag, but when I add the magnific pop-up code the view disappears.
Here is my html code:
<section class="feed">
<script id="bookTemplate" type="text/template">
<div class="book">
<a href="<%= video %>" class="video">
<span><img src="img/go.png" class="go"></span>
<img src="<%= image %>"/>
<h2 class="bookTitle"><%= title %><h2>
</a>
</div>
</script>
</section>
And now my views and some data to test them:
var app = app || {};
app.BookListView = Backbone.View.extend({
el: '.feed',
initialize: function ( initialBooks ) {
this.collection = new app.BooksList (initialBooks);
this.render();
},
render: function() {
this.collection.each(function( item ){
this.renderBook( item );
}, this);
},
renderBook: function ( item ) {
var bookview = new app.BookView ({
model: item
})
this.$el.append( bookview.render().el );
}
});
app.BookView = Backbone.View.extend ({
tagName: 'div',
className: 'book',
template: _.template( $( '#bookTemplate' ).html()),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
events: {
'click .video' : 'popUp'
},
popUp: {
function () {
$('.popup').magnificPopup({
disableOn: 700,
type: 'iframe',
mainClass: 'mfp-fade',
removalDelay: 160,
preloader: false,
fixedContentPos: false
});
}
}
});
$(function(){
var books = [
{title:'American Psycho', image:'http://2.bp.blogspot.com/ ytb9U3mZFaU/Ti2BcgQHuhI/AAAAAAAAAkM/NMyfgRIFgt0/s1600/american-psycho_44538679.jpg',
video:'http://www.youtube.com/watch?v=qoIvd3zzu4Y'},
{title:'The Informers',
image:'http://www.movieposterdb.com/posters/09_03/2008/865554/l_865554_d0038c1c.jpg',
video:'http://www.youtube.com/watch?v=g4Z29ugHpyk'}
];
new app.BooksListView (books);
I don't know if the problem is related with my views code or vith the magnific pop-up code.
Thanks
Looks like a syntax error
you have an extra set of braces which is not supposed to be present there.
Here popup is supposed to be a event handler and not a object hash
popUp: {
function () {
$('.popup').magnificPopup({
disableOn: 700,
type: 'iframe',
mainClass: 'mfp-fade',
removalDelay: 160,
preloader: false,
fixedContentPos: false
});
}
}
supposed to be
popUp: function (e) {
e.preventDefault();
$('.popup').magnificPopup({
disableOn: 700,
type: 'iframe',
mainClass: 'mfp-fade',
removalDelay: 160,
preloader: false,
fixedContentPos: false
});
}

Backbone Marionette Nested Composite View

So I am stuck. I got the great Backbone.Marionette to handle my nested childs/parents relationships and rendering(doing it with the bare backbone was a nightmare), but now i'm facing problems with my nested composite view,
I'm always getting a The specified itemViewContainer was not found: .tab-content from the parent composite view - CategoryCollectionView, although the itemViewContainer is available on the template, here is what I'm trying to do, I have a restaurant menu i need to present, so I have several categories and in each category I have several menu items, so my final html would be like this:
<div id="order-summary">Order Summary Goes here</div>
<div id="categories-content">
<ul class="nav nav-tabs" id="categories-tabs">
<li>Appetizers</li>
</ul>
<div class="tab-content" >
<div class="tab-pane" id="category-1">
<div class="category-title">...</div>
<div class="category-content">..the category items goes here.</div>
</div>
</div>
Here is what I have so far:
First the templates
template-skeleton
<div id="order-summary"></div>
<div id="categories-content"></div>
template-menu-core
<ul class="nav nav-tabs" id="categories-tabs"></ul>
<div class="tab-content" ></div>
template-category
<div class="category-title">
<h2><%=name%></h2>
<%=desc%>
</div>
<div class="category-content">
The menu items goes here
<ul class="menu-items"></ul>
</div>
template-menu-item
Item <%= name%>
<strong>Price is <%= price%></strong>
<input type="text" value="<%= quantity %>" />
Add
Now the script
var ItemModel = Backbone.Model.extend({
defaults: {
name: '',
price: 0,
quantity: 0
}
});
var ItemView = Backbone.Marionette.ItemView.extend({
template: '#template-menuitem',
modelEvents: {
"change": "update_quantity"
},
ui: {
"quantity" : "input"
},
events: {
"click .add": "addtoBasket"
},
addtoBasket: function (e) {
this.model.set({"quantity": this.ui.quantity.val() });
},
update_quantity: function () {
//#todo should we do a re-render here instead or is it too costy
this.ui.quantity.val(this.model.get("quantity"));
}
});
var ItemCollection = Backbone.Collection.extend({
model: ItemModel
});
var CategoryModel = Backbone.Model.extend({
defaults: {
name: ''
}
});
var CategoryView = Backbone.Marionette.CompositeView.extend({
template: '#template-category',
itemViewContainer: ".menu-items",
itemView: ItemView,
className: "tab-pane",
id: function(){
return "category-" + this.model.get("id");
},
initialize: function () {
this.collection = new ItemCollection();
var that = this;
_(this.model.get("menu_items")).each(function (menu_item) {
that.collection.add(new ItemModel({
id: menu_item.id,
name: menu_item.name,
price: menu_item.price,
desc: menu_item.desc
}));
});
}
});
var CategoryCollection = Backbone.Collection.extend({
url: '/api/categories',
model: CategoryModel
});
var CategoryCollectionView = Backbone.Marionette.CompositeView.extend({
el_tabs: '#categories-tabs',
template: '#template-menu-core',
itemViewContainer: ".tab-content", // This is where I'm getting the error
itemView: CategoryView,
onItemAdded: function (itemView) {
alert("halalouya");
//this.$el.append("<li>" + tab.get("name") + "</li>");
//$(this.el_tabs).append("<li><a href='#category-" + itemView.model.get("id") + "'>"
//+ itemView.model.get("name") + "</a></li>")
}
});
I know It's a bit hard to follow but you guys are my last resort. There is no problems with the templates and the cateogry fetching and the other stuff(it was already working before converting the CategoryCollectionView from a Marionette collection to a composite view.)
Edit 1
Added App initalizer on request:
AllegroWidget = new Backbone.Marionette.Application();
AllegroWidget.addInitializer(function (options) {
// load templates and append them as scripts
inject_template([
{ id: "template-menuitem", path: "/js/templates/ordering-widget-menuitem.html" },
{ id: "template-category", path: "/js/templates/ordering-widget-category.html" },
{ id: "template-menu-core", path: "/js/templates/ordering-widget-menu-core.html" },
{ id: "template-skeleton", path: "/js/templates/ordering-widget-skeleton.html" }
]);
// create app layout using the skeleton
var AppLayout = Backbone.Marionette.Layout.extend({
template: "#template-skeleton",
regions: {
order_summary: "#order-summary",
categories: "#categories-content"
}
});
AllegroWidget.layout = new AppLayout();
var layoutRender = AllegroWidget.layout.render();
jQuery("#allegro-ordering-widget").html(AllegroWidget.layout.el);
// Initialize the collection and views
var _category_collection = new CategoryCollection();
var _cateogories_view = new CategoryCollectionView({ api_key: window.XApiKey, collection: _category_collection });
_category_collection.fetch({
beforeSend: function (xhr) {
xhr.setRequestHeader("X-ApiKey", window.XApiKey);
},
async: false
});
//AllegroWidget.addRegions({
/// mainRegion: "#allegro-ordering-widget"
//});
AllegroWidget.layout.categories.show(_cateogories_view);
});
AllegroWidget.start({api_key: window.XApiKey});
You are adding to the collection via fetch before you call show on the region.
Marionette.CompositeView is wired by default to append ItemViews when models are added to it's collection. This is a problem as the itemViewContainer .tab-content has not been added to the dom since show has not been called on the region.
Easy to fix, rework you code as below and it should work without overloading appendHtml.
// Initialize the collection and views
var _category_collection = new CategoryCollection();
// grab a promise from fetch, async is okay
var p = _category_collection.fetch({headers: {'X-ApiKey': window.XApiKey});
// setup a callback when fetch is done
p.done(function(data) {
var _cateogories_view = new CategoryCollectionView({ api_key: window.XApiKey, collection: _category_collection });
AllegroWidget.layout.categories.show(_cateogories_view);
});
okay this is pretty weird but adding this in the CategoryCollectionView class:
appendHtml: function (collectionView, itemView, index) {
//#todo very weird stuff, assigning '.tab-content' to itemViewContainer should have been enough
collectionView.$(".tab-content").append(itemView.el);
}
solved the problem, however i have no idea why it works, asssigning '.tab-content' to the itemViewContainer should have been enough, any idea?

ItemView events not Triggering

What i want to do:
Render a select dropdown with option tags inside, and when user selects an option in the dropdown, get the newly selected model and do stuff with it.
Problem:
I'm having a hard time to get the change event to be triggered in an ItemView that's been called through a CompositeView.
For some reason the CompositeView:change (log: holy moses) is being triggered, however it doesn't help me much, since it won't give me the selected model.
I've tried a ton of stuff but nothing really worked.
any help would be greatly appreciated!
code:
Configurator.module('Views.Ringsizes', function(Views, Configurator, Backbone, Marionette, $, _) {
Views.DropdownItem = Marionette.ItemView.extend({
tagName: 'option',
template: "#dropdown-item",
modelEvents: {
'change': 'modelChanged'
},
onRender: function(){
console.log('tnt');
this.$el = this.$el.children();
this.setElement(this.$el);
},
modelChanged: function(model) {
console.log("holy mary");
}
});
Views.DropdownView = Marionette.CompositeView.extend({
template: "#dropdown-collection",
className: 'configurator-ringsizes-chooser',
itemView: Views.DropdownItem,
itemViewContainer: '.product_detail_ring_sizes',
events: {
"change": "modelChanged"
},
initialEvents: function(){},
initialize: function(){
console.log(this.model);
this.collection = new Backbone.Collection(this.model.getRingsizes());
},
modelChanged: function(model) {
console.log("holy moses");
}
});
Views.List = Marionette.CollectionView.extend({
className: 'configurator-ringsizes',
itemView: Views.DropdownView
});
});
template code: (if needed)
<script type="text/template" id="dropdown-item">
<option value="<#- code #>" <# if(current) { #> selected="selected" <#}#> ><#- name #> </option>
</script>
<script type="text/template" id="dropdown-collection">
<div class="accordionContent accordionContent_ringsizes">
<div class="configurator-ringsizes-chooser-ringsizes-region">
<select class="product_detail_ring_sizes"></select>
</div>
</div>
</script>
A "change" event won't trigger on a option when you select it, instead it will fire on the select when you change the choosen option (that's why it triggers on the composite view).
So you should use this in your itemView:
events: {
'click' : 'modelChanged'
}
Okay, i finally got this to work.
I'm a bit dissapointed that i have to rely on a data- attribute for this,
but this is the only way i found. took me long enough already :)
Here's how i did it now:
Template code:
<script type="text/template" id="dropdown-item">
<option data-cid="<#- cid #>" value="<#- code #>" <# if(current) { #> selected="selected" <#}#> ><#- name #></option>
</script>
<script type="text/template" id="dropdown-collection">
<div class="configurator-ringsizes-chooser-ringsizes-region">
<select class="product_detail_ring_sizes"></select>
</div>
</script>
Code:
Configurator.module('Views.Ringsizes', function(Views, Configurator, Backbone, Marionette, $, _) {
Views.DropdownItem = Marionette.ItemView.extend({
tagName: 'option',
template: "#dropdown-item",
serializeData: function() {
var data = {
cid: this.model.cid,
code: this.model.get('code'),
name: this.model.get('name'),
current: this.model.get('current')
};
return data;
},
onRender: function(){
this.$el = this.$el.children();
this.setElement(this.$el);
}
});
Views.DropdownView = Marionette.CompositeView.extend({
template: "#dropdown-collection",
className: 'configurator-ringsizes-chooser',
itemView: Views.DropdownItem,
itemViewContainer: '.product_detail_ring_sizes',
events: {
"change select": "modelChanged"
},
initialEvents: function(){},
initialize: function(){
this.collection = new Backbone.Collection(this.model.getRingsizes());
},
modelChanged: function(e) {
var cid = $(e.currentTarget+"option:selected").data('cid');
var currentModel = this.collection.find(function(elem) {
return elem.get('current');
});
var model = this.collection.find(function(elem) {
return elem.cid === cid;
});
currentModel.set({
current: false
});
model.set({
current: true
});
// AND here i'm doing my stuff, getting the overall model through this.model, the collection of options through this.collection and the currently selected model through currentModel.
}
});
Views.List = Marionette.CollectionView.extend({
className: 'configurator-ringsizes',
itemView: Views.DropdownView,
model: this.model
});
});

Event does not bind when adding view dynamically

I have two simple views, one with a button that creates a view and append it to the page. The new view consists of a single list item with a link and an event to I need to bind to each list item. I think the problem here is the el object: What I have been reading the el object should be created automatically when it's not defined on construction of the view? See this fiddle
HTML:
<div id="main">
<button type="button" class="add">Add view</button>
<ul id="tasks" />
</div>
<script id="view-template-new-task" type="text/html">
<li>Task</li>
</script>
​
JS:
var TaskView = Backbone.View.extend({
events: {
'click a.fire': 'fire'
},
fire: function() {
alert('fire');
},
initialize: function() {
this.template = _.template($('#view-template-new-task').html());
},
render: function() {
$('#tasks').append(this.template());
}
});
var View = Backbone.View.extend({
events: {
'click button.add': 'addView'
},
addView: function(e) {
var task = new TaskView();
task.render();
}
});
$(function() {
var view = new View({
el: $('#main')
});
});​
Backbone automatically delegates events to the view element. As is, the el in your TaskView would point to an unattached div (the default el created by Backbone) and not to an element in your list.
The cure is simple : create your subview with its el set to a correct DOM node by setting a tagName to li and appending this element in your main view.
var TaskView = Backbone.View.extend({
tagName: 'li',
events: {
'click a.fire': 'fire'
},
fire: function() {
alert('fire');
},
initialize: function() {
this.template = _.template($('#view-template-new-task').html());
},
render: function() {
this.$el.html(this.template());
return this;
}
});
var View = Backbone.View.extend({
events: {
'click button.add': 'addView'
},
addView: function(e) {
var task = new TaskView();
this.$('#tasks').append(task.render().el);
}
});
And an updated Fiddle http://jsfiddle.net/BLP6J/31/

Resources