simple Backbone event not firing - backbone.js

I have the following view and I'm trying to bind a click event to a button with the id of #add. However, it won't even register the preventDefault function.
var app = app || {};
app.LibraryView = Backbone.View.extend({
el: '#books',
events: {
'click #add' : 'addBook'
},
initialize: function() {
this.collection = new app.Library();
this.render();
this.listenTo( this.collection, 'add', this.renderBook);
},
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 );
},
addBook: function(e) {
e.preventDefault();
var formData = {};
$('#addBook div').children('input').each(function(index, el) {
if($(el).val() != '') {
formData[el.id] = $(el).val();
}
});
this.collection.add(new app.Book(formData));
console.log("book added");
}
});
Also, I can call renderBook in the console and it will add a new bookView, however, the render function doesn't seem to be working when the page loads initially.
Here is my html in jade.
block content
#books
form#addBook(action'#')
div
label(for='coverImage').span5 Image URL:
input#coverImage(type='text')
label(for='title').span5 Title:
input#title(type='text').span5
label(for='author') Author:
input#author(type='text').span5
label(for='date') Date:
input#date(type='text').span5
label(for='tags') Tags:
input#tags(type='text').span5
button#add Add
.bookContainer
ul
li Title
li Author
li Date
button Delete

You have to instantiate your view new app.LibraryView() in order for your view's events to be binds.

Related

reuse Bootsrap Modal view in BackboneJS

I just started to learn BackboneJS and getting deeper inside I face a problem. I have a bootstrap modal where I would like populate the modal-content in function of a called event fired in my main view and try to figure out how to inject a subview in my Modal view which is dynamically generated. So far my code looks like but not working
Main view
//here events are mapped
Fefe.Views = Fefe.Views || {};
(function () {
'use strict';
Fefe.Views.Editor = Backbone.View.extend({
template: JST['app/scripts/templates/editor.ejs'],
tagName: 'div',
el: '.container',
id: '',
className: '',
events: {
"click button.expand" : "controlToggle",
"click .grid" : "grid"
},
controlToggle: function(e){
var controlls = $(e.currentTarget).closest('.editor-controls')
$(controlls).find('.active').removeClass('active')
$(e.currentTarget).parent().addClass('active')
},
grid: function() {
this.model = new Fefe.Models.Grids({
'title': 'Edit Grids'
})
var gridView = new Fefe.Views.Grids({
model: this.model
})
var grids = new Fefe.Views.Modal({
model : this.model,
subview: gridView
}).render()
},
initialize: function () {
var body = $('body')
var rows = body.find('.row')
$.each(rows, function(e , v){
$(this).addClass('editor-row empty-row')
})
$('.sortable-rows').sortable({ handle: 'button.row-handle.btn.btn-default' })
this.listenTo(this.model, 'change', this.render);
},
render: function () {
return this;
}
});
})();
Modal view
//this one holds the modal markup
Fefe.Views = Fefe.Views || {};
(function () {
'use strict';
Fefe.Views.Modal = Backbone.Marionette.View.extend({
template: JST['app/scripts/templates/modal.ejs'],
subview: '',
className: "modal",
attributes: {
tabindex: "-1",
role: "dialog",
},
initialize: function() {
this.template = this.template;
console.log(this)
},
events: {
"click .save": "save",
"click .close": "close",
"change input": "modify",
},
render: function(e) {
this.$el.html(this.template(this.model.toJSON())).modal()
$(".modal-dialog").draggable({
handle: ".modal-header"
})
return this
},
show: function() {
$(document.body).append(this.render().el);
},
close: function() {
this.remove();
},
save: function() {
if(this.model.id == null) {
tasks.create(this.model);
}
else {
this.model.save();
}
this.remove();
},
edit: function(e) {
var attribute = {};
attribute[e.currentTarget.name] = e.currentTarget.value;
this.model.set(attribute);
},
});
})();
Maybe the approach is wrong and I'm on the wrong track
You should checkout the way with custom regions, described by Brian Mann at backbonerails.com
So the idea is following:
1) Define a region in your app with special class, lets call it DialogRegion
regions: {
dialogs: {
selector: '#dialogs',
regionClass: DialogRegion
}
}
2) Extend DialogRegion like following. I used Bootstrap modal API, please expect
var DialogRegion = Marionette.Region.extend({
onShow: function(view) {
view.$el.addClass('modal');
view.$el.modal();
// add handler to close popup via event
view.on('before:destroy', function() {
view.$el.modal('hide');
});
//destroy view on popup close
view.$el.on('hidden.bs.modal', function (e) {
view.destroy();
});
})
})
3) Later from any place of your app you can render Modal via rendering any view in dialogs App region:
App.dialogs.show( new SomeSuperView({
model: model
}))
I recommend you to checkout tutorial at Backbonerails to clarify this way. Hope you will find it usefull

Multiple Views and Sub Views with 1 collection in Backbone

I have issue in rendering Shopping Bag Views using Backbone for my website.
I am using 1 collection for all Bag Views (“Quick_View” of items list & “Normal_View” of Items list). Also I created “Item_View”, which is being used to render each item in both the Views.
It is a SPA (Single Page Application) and “Quick_View” is initiated and rendered for all Backbone routes and hidden by default. Whenever user clicks on “Quick_View” link from any page it is showing. There is no route defined for it.
The “Normal_View”, can be accessed using Checkout button given in “Quick_View”. It is bind with “domain/checkout” route.
When I access “Normal_View” from “Quick_View” check button; it works fine and both (Quick and Normal) views are in Sync. Means, when we add, delete, update any item in any of the View, both Views are getting updated accordingly.
But when I access “domain/checkout” route directly in a new browser, both views are getting rendered fine, but they are not in sync. Means, change in 1 view does not update another view.
The reason, I tracked is, when I access “Normal_View” through “Quick_View”, model for each item in both the Views having same CID, so the both Views are in sync, if there is any change in a model from any of the View.
And, when I access “Normal_View” directly, model for each item in both the views are not having same CID, so they do not work as expected.
There are few more points to consider:
Collection is firing reset event twice for “Quick_View” and each item
in “Quick_View” is rendering twice.
When, I access “Normal_View” (in either way), “Quick_View” is again getting rendered but once “Normal_View” rendering is over.
// Main View
var mainView = Backbone.View.extend({
el: 'body',
template: {
header: Handlebars.compile(headerTemplate),
mainNav: Handlebars.compile(mainNavtemplate),
footer: Handlebars.compile(footerTemplate)
},
initialize: function() {
_.bindAll();
AW.collection.bag = new bagCollection();
//AW.collection.bag.fetch({reset:true});
},
render: function() {
this.$el.html(this.template());
this.loadSubView('bagQV');
},
loadSubView: function(subView) {
switch(subView) {
case 'home' :
if(!AW.view.home) AW.view.home = new homepageView();
AW.view.home.render();
break;
case 'bagQV' :
if(!AW.view.bagQV) AW.view.bagQV = new bagQuickView({collection: AW.collection.bag});
//AW.view.bagQV.render();
break;
case 'checkout' :
if(!AW.view.checkout) AW.view.checkout = new checkoutView({collection: AW.collection.bag});
AW.view.checkout.render();
break;
}
}
});
// Single Item View
var bagItemView = Backbone.View.extend({
tagName: 'tr',
template: Handlebars.compile(bagItemTemplate),
initialize: function() {
_.bindAll(this);
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'remove', this.removeItem);
$(document).on('keyup', this.listenKeyboard);
},
events: {
'click .qtyInput .button' : 'updateItem',
'click .controls a.remove' : 'removeModel'
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.$el.attr('data-id',this.model.cid);
return this;
},
updateItem: function(e) {
e.preventDefault();
e.stopPropagation();
var newQty = this.$el.find('.qtyInput input').val();
var newAmt = newQty * parseFloat(this.model.get('prodRate').replace('$',''));
this.model.set({prodQty: newQty, amount: '$' + newAmt});
this.cancelEdit(e);
},
removeModel: function(e) {
e.preventDefault();
e.stopPropagation();
if(AW.collection.bag) AW.collection.bag.remove(this.model);
},
removeItem: function() {
this.$el.remove();
}
});
// Bag Quick View
var bagQuickView = Backbone.View.extend({
tagName: 'div',
id: 'myBagQV',
template: Handlebars.compile(bagQuickViewTemplate),
initialize: function() {
_.bindAll(this);
this.collection.fetch({reset:true});
//this.collection.bind("reset", _.bind(this.render, this));
this.listenTo(this.collection, 'add', this.addItem);
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
if($('#myBagQV').length == 0) {
this.$el.html(this.template());
$('body').append(this.el);
}
this.addAllItems();
return this;
},
addItem: function(item) {
var parent = this;
var itemView = new bagItemView({model: item});
$('#itemsInBag table tbody').append(itemView.render().el);
},
addAllItems: function() {
if(this.collection.length > 0) {
$('#itemsInBag table tbody').html('');
this.collection.each(this.addItem, this);
}
},
});
// Normal Bag View
var bagView = Backbone.View.extend({
tagName: 'div',
id: 'myBag',
template: Handlebars.compile(checkoutBagTemplate),
initialize: function() {
_.bindAll(this);
this.collection.fetch({reset:true});
//this.collection.bind("reset", _.bind(this.render, this));
this.listenTo(this.collection, 'add', this.addItem);
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
this.$el.html(this.template());
$('#checkoutContainer #details').append(this.el);
this.addAllItems();
return this;
},
addItem: function(item) {
var parent = this;
var itemView = new bagItemView({model: item});
this.$el.find('table tbody').append(itemView.render().el);
},
addAllItems: function() {
if(this.collection.length > 0) {
this.$el.find('table tbody').html('');
this.collection.each(this.addItem, this);
}
}
});
Looking for you help.
Thank you in advance
Cheers,
Vikram

Backbone collection is not rendering

I have problem with render of collection. Its simple model with title and boolean 'completed', when you click on list item it's changing to completed value (true/false). Value is changed ( I know it because when I refresh page, in initialize after fetch() I have collection.pluck, where order made by comparator is correct), but view looks all the time the same.
In collection I have comparator which works like I described upper, after collection.fetch() I have pluck, and pluck gives me well sorted list (but in view I see bad, default order). I dont know how to refresh collection to be well sorted.
Collection is just:
var TodolistCollection = Backbone.Collection.extend({
model: TodoModel,
localStorage: new Store('todos-backbone'),
// Sort todos
comparator: function(todo) {
return todo.get('completed');
}
});
Model is:
var TodoModel = Backbone.Model.extend({
defaults: {
title: '',
completed: false
},
// Toggle completed state of todo item
toggle: function(){
this.save({
completed: !this.get('completed')
});
}
});
return TodoModel;
Single todoView is:
var TodoView = Backbone.View.extend({
tagName: 'li',
template: JST['app/scripts/templates/todoView.ejs'],
events: {
'click .js-complete': 'toggleCompleted'
},
initialize: function(){
this.listenTo(this.model, 'change', this.render);
},
render: function() {
this.$el.html( this.template( this.model.toJSON() ));
this.$el.toggleClass( 'l-completed', this.model.get('completed') );
return this;
},
// Toggle the `"completed"` state of the model.
toggleCompleted: function() {
this.model.toggle();
}
and app View:
var ApplicationView = Backbone.View.extend({
el: $('.container'),
template: JST['app/scripts/templates/application.ejs'],
events: {
'click #add' : 'createOnEnter',
'keypress #addTodo' : 'createOnEnter'
},
// aliasy do DOMu,
// nasluchiwanie w kolekcji czy zaszlo jakies zdarzenie, jesli tak, to wykonuje funkcje
initialize: function() {
this.$input = this.$('.js-input');
this.listenTo(todoList, 'add', this.addOne);
this.listenTo(todoList, 'reset', this.addAll);
this.listenTo(todoList, 'all', this.render);
todoList.fetch();
console.log(todoList.pluck('title'));
},
render: function() {
},
// Generate the attributes for a new Todo item.
newAttributes: function() {
return {
title: this.$input.val().trim(),
completed: false
};
},
// Tworzy nowy model dzieki newAtributes() do localStorage
addTodo: function ( e ) {
e.preventDefault();
if( e.which !== Common.ENTER_KEY || !this.$input.val().trim() ){
return;
}
todoList.create( this.newAttributes() );
this.$input.val('');
},
// Tworzy model i dopisuje go do listy
addOne: function( todo ){
var view = new todoView({ model: todo });
$('.js-todolist').append( view.render().el );
},
// Tworzy nowego todo gdy nacisniemy enter
createOnEnter: function( e ) {
if( e.which !== Common.ENTER_KEY || !this.$input.val().trim() ){
return;
}
todoList.create( this.newAttributes() );
this.$input.val('');
},
// Przy rerenderze, dodaj wszystkie pozycje
addAll: function() {
this.$('.js-todolist').html('');
todoList.each(this.addOne, this);
}
});
return ApplicationView;
When I change listenTo render like that: this.listenTo(todoList, 'all', function(){console.log('whateva')}); I can see that on my click 'all' is triggering (even three times per one click ;s ).
Its hard for me to put it on jsfiddle, but here's git link with all files: https://github.com/ozeczek/ozeczek/tree/master/bb-todo-yo/app/scripts
In app view initialize I've changed todoList.fetch() to todoList.fetch({reset:true});
Second problem was to show in browser right order of todos, I've added to initialize:
this.listenTo(todoList, 'change', this.walcze);
this.listenTo(todoList, 'remove', this.walcze);
and walcze body function is:
walcze: function(){
todoList.sort();
this.$('.js-todolist').html('');
todoList.each(this.addOne, this);
}
Now every time Todo paremeter complete is changed, Im sorting list (comparator by itself isn't), clearing div with list, and rewriting whole list. I think it is not the best way of doing it, but it works.

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/

How to bind elements in backbone?

I have a small backbone class:
view = Backbone.View.extend({
events: {
"click textarea" : "doSomething"
},
doSomething : function() {
var textarea = $(this.el).find('textarea')
// I would like to just call, this.textarea, or this.elements.textarea
}
});
Ideally I would like to be able to access my textarea through a variable instead of having to search the element every time. Anyone have any idea on how to accomplish this?
maybe i am under thinking it but how bout giving the textarea a class or id and target specifically when needed,
or create a sub view that generates the textarea
var View = Backbone.View.extend({
el: '#main',
initialize: function(){
this.render();
},
render: function() {
var subview = new SubView();
this.$('form').append(subview.el);
this.$('form').show();
},
});
var SubView = Backbone.View.extend({
tagName: 'textarea',
id: 'whateverId',
events: {
"click" : "doSomething"
},
initialize: function(){
this.render();
},
doSomething : function(event) {
var textarea = $(event.target);
// or var textarea = $(this.el);
},
render: function(){
return $(this.el);
}
});
The other answers get you the reference that you need, but if you really need a handle to the textarea, then you can do something like this:
view = Backbone.View.extend({
initialize: function() {
this.myTextareaElement = $(this.el).find('textarea')
}
events: {
"click textarea" : "doSomething"
},
doSomething : function() {
// use this.myTextareaElement ...
}
});
pass the event as the argument and then use the event target
view = Backbone.View.extend({
events: {
"click textarea" : "doSomething"
},
doSomething : function(event) {
var textarea = $(event.target);
}
});

Resources