Backbone view class events overwritten when creating instance - javascript

I have created a class inherited from Backbone.View which defines some DOM events:
var MyView = Backbone.View.extend({
el: '#myview',
events: {
'click .somebutton': 'somefunction',
'click .otherbutton': 'otherfunction'
},
somefunction: function(){ console.log('somefunction!'); },
otherfunction: function(){ console.log('otherfunction!'); }
});
When instantiating this view (new MyView();) all seem to be in order and my callbacks are fired whenever the elements are clicked.
However if I instantiate my view like this:
new MyView({
events: {
'click .thirdbutton': function(){
console.log('thirdfunction');
}
}
});
All my existing class events get overriden by this single one. What is the correct way to merge my instance-only events with the existing class events? In my example I want all 3 events to be active in my instance.

You could do it in the initialize -method
initialize: function() {
_.extend(this.events, {
'click .thirdbutton': function() {...}
});
}
This might not be the prettiest of answers, but it should work.

Found a solution, thanks to this answer:
Backbone View: Inherit and extend events from parent
I added a extraEvents key to the options of the class, and I changed my events object to a function that merges the extra events. Code example below, if it helps anyone else:
var MyView = Backbone.View.extend({
el: '#myview',
options: {
extraEvents: {}
},
originalEvents: {
'click .somebutton': 'somefunction',
'click .otherbutton': 'otherfunction'
},
events: function(){
return _.extend({}, this.originalEvents, this.options.extraEvents);
},
somefunction: function(){ console.log('somefunction!'); },
otherfunction: function(){ console.log('otherfunction!'); }
});
Now I can instantiate my view like this:
new MyView({
extraEvents: {
'click .thirdbutton': function(){
console.log('thirdfunction');
}
}
});

Related

Remove corresponding item when delete button is clicked. BackboneJs

I am currently learning BackboneJs and trying to understand how Backbone handles events. I have a simple list of items and each item has a delete button right next to it. I'm trying to figure out why the click event(Delete button) is registered in the console but the item is not removed. Here's what I have:
var Vehicle = Backbone.Model.extend();
var Vehicles = Backbone.Collection.extend({
model: Vehicle
});
/*************
single view
**************/
var VehicleView = Backbone.View.extend({
tagName: 'li',
className: 'vehicle',
render: function() {
this.$el.html(this.model.get("title") + " Registration Number is: " + this.model.get("regiNum") + " <button class='delete-btn'>Delete</button>");
this.$el.attr("id", this.model.id);
return this;
}
});
/*************
Collection View
*************/
var VehiclesView = Backbone.View.extend({
tagName: "ul",
initialize: function() {
this.model.on('remove', this.vehicleRemove, this);
},
events: {
"click .delete-btn": "vehicleRemove"
},
vehicleRemove: function(vehicle) {
this.$("li#" + vehicle.id).remove() // this is not working. the item is not being removed
console.log('Delete button clicked') // this is registered in the console log
},
render: function() {
var self = this;
this.model.each(function(vehicle) {
var vehicleView = new VehicleView({
model: vehicle
});
self.$el.append(vehicleView.render().$el);
})
}
});
var vehicles = new Vehicles([
new Vehicle({
id: 1,
title: "Toyota",
regiNum: "453454624"
}),
new Vehicle({
id: 2,
title: "Honda",
regiNum: "daf4526"
}),
new Vehicle({
id: 3,
title: "Audi",
regiNum: "jlkjfa34"
})
])
var vehiclesView = new VehiclesView({
el: "#container",
model: vehicles
});
vehiclesView.render();
Please help me out or point me to the right direction would be greatly appreciated.
Since you have an item view, it's better to give it the functionality for removing itself. This way you don't have to hack your way reading stuff from DOM to find the related model based on the clicked item view element.
Also, you should use the remove() method of Backbone.View rather than the jQuery remove() method, because it safely removes the backbone events registered on the view, as well as calls jQuery remove() to remove the element from DOM.
You can call model.destroy() which will signal your persistence layer to remove the model and removes it from collection as well. Since you don't have a persistence layer in this example, I'm triggering a custom event on the item view's model which is handled in the collection for removing the model from it (model events propagates to it's collection).
There is no need to manually initialize models in a collection by yourself, backbone does it automatically, that's what the collections model property is for.
You should use some sort of templating engine rather than doing string manipulation in the view for rendering, you have _.template() at your disposal anyway.
Also, as dskoda1 already mentioned, you shouldn't pass a Backbone.Collection using the model option, model and collection are two options that will be detected by Backbone.view. Even if it does no harm, it's still very confusing.
var Vehicle = Backbone.Model.extend();
var Vehicles = Backbone.Collection.extend({
model: Vehicle,
initialize: function() {
this.on('delete', this.remove);
}
});
var VehicleView = Backbone.View.extend({
tagName: 'li',
className: 'vehicle',
template: _.template($('#vehicle-template').html()),
events: {
"click .delete-btn": "vehicleRemove"
},
initialize: function() {
this.render();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
vehicleRemove: function(vehicle) {
this.remove();
//this.model.destroy(); /* The collection should have a url */
this.model.trigger('delete', this.model);
},
});
var VehiclesView = Backbone.View.extend({
tagName: "ul",
initialize: function() {
this.render();
},
render: function() {
this.collection.each(function(vehicle) {
var vehicleView = new VehicleView({
model: vehicle
});
this.$el.append(vehicleView.$el);
}, this)
}
});
var vehicles = new Vehicles([{
id: 1,
title: "Toyota",
regiNum: "453454624"
}, {
id: 2,
title: "Honda",
regiNum: "daf4526"
}, {
id: 3,
title: "Audi",
regiNum: "jlkjfa34"
}]);
var vehiclesView = new VehiclesView({
el: "#container",
collection: vehicles
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.2.3/backbone-min.js"></script>
<div id="container"></div>
<script type="text/template" id="vehicle-template">
<%=title%>Registration Number is:
<%=regiNum%>
<button class='delete-btn'>Delete</button>
</script>
A useful atrribute you might want to use on the buttons would be data-id. Setting this value to the models id would allow you to more cleanly select the correct model for deletion. The new button html would now be:
"<button class='delete-btn' data-id=" + this.model.get('id') + ">Delete</button>"
Having buttons like this, the new click event could be fired as such:
vehicleRemove: function(e) {
e.preventDefault(); //Good practice for button clicks
var id = $(e.currentTarget).data('id'); //Id of model clicked
//Only one of these next two lines needs to be used
this.collection.remove(id); //If not persisting to server
this.collection.remove(id).destroy() //If persisting to server
},
Also, when you instantiate your VehiclesView, since you're passing it a collection called vehicles, that attribute should really be called collection, not model. An attribute named model is typically used on a single model view, or to represent which model a collection represents. The appropriate changes would need to be made inside of VehiclesView, i.e. replace every instance of the word model with collection.
this.remove() ?
At this point, Backbone already knows which model you're trying to delete so there is no need to specify a jQuery selector.
Of course, .remove() is only going to remove the element from the DOM. Are you looking for model.destroy() which will send a DELETE command to the server?

How to find which event has fired on the collection

I had a collection called approvals on which I had an event on sync and delete event types of collection to renderRows. check below code I need to reset the current collection based on the approval collection delete event.
this.approvals.on("sync delete", this.renderRows, this);
function renderRows(model, e, event ) {
//some code
if (event.type == "delete") {
this.collection.reset();
}
}
But I am getting the event as undefined. Can you please let me know how to get the event.type for collections
You also have this option:
this.listenTo(this.approvals, 'sync', _.partial(this.renderData, 'sync'));
this.listenTo(this.approvals, 'delete', _.partial(this.renderData, 'delete'));
and renderData (or however you want to call it) gets one extra param which you're passing with the _.partial (curry)
renderData: function(eventName, collection, resp, options) {}
this is the method signature: http://backbonejs.org/docs/backbone.html#section-133
collection.trigger('sync', collection, resp, options); delete looks the same
Looks like this as a basic example: (cant do delete but i can trigger change, just wait 5 seconds)
var Model1 = Backbone.Model.extend({
url: 'http://jsonplaceholder.typicode.com/posts/1'
});
var View1 = Backbone.View.extend({
template: _.template('<%= eventName %> - <%= body %>'),
initialize: function() {
// render something as soon as possible
this.render();
this.model = new Model1();
this.listenTo(this.model, 'sync', _.partial(this.renderData, 'sync'));
this.listenTo(this.model, 'change', _.partial(this.renderData, 'change'));
this.model.fetch();
// to test it
setTimeout(_.bind(function(){this.model.set('body', 'it was changed')}, this), 5000);
},
// this is the normal sync/change function signature only with one extra param `eventName`
// which is being `curry`'ed in
renderData: function(eventName, model, resp, options) {
this.$el.html(this.template({
'eventName': eventName,
'body': model.get('body')
}));
return this;
},
render: function() {
this.$el.html('nothing to see here');
return this;
}
});
new View1({el: $('body').append($('<div>'))});
Run it here: http://jsfiddle.net/tLaLykk8/
The event name isn't passed through unless it was specifically passed as an arg e.g. a trigger('sync', 'sync'). So you can either inspect the arguments (because they are different depending on the event in this case I think) - but that's a bad idea because they could change and it makes your code fragile. Your best bet is to simply split it out:
this.listenTo(this.approvals, "sync", this.onApprovalSync);
this.listenTo(this.approvals, "delete", this.onApprovalDelete);
onApprovalSync: function() {
this.renderRows();
}
onApprovalDelete: function() {
this.collection.reset();
this.renderRows();
}
From what I understood you want to have a common handler for sync and remove events and wants to identify which event triggered the handler.
Since the signature of the callbacks of these events are different:
remove: (model, collection, options)
and sync: (model_or_collection, resp, options)
We can achieve this by checking the type of arguments passed to the handler as shown below:
var View = Backbone.View.extend({
initialize: function() {
this.listenTo(this.collection, 'sync', this.dosomething);
this.listenTo(this.collection, 'remove', this.dosomething);
this.render();
},
events: {
'click #remove': 'triggerRemove',
'click #sync': 'triggerSync',
},
render: function() {
this.$el.html('<button id="remove">Trigger remove</button><button id="sync">trigger sync</button>').appendTo('body');
return this;
},
triggerRemove: function() {
var model = this.collection.add({
name: 'test'
});
this.collection.remove(model);
},
triggerSync: function() {
this.collection.trigger('sync');
},
dosomething: function(model) {
if (arguments[1] instanceof Backbone.Collection) //second argument is a collection
console.log('remove triggered')
else
console.log('sync triggered')
}
});
var view = new View({
collection: new Backbone.Collection([{
name: 'hi'
}, {
name: 'hellow'
}])
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.2.3/backbone-min.js"></script>

Backbonejs events syntax on objects

Suppose that I have the following view:
var SampleView = Backbone.View.extend({
initialize: function() {
this.child_element = this.$('#new-catalog>button:first');
},
events: {
'click #new-catalog>button:first': function() { alert('clicked'); },
},
el: '#some-element',
});
If you have some non trivial selection for custom children items (like my child_element selected with #new-catalog>button:first, is it possible to avoid redefining it in the events property and instead refer to this.child_element?
Of course I can do this.child_element.on('click', ..., but I'd like to use the provided events dictionary.

Backbone.js model event not triggering

I've got the following view file:
var BucketTransferView = Backbone.View.extend(
{
initialize: function(args)
{
_.bindAll(this);
this.from_bucket = args.from_bucket;
this.to_bucket = args.to_bucket;
},
events:
{
'click input[type="submit"]' : 'handleSubmit',
},
render: function()
{
$(this.el).html(ich.template_transfer_bucket(this.model.toJSON()));
return this;
},
handleSubmit: function(e)
{
that = this;
this.model.save(
{
date: 1234567890,
amount: this.$('#amount').val(),
from_bucket_id: this.from_bucket.get('id'),
to_bucket_id: this.to_bucket.get('id')
},
{
success: function()
{
// recalculate all bucket balances
window.app.model.buckets.trigger(
'refresh',
[that.to_bucket.get('id'), that.from_bucket.get('id')]
);
}
}
);
$.colorbox.close();
}
});
My buckets collection has this refresh method:
refresh: function(buckets)
{
that = this;
_.each(buckets, function(bucket)
{
that.get(bucket).fetch();
});
}
My problem is that when the fetch() happens and changes the collection's models, it's not triggering change events in other view classes that has the same models in it. The view's models have the same cid, so I thought it would trigger.
What's the reason this doesn't happen?
Fetch will create new model objects. Any view that's tied to the collection should bind to the collection's reset event and re-render itself. The view's models will still have the same cid's because they're holding a reference to an older version of the model. If you look at the buckets collection it probably has different cids.
My suggestion is in the view that renders the buckets, you should render all the child views and keep a reference to those views. then on the reset event, remove all the child views and re-render them.
initialize: function()
{
this.collection.bind('reset', this.render);
this._childViews = [];
},
render: function()
{
_(this._childViews).each(function(viewToRemove){
view.remove();
}, this);
this.collection.each(function(model){
var childView = new ChildView({
model: model
});
this._childViews.push(childView);
}, this)
}
I hope this works for you, or at least gets you going in the right direction.

Loading initial data in Backbone.js

I'm new to backbone.js and MVC so apologise if this is a silly question...
I have been experimenting with some of the backbone.js tutorials out there and am trying to work out how to load an initial set of data onto the page.
If anyone could point me in the right direction or show me the what I'm missing below, it would be greatly appreciated!
Thanks!
The code is below or at: http://jsfiddle.net/kiwi/kgVgY/1/
The HTML:
Add list item
The JS:
(function($) {
Backbone.sync = function(method, model, success, error) {
success();
}
var Item = Backbone.Model.extend({
defaults: {
createdOn: 'Date',
createdBy: 'Name'
}
});
var List = Backbone.Collection.extend({
model: Item
});
// ------------
// ItemView
// ------------
var ItemView = Backbone.View.extend({
tagName: 'li',
// name of tag to be created
events: {
'click span.delete': 'remove'
},
// `initialize()` now binds model change/removal to the corresponding handlers below.
initialize: function() {
_.bindAll(this, 'render', 'unrender', 'remove'); // every function that uses 'this' as the current object should be in here
this.model.bind('change', this.render);
this.model.bind('remove', this.unrender);
},
// `render()` now includes two extra `span`s corresponding to the actions swap and delete.
render: function() {
$(this.el).html('<span">' + this.model.get('planStartDate') + ' ' + this.model.get('planActivity') + '</span> <span class="delete">[delete]</span>');
return this; // for chainable calls, like .render().el
},
// `unrender()`: Makes Model remove itself from the DOM.
unrender: function() {
$(this.el).remove();
},
// `remove()`: We use the method `destroy()` to remove a model from its collection.
remove: function() {
this.model.destroy();
}
});
// ------------
// ListView
// ------------
var ListView = Backbone.View.extend({
el: $('body'),
// el attaches to existing element
events: {
'click button#add': 'addItem'
},
initialize: function() {
_.bindAll(this, 'render', 'addItem', 'appendItem'); // every function that uses 'this' as the current object should be in here
this.collection = new List();
this.collection.bind('add', this.appendItem); // collection event binder
this.render();
},
render: function() {
_(this.collection.models).each(function(item) { // in case collection is not empty
appendItem(item);
}, this);
},
addItem: function() {
var item = new Item();
var planStartDate = $('#planStartDate').val();
var planActivity = $('#planActivity').val();
item.set({
planStartDate: planStartDate,
planActivity: planActivity
});
this.collection.add(item);
},
appendItem: function(item) {
var itemView = new ItemView({
model: item
});
$('ul', this.el).append(itemView.render().el);
}
});
var listView = new ListView();
})(jQuery);
Thanks.
Here's the modified example: http://jsfiddle.net/kgVgY/2/
You create the collection first with the data you want
var list = new List([{
createdOn: 'Jan',
createdBy: 'John',
planStartDate: "dfd",
planActivity: "dfdfd"
}]);
and then pass the collection to the view you want
var listView = new ListView({collection: list});
That's about all you had wrong in this code. Few minor unrelated notes:
You were using _(this.collection.models).each. Backbone collections use underscore to expose all those functions on themselves, so that is equivalent to this.collection.each
You don't really need the "unrender" method on the ItemView but since you aren't using that I'm guessing you're using it for debugging.

Categories

Resources