Call methods of different views in Backbone.js - javascript

I am using backbone.js with ASP.NET MVC 4.
I want to call methods of different view from one of the view. To make this simpler to understand I have created a small example below.
Here in the MyView2 in side the OperationCompleted method I want to call the following...
call myMethodB of MyView 2
call myMethodA of MyView 1
call myMethodC of AppView
How do I do this ? I have temporarily used something like creating objects of view and calling them.
Something like this var view1 = new MyView1(); and then view1.myMethodA();, there has to be a better way, Please help me find it. Thanks
var MyModel = Backbone.Model.extends({
});
// View for a Main Grid
var MyView1 = Backbone.View.extend({
...
myMethodA: function(){
// do something with View 1
}
...
});
// View for subgrid in Main Grid
var MyView2 = Backbone.View.extend({
...
myMethodB: function(){
// do something with View 2
},
OperationCompleted: function(){
// call myMethodB of MyView 2
// call myMethodA of MyView 1
// call myMethodC of AppView
}
...
});
var AppView = Backbone.View.extend({
...
myMethodC: function(){
// do something with App View
}
...
});

Got this working ! had to use the Aggregator pattern, have pasted below a sample example of how I used it...
Backbone.View.prototype.eventAggregator = _.extend({}, Backbone.Events);
var model = Backbone.Model.extend({
});
var view1 = Backbone.View.extend({
initialize: function () {
this.eventAggregator.bind("doSomething_event", this.doSomething);
},
doSomething: function(name){
alert("Hey " + name + " !");
}
});
var view2 = Backbone.View.extend({
callToDoSomething: function(){
self.eventAggregator.trigger("doSomething_event", "Yasser");
}
});
References
https://stackoverflow.com/a/11926812/1182982

Another pattern here would be to call a view's function by triggering an event on the DOM element the view is attached to.
For example:
var AppView = Backbone.View.extend({
el: 'body',
events: {
'alertMe': 'alertMe'
},
alertMe: function(ev, arg){
console.log(args)
alert("You've been alerted!")
}
});
Then at some point later in your code (even in another view):
// Shows an alert and prints object.
$('body').trigger('alertMe', { foo: 'bar' });

Related

Backbone JS Button to open a new view, save values in form

Im new to backbone and I'm looking to a very simple 2 view configuration page usig backbone.
I have the following code;
define(
["backbone","...","..."],
function(Backbone, ... , ... ) {
var PopupView = Backbone.View.extend({
initialize: function initialize() {
Backbone.View.prototype.initialize.apply(this,arguments);
},
events: {
"click .save_conf_button": "save_conf",
},
render: function() {
this.el.innerHTML = this.get_popup_template();
return this;
},
save:conf: function save_conf() {
//get the field values from popup_template
//var items = jquery(....);
});
var ExampleView = Backbone.View.extend({
//Starting view
initialize: function initialize() {
Backbone.View.prototype.initialize.apply(this, arguments);
},
events: {
"click .setup_button": "trigger_setup", //Triggers final setup
"click .create_conf_button": "trigger_popup_setup", //This is the conf popup
},
render: function() {
this.el.innerHTML = this.get_start_html();
return this;
},
trigger_popup_setup: function trigger_popup_setup() {
console.log("Pop up");
//this.el.innerHTML = this.get_popup_template();
PopupView.render();
...
},
}); //End of exampleView
return ExampleView;
} // end of require asynch
); // end of require
E.g. The ExampleView is the starting view with a couple of fields and 2 buttons; create popup and save. Upon pressing the create_conf_button I want to render the popup view, however this does not seem to work as I expected. (Uncaught TypeError: PopupView.render is not a function)
I'm not sure how to proceed and additionally what the "best practice" is for generating these types of dialogs?
Additionally, keeping the values filled in on the previous page after returning from the popupview would be preferential.
Thanks for any help
try
new PopupView.render()
you have to create an instance to call the methods this way
#ashish is correct, you have to instantiate an instance of the PopupView before calling its render method. Currently, you have defined a blueprint for a view called PopupView, which will act as a constructor for newly created PopupView view instances. In order to use this defined view I would suggest storing it in ExampleView's render or initialize method:
// Example View's initialize method
initialize: function initialize() {
this.popUpView = new PopupView();
Backbone.View.prototype.initialize.apply(this, arguments);
},
then referencing it in your trigger_popup_setup function as follows:
trigger_popup_setup: function trigger_popup_setup() {
console.log("Pop up");
//this.el.innerHTML = this.get_popup_template();
this.popUpView.render();
...
},
As for storing state Backbone models are used for that :)
In general to nest subviews within a master view in Backbone you can do the following:
initialize : function () {
//...
},
render : function () {
this.$el.empty();
this.innerView1 = new Subview({options});
this.innerView2 = new Subview({options});
this.$('.inner-view-container')
.append(this.innerView1.el)
.append(this.innerView2.el);
}
In this example the master view is creating instances of it's subviews within its render method and attaching them to a corresponding DOM element.

shared model in backbone not acquiring events

Here is what i have as the backbone code:
myEventModel = Backbone.Model.extend({});
var view_one = Backbone.View.extend({
initialize : function(){
myEventModel.on('change:someChange',function(){alert('Change observed for view one')});
}
render: function(){ $('body').append('view one')}
});
var view_two = Backbone.View.extend({
initialize : function(){
myEventModel.on('change:someChange',function(){alert('Change observed for view two')})
}
render: function(){ $('body').append('view two')}
});
So I have two views listening to a global model events. So when in console, I do
myEventModel.set({'someChange':new Date().getTime()});
i should see two alerts. But I don't.
jsfiddle
i've made some changes to your fiddle: http://jsfiddle.net/ya4t1c24/1/
you should create your model and views first, then events will fire.
myEventModel = Backbone.Model.extend({});
var view_two = Backbone.View.extend({
initialize : function(){
model.on('change:someChange',function(){alert('Change observed for view one')})
}
});
var view_one = Backbone.View.extend({
initialize : function(){
model.on('change:someChange',function(){alert('Change observed for view two')})
}
});
var model = new myEventModel;
var var_view_one = new view_one({model: model})
var var_view_two = new view_two({model: model})
model.set({'someChange':new Date().getTime()});
You are mixing the definition of the views/models with creating objects of those types.
After you have defined your model, you should create an object of that type:
var myEventModel = Backbone.Model.extend({});
var myEventModelInstance = new myEventModel();
Then in your views you want to listen to events fired from the object not from the model definition, so you should change your views as in:
var view_one = Backbone.View.extend({
initialize : function(){
myEventModelInstance.on('change:someChange',function(){alert('Change observed for view one');})
}
});
var view_two = Backbone.View.extend({
initialize : function(){
myEventModelInstance.on('change:someChange',function(){alert('Change observed for view two');})
}
});
Finally you need to create your view objects before changing the model object, as the view objects are the ones will react to the event, not the view definitions:
var viewOneInstance = new view_one();
var anotherViewOneInstance = new view_one();
var viewTwoInstance = new view_two();
myEventModelInstance.set({'someChange':new Date().getTime()});
With the code above you will see 2 alerts with the message 'Change observed for view one' because 2 view_one objects were created, and a single alert with the message 'Change observed for view two' as a single view_two object was created.
See updated fiddle

backbone paginator multi model in a view

I have a shopping cart app made with Backbone.Paginator.Fluenced and forked with this example; https://github.com/msurguy/laravel-backbone-pagination
I made some small changes;
when you click over an item link, it opens a bootstrap modal window.
The code is below.
app.views.ItemView = Backbone.View.extend({
tagName: 'div',
className: 'col-sm-4 col-lg-4 col-md-4',
template: _.template($('#ProductItemTemplate').html()),
events: {
'click a.openModal': 'openModal'
},
initialize: function() {
this.model.bind('change', this.render, this);
this.model.bind('remove', this.remove, this);
},
render : function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
openModal : function () {
var view = new app.views.ModalView({model:this.model});
view.render();
}
});
and this is my ModalView to show product details in a modal window.
app.views.ModalView = Backbone.View.extend({
template: _.template($('#modal-bsbb').html()),
initialize: function() {
_.bind(this.render, this);
},
render: function () {
$('#myModalPop').modal({backdrop: 'static',keyboard: true});
$('#myModalPop').html(this.template({
'model':this.model.toJSON()
}));
return this;
}
});
Everything is fine for above codes.
I decided to optimize this code and wanted some improvements on this.
Firstly I am fetching all product data and send these data to modal windows.
I think i must send only main meta data and must fetch details from these window.
So i made a new Backbone Model and Collection;
app.models.ItemDetails = Backbone.Model.extend({});
app.collections.ItemDetails = Backbone.Collection.extend({
model: app.models.ItemDetails,
dataType: 'json',
url : "/api/item-details",
parse: function(response){
return response.data;
}
});
My api returns JSON :
{"data":{"id":8,"title":"Product 8","seo":"product-8","code":"p8","review":"Lorem30"}}
My problem is adding multiple models to ModalView;
I tried a lot of example and questions in blogs&forums couldnt find any solve.
I tried a lot of things ($.extend, to set model and model vs..)
to change ModalView and below codes are last position of them;
app.views.ModalView = Backbone.View.extend({
template: _.template($('#modal-bsbb').html()),
initialize: function() {
_.bind(this.render, this);
},
render: function () {
var itemDetails = new app.collections.ItemDetails(); // this is new line
var model2 = itemDetails.fetch(); // this is new line
$('#myModalPop').modal({backdrop: 'static',keyboard: true});
$('#myModalPop').html(this.template({
'model1':this.model.toJSON(),
'model2':model2.model // this is new line
}));
return this;
}
});
I want to add a second model to my underscore template. But cant!
Firstly when i run below codes on chrome developer console it gets an Object;
but couldnt convert as a new model or JSON.
var itemDetails = new app.collections.ItemDetails();
var model2 = itemDetails.fetch();
console.log(model2); // gets fetch data as an object
I am afraid I am confused about where the problem exactly is.
Sorry guys I am not a backbone expert and probably I am doing something wrong though I searched a lot about it on the forum. I read about it again and again but I could not solve the problem. Could you please help me. Thank you in advance.
SOLVE:
After searchs and by the help of below reply.
I solved my problem.
app.views.ModalView = Backbone.View.extend({
template: _.template($('#modal-bsbb').html()),
initialize: function() {
_.bind(this.render, this);
},
render: function () {
var _thisView = this;
var itemsDetails = new app.collections.ItemsDetails();
itemsDetails.fetch({
success:function(data){
$('#myModalPop').modal({backdrop: 'static',keyboard: true})
.html(_thisView.template({
'model1':_thisView.model.toJSON(),
'model2':data.at(0).toJSON()
}));
}});
}
});
Every request to server using backbone is async, it means that you will not have the returned data immediately after the request, maybe the server still processing the data.
To solve this problem you have 2 ways.
First Way: Callbacks
Inside your Model/Collection
GetSomeData:->
#fetch(
success:=(data)>
console.log data // the returned data from server will be avaiable.
)
Second way: Listen for an trigger.
This one it's more elegant using backbone because you don't write callbacks.
Inside Model
GetSomeData:->
#fecth()
Inside View
initialize:->
#model = new KindOfModel()
#model.on "sync", #render, #
backbone automatically will trigger some events for you, take a read here.
http://backbonejs.org/#Events
As you're already doing, you'll need to listen to some trigger on the collection too
var itemDetails = new app.collections.ItemDetails(); // this is new line
var model2 = itemDetails.fetch(); // here is the problem

Triggering event on an element in memory when testing Backbone Views

I am writing some integration tests for my Backbone views/models/collections. When I call render on my View, it simply renders a template to it's own el property, hence the html is simply stored in memory rather than on the page. Below is a simple model, and a view with a click event bound to a DOM element:
var model = Backbone.Model.extend({
urlRoot: '/api/model'
});
var view = Backbone.View.extend({
events: {
'click #remove': 'remove'
}
render: function () {
var html = _.template(this.template, this.model.toJSON());
this.$el.html(html);
},
remove: function () {
this.model.destroy();
}
});
I am using Jasmine to write my tests. In the test below all I want to do is spy on the remove function to see if it is called when the click event is fired for the element #remove which is present in the template I pass to the view.
// template
<script id="tmpl">
<input type="button" value="remove" id="remove"/>
</script>
// test
describe('view', function () {
var view;
beforeEach(function () {
view = new view({
template: $('#tmpl').html(),
model: new model()
});
});
it('should call remove when #remove click event fired', function () {
view.$('#remove').click();
var ajax = mostRecentAjaxRequest();
expect(ajax.url).toBe('/api/model');
expect(ajax.method).toBe('DELETE');
});
});
However, as the #remove element is in memory, and it hasn't actually been added to the DOM, I'm not sure how you would simulate the click event. In fact I'm not even sure if it's possible?
It may seem a bit strange to want to do this in a test, but with my tests I am trying to test behaviour rather than implementation, and this way I don't care what is happening in between - I just want to test that if the user clicks #remove a DELETE request is sent back to the server.
Looks to me like you forgot to call render() on the view before click()ing the button. And the model needs to have an id or backbone won't actually try to make a delete call to the server. I've tested plenty of views just like that before with no problems.
I just ran a similar test against jasmine 2.0 and jasmine-ajax 2.0.
live code:
var MyModel = Backbone.Model.extend({
urlRoot: '/api/model'
});
var MyView = Backbone.View.extend({
events: {
'click #remove': 'remove'
},
initialize: function(options) {
this.template = options.template;
},
render: function () {
var html = _.template(this.template, this.model.toJSON());
this.$el.html(html);
},
remove: function () {
this.model.destroy();
}
});
specs:
describe("testing", function() {
var view;
beforeEach(function() {
jasmine.Ajax.install();
view = new MyView({
template: '<input type="button" value="remove" id="remove"/>',
model: new MyModel({id: 123})
});
view.render();
});
it('should call remove when #remove click event fired', function () {
view.$('#remove').click();
var ajax = jasmine.Ajax.requests.mostRecent();
expect(ajax.url).toBe('/api/model/123');
expect(ajax.method).toBe('DELETE');
});
});

Backbone.js: How do you call a View's "method" from outside the View's scope (e.g: inside a model's validation handler)

Basically, I'm trying to do something like this:
Person = Backbone.Model.extend({
validate: { ... },
initialize: function(){
this.bind('error', ?......?); <== what do I put?
},
// I DON'T WANT TO CALL THIS ONE
handleError: function(){ }
});
ViewOne = Backbone.View.extend({
//I WANT TO CALL THIS ONE:
handleError: function(model, error){
//display inside segmented view using jQuery
};
});
I tried options.view.handleError but it doesn't work...
My main purpose: I want a specific View that created the model to handle the error, not have the model to globally handle it all. For example, I want View#1 to do an alert while I want View#2 to display in a div. I don't know if this is the right way of doing it. If not, I would be gladly accept your help.
Thank you.
UPDATE: here's my jsFiddle
http://jsfiddle.net/jancarlo000/87mAk/
Since Backbone 0.5.2 it is recommended to drop bindAll in favor of third argument to bind if you need to pass the context.
ViewOne = Backbone.View.extend({
initialize: function() {
this.model.on('error', this.handleError, this);
},
handleError: function(model, error) { /* ... */ }
});
...
var person = new Person();
var viewone = new ViewOne({model : person});
General note here is that Models should never know about their Views. Only Views should subscribe to Model events.
You have it backwards, the view should be binding to the model's events:
ViewOne = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'handleError');
this.model.bind('error', this.handleError);
},
handleError: function(model, error) { /* ... */ }
});

Categories