How to use events on backboneJS views - backbone.js

So I'm trying to learn how to use the backbone events using the documentation
but I'm stuck at the events part, when I click on the page class content it should alert "page tag has been clicked" but it throws an error on the commented line.
<body>
<div class="page"></div>
</body>
<script type="text/javascript">
var View = Backbone.View.extend({
initialize: function()
{
this.render();
},
render: function()
{
this.$el.html('Click me im an element');
},
events: function()
{
//Uncaught SyntaxError: Unexpected token :
"click .page" : "callme"
},
callme: function()
{
alert('page tag has been clicked');
}
});
var view = new View({
el: '.page'
});
</script>

Your events property must be either a hash
events: {
"click .page" : "callme"
}
or a function that returns a hash
events: function() {
return {
"click .page" : "callme"
};
}
You indicate a .page selector but that's your view element. Either use a global selector
events: {
"click " : "callme"
}
or set your el to an ancestor node, for example
var view = new View({
el: 'body'
});

The events is actually just an object it isn't a function. It'll work with the code below. Hope that helps.
events: {
"click .page" : "callme"
}
The other error you have is that you're not actually using the Backbone view.
You create the View correctly but you're not appending it to the view as far as I can tell.
You'd need to do something like.
$('body').html(view.render().el);
That will append your view to the DOM and add all of the event listeners.
Also instead of passing in the el to the BackboneView you could just add the class of page onto the View. Example below.
className: page,

You actually have two problems with your code, the first is that you are using a function for our events hash without returning an object (using a function is fine, but you need to return an object, or bind directly to the events hash). The second is that your are listing for an event for a child element with the page class, which doesn't exist. You want to either remove the class from where you are binding to the event and just listen to a click anywhere in your view, or listen to an existing element.
For example
var View = Backbone.View.extend({
initialize: function()
{
this.render();
},
render: function()
{
this.$el.html('Click me im an element');
},
events: {
"click" : "callme"
},
callme: function()
{
alert('page tag has been clicked');
}
});
var view = new View({
el: '.page'
});
http://jsbin.com/temicema/1/
That should be enough to get your code working, however it is probably worth understanding how backbone events work. When you specify an event in backbone that event is bound to the root el and and then listens to the events you specify matching the selector you specified. Under the hood backbone is basically using jQuery's .on to delegate the events, so in your case backbone is basically doing this.$el.on('click, '.page', this.callme).

Related

Bug while creating object in View

I'm working on a backbone.js project which is mainly to learn backbone framework itself.
However I'm stuck at this problem which i can't figure out but might have an idea about the problem...
I've got an Create View looking like this...
define(['backbone', 'underscore', 'jade!templates/addAccount', 'models/accountmodel', 'common/serializeObject'],
function(Backbone, underscore, template, AccountModel, SerializeObject){
return Backbone.View.extend({
//Templates
template: template,
//Constructor
initialize: function(){
this.accCollection = this.options.accCollection;
},
//Events
events: {
'submit .add-account-form': 'saveAccount'
},
//Event functions
saveAccount: function(ev){
ev.preventDefault();
//Using common/serializeObject function to get a JSON data object from form
var myObj = $(ev.currentTarget).serializeObject();
console.log("Saving!");
this.accCollection.create(new AccountModel(myObj), {
success: function(){
myObj = null;
this.close();
Backbone.history.navigate('accounts', {trigger:true});
},
error: function(){
//show 500?
}
});
},
//Display functions
render: function(){
$('.currentPage').html("<h3>Accounts <span class='glyphicon glyphicon-chevron-right'> </span> New Account</h3>");
//Render it in jade template
this.$el.html(this.template());
return this;
}
});
});
The problem is that for every single time I visit the create page and go to another and visit it again. It remebers it, it seems. And when i finally create a new account I get that many times I've visited total number of accounts...
So console.log("Saving!"); in saveAccount function is called x times visited page...
Do I have to close/delete current view when leaving it or what is this?
EDIT
Here's a part of the route where i init my view..
"account/new" : function(){
var accCollection = new AccountCollection();
this.nav(new CreateAccountView({el:'.content', accCollection:accCollection}));
console.log("new account");
},
/Regards
You have zombie views. Every time you do this:
new CreateAccountView({el:'.content', accCollection:accCollection})
you're attaching an event listener to .content but nothing seems to be detaching it. The usual approach is to call remove on a view to remove it from the DOM and tell it to clean up after itself. The default remove does things you don't want it to:
remove: function() {
this.$el.remove();
this.stopListening();
return this;
}
You don't want that this.$el.remove() call since your view is not responsible for creating its own el, you probably want:
remove: function() {
this.$el.empty(); // Remove the content we added.
this.undelegateEvents(); // Unbind your event handler.
this.stopListening();
return this;
}
Then your router can keep track of the currently open view and remove it before throwing up another one with things like this:
if(this.currentView)
this.currentView.remove();
this.currentView = new CreateAccountView({ ... });
this.nav(this.currentView);
While I'm here, your code will break as soon as you upgrade your Backbone. As of version 1.1:
Backbone Views no longer automatically attach options passed to the constructor as this.options, but you can do it yourself if you prefer.
So your initialize:
initialize: function(){
this.accCollection = this.options.accCollection;
},
won't work in 1.1+. However, some options are automatically copied:
constructor / initialize new View([options])
There are several special options that, if passed, will be attached directly to the view: model, collection, el, id, className, tagName, attributes and events.
so you could toss out your initialize, refer to this.collection instead of this.accCollection inside the view, and instantiate the view using:
new CreateAccountView({el: '.content', collection: accCollection})

listenTo not working when using in another view in backbone.js

I am a newbee to backbone.I have a view called AbcView
abc.js
var AbcView = Backbone.View.extend({
events: {
"click" : "display",
},
display: function(e){
console.log("hello");
alert("click function");
}
});
Now I am passing this abc.js to another xyz.js file and calling it in another view using ListenTo.
xyz.js
var xyzView = Backbone.View.extend({
initialize: function(){
var AbcView = new AbcView ();
this.lisenTo(AbcView, "click",this.display);
},
render: function(){
var html = this.template(AbcView);
this.$el.html(html);
return this;
},
display: function(e){
console.log("parent hello");
alert("parent display function");
}
});
With abc.js click event is triggering fine. But with xyz.js click event is not triggering.
Is this the correct way to call listenTo.
DOM events aren't delegated on the View object.
If you want to emulate this though, you'd have to manually emit the event in ABC display method:
display: function(e){
// Trigger manually
this.trigger("click");
// Your usual code
console.log("hello");
alert("click function");
}
In term of best practice, I'd probably rename "click" to a more descriptive event name.
Backbone's on and listenTo are intended for listening to events on Backbone Models and Collections.
http://backbonejs.org/#Events-catalog
This is an important thing to understand. It is not the same as UI event binding, as described in http://backbonejs.org/#View-delegateEvents.
That being said, you can use something like what Simon suggests to intermingle the two.

Backbone view's "delete" button is deleting all elements from the collection as opposed to the targeted element only

EDIT1: I have tried many different strategies for deleting the element including this.model.destroy(), this.collection.remove(this.model), etc. I have also bound the events using the events: syntax and using a direct binding strategy in both initialize and render.
If I change the code to use this.collection.add instead of remove it adds an additional X notes where X is the number of notes currently in the collection.
I am writing a basic list app (to be fleshed out eventually) and I want each element in the list to have a button that will delete it from the list.
I am using
require.js
backbone
lo-dash
jquery
My code snippets below show my view code for the Notes View, Note View and some supporting into including the templates being used to render each.
At the moment, the code is not functioning as desired because clicking ANY Note's "hide" button causes all elements in the collection to be removed one at a time. I know that is what is happening because I can insert alerts at the end of the "deletenote" method which allows me to view the deletion piece-wise.
I also know that the parent render method is not the cause of the problem because I can turn off the event callback to re-render the parent NotesView and all Note views are still deleted.
<script type="text/template" id="noteslist-template">
<ul id="noteslist" style="list-style: none;">
</ul>
</script>
<script type="text/template" id="note-template">
<li>
<button type="button" class="notesbutton" id="hidenote">hide</button>
<div id="note"><%= text %></div>
</li>
</script>
NotesView.js
define
([ 'jquery',
'underscore',
'backbone',
'notes/sample/sampletext', //included to populate a collection for rendering *REMOVE ONCE DONE TESTING*
'notes/collections/Notes', //included to create a new instance of the Notes collection during the initialize method of this view
'notes/models/Note', //included to reference the model when creating NoteView instances for rendering
'notes/views/NoteView' ], //included to call render functions on each Note model in the collection
function($,_,Backbone,SampleText,Notes,Note,NoteView)
{
var NotesView = Backbone.View.extend
({
initialize: function()
{
this.template = _.template($('#noteslist-template').html());
_.bindAll(this,'render','rendernote');
this.collection.bind('add',this.render);
this.collection.bind('remove',this.render);
},
render: function()
{
console.log('collection render');
this.$el.html(this.template({})); //change to this.notelist = THISLINE
this.collection.each(this.rendernote);
//add call to render notelist to DOM
return this;
},
rendernote: function(note) //add notelist variable
{
console.log('collection rendernote');
var noteview = new NoteView(
{ model:note,
collection:this.collection} );
//add notelist += LINEBELOW
noteview.setElement(this.$('#noteslist'));
noteview.render();
//change noteview.render to NOT write to DOM
}
});
return NotesView;
}
);
NoteView.js
define
( [ 'jquery',
'underscore',
'backbone',
'notes/models/Note', ], //include the Note model to reference as the model for the collection
function($,_,Backbone,Note)
{
var NoteView = Backbone.View.extend
({
tagName: "li",
className: "note",
events:
{
'click #hidenote':'deletenote',
},
initialize: function()
{
_.bindAll(this,'render','remove','deletenote');
//this.model.bind('change',this.render);
this.template = _.template($('#note-template').html());
this.model.bind('remove', this.remove);
},
render: function()
{
this.notetorender = this.template(this.model.toJSON());
this.$el.append(this.notetorender);
return this;
},
deletenote: function()
{
this.options.collection.remove(this.model);
}
});
return NoteView;
}
);
noteview.setElement(this.$('#noteslist')); causes event delegation.
So eventhough you wrote 'click #hideNotes : deleteNode' inside NoteView, after the setElement call it works like that code is present inside NotesListView.
So when you click hide button of single li and expect only that li to be removed from ul however the click event is received by all li's hide button.That's why all items in collection are deleted .
JsFiddle here i do the same thing without using setElement
var NoteView = Backbone.View.extend({
events: {'click #hidenote': 'deletenote'},
initialize: function(){
this.model.on('destroy', this.$el.remove)
},
deletenote: function(){
this.model.destroy()
}
})

Backbone custom event trigger not being recognised?

I'm learning Backbone.js for the first time and I'm having an issue trying to get a custom event from triggering (or from the View from recognising when it's been triggered)?
You can see my Collection code here: https://github.com/Integralist/Backbone-Playground/blob/master/Assets/Scripts/App/main.js#L72-86 which when initialized it triggers a custom collection:init event.
var Contacts = Backbone.Collection.extend({
model: Contact,
initialize: function(){
this.trigger('collection:init');
this.bind('add', this.model_added, this);
},
model_added: function(){
console.log('A new model has been created so trigger an event for the View to update the <select> menu');
}
});
But later on in my View where I'm listening for that event I can't get the function populate to fire: https://github.com/Integralist/Backbone-Playground/blob/master/Assets/Scripts/App/main.js#L90-107
var ContactsView = Backbone.View.extend({
initialize: function(){
console.log(contacts.models, 'get initial model data and populate the select menu?');
},
events: {
'collection:init': 'populate',
'change select': 'displaySelected'
},
populate: function(){
console.log('populate the <select> with initial Model data');
},
displaySelected: function (event) {
console.log('get model data and display selected user', event);
}
});
Any ideas what I'm doing wrong?
The events hash in a view is used to bind events from the DOM to your view, e.g. events raised by the elements in your rendered view. To listen to events raised by your collection, you will have to set them manually:
var ContactsView = Backbone.View.extend({
initialize: function(){
contacts.on("collection:init",this.populate,this);
}
...
});
Note that you are using a global contacts variable, I would advise to use Backbone mechanisms and pass your collection to the constructor, as you do with the el:
var ContactsView = Backbone.View.extend({
initialize: function(){
console.log(this.collection.models);
this.collection.on("collection:init",this.populate,this);
}
...
});
var contacts_view = new ContactsView({
el: $('#view-contacts'),
collection:contacts
});
As #mu said in the comments, as is, your event won't do anything since you trigger it in the initialize method of the collection, which is automatically called by the constructor of the collection therefore before you can bind anything in the view. See this Fiddle to visualize the call order : http://jsfiddle.net/yRuCN/
Trigger it elsewhere, or, if I read correctly your intent, you (probably) want to use the built in reset event:
var ContactsView = Backbone.View.extend({
initialize: function(){
this.collection.on("reset",this.populate,this);
}
...
});
See http://jsfiddle.net/yRuCN/1/ for an example with potential uses.

Event delegation failing to attach events in Backbone.js app

My Backbone.js app has a popup editor view that will be repeatedly closed and reopened as the user makes changes. I'm trying to figure out the cleanest way to implement this, and I'm stuck on an event delegation issue.
I believe the problem lies in the way I'm applying the template in my render method. I haven't had any issues with getting events to fire in other views, but those views differed in that they had a model. The view I'm having issues with is more of an application view that contains sub-views, so I'm not sure how to pass the view's context to the MyApp view.
Here's my code:
MyApp = Backbone.View.extend({
tagName: 'div',
template: _.template($('#app-template').html()),
initialize: function() {
_.bindAll(this);
this.render();
},
render: function() {
$('#container').html(this.template);
return this;
},
events: {
"click .save" : "onSaveClicked"
},
onSaveClicked: function () {
console.log("Save clicked.");
this.$el.remove();
}
});
$('#show').click(function () {
var myapp = new MyApp;
});
I've also posted it as a jsFiddle.
I stepped through the Backbone.js source, and it appears that render is called first, then events are assigned, which is what I'd expect. Everything looks OK from what I can tell, but onSaveClicked never fires when I click Save.
The desired functionality is that clicking Show displays the form and Save removes it. If there's a better way to do this that's more inline with Backbone's underlying philosophy I'm open to that as well. You'll notice that I'm nesting an unnamed div inside container, and that's because I wanted to maintain a consistent anchor point for my popup.
The events are bound to the view el, but you never append the el to the DOM. Try
MyApp = Backbone.View.extend({
tagName: 'div',
template: _.template($('#app-template').html()),
initialize: function() {
_.bindAll(this);
this.render();
},
render: function() {
this.$el.html(this.template);
return this;
},
events: {
"click .save" : "onSaveClicked"
},
onSaveClicked: function () {
console.log("Save clicked.");
this.$el.remove();
}
});
$('#show').click(function () {
var myapp = new MyApp;
$("#container").append(myapp.$el);
});
http://jsfiddle.net/WBPqk/18/
Note that in your Fiddle you bound the click event to .save where your template uses a done class.

Resources