I am new to Backbone.js. I have this project where there is a hierarchy of multiple views and sometimes the views need to communicate with each other.
After a little research on the Internet, I came across https://lostechies.com/derickbailey/2011/07/19/references-routing-and-the-event-aggregator-coordinating-views-in-backbone-js/ and tried to use the event aggregator.
However, it didn't work. My guess is that the 'vent' is not the same instance across all the views. So, if there is any way to definine it as a static variable, I can probably make it work. So, is there a way to define static variables in Backbone.js?
Yes, it is possible to add static variables to any Backbone objects using extend:
obj.extend( protoProps, staticProps );
e.g.:
var MyView = Backbone.View.extend( { events: ... }, {
myStaticVar: 'anything'
} );
...
var value = MyView.myStaticVar;
However, I am not sure you need this in this case.
Let's see. Not quite sure what was that example you showed, daga (but - Japanese version) if you want to make a static something in Bb.js(Backbone.js) u'll have to use some tricks.
For example you got a list of bb.views:
[Code #01]
01|var ViewX01 = Backbone.View.extend({
02| initialize: function(){
03| console.log('ViewX01');
04| this.addToList();
05| },
06| addToList : function(){
07| ViewStaticStyle.elEventos.push(this);
08| console.log('Push #1: ', ViewStaticStyle.elEventos);
09| }
10|});
11|var ViewX02 = Backbone.View.extend({
12| initialize: function(){
13| console.log('ViewX02');
14| this.addToList();
15| },
16| addToList : function(){
17| ViewStaticStyle.elEventos.push(this);
18| console.log('Push #2: ', ViewStaticStyle.elEventos);
19| }
20|});
21|var ViewX03 = Backbone.View.extend({
22| initialize: function(){
23| console.log('ViewX03');
24| this.addToList();
25| },
26| addToList : function(){
27| ViewStaticStyle.elEventos.push(this);
28| console.log('Push #3: ', ViewStaticStyle.elEventos);
29| }
30|});
31|var ViewX04 = Backbone.View.extend({
32| initialize: function(){
33| console.log('ViewX04');
34| this.addToList();
35| },
36| addToList : function(){
37| ViewStaticStyle.elEventos.push(this);
38| console.log('Push #4: ', ViewStaticStyle.elEventos);
39| }
40|});
A view/model that will imitate a static variable upon initialization:
[Code #02]
01|var ViewStaticStyle = Backbone.View.extend({
02| initialize: function(){
03| console.log('Init static variable');
04| ViewStaticStyle.elEventos = []; // <- This one is a static array ;)
05| }
06|});
And a master view that inits them all except for "ViewStaticStyle":
[Code #03]
01|var MasterView = Backbone.View.extend({
02| initialize: function(){
03| // Instantiate each and store inside the MasterView
04| this.view1 = new ViewX01();
05| this.view2 = new ViewX02();
06| this.view3 = new ViewX03();
07| this.view4 = new ViewX04();
08| }
09|});
Then upon $(document).ready():
[Code #04]
01|// Instantiate the static array
02|var Eventitoz = new ViewStaticStyle();
03|// Instantiate the MasterView, add it to the window in case you
04|// want to test the "static" thing further via browser console ;)
05|window.App = new MasterView();
So the [Code 02] Line [04] Is the "static" thing.
Hope it helped.
A source code would be provided upon request.
Related
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
I think this is just a JavaScript scope question.
I'm trying to add some Jasmine tests to a Backbone application, but I can't figure out how to access Backbone models from within my Jasmine setup.
This is my current application structure (main.js is my Backbone application):
index.html
js/
main.js
vendor/
backbone.js
jquery.min.js // etc
tests/
SpecRunner.html
spec/
testSpec.js
The content of main.js is like this, and it's all running OK from index.html:
$(function(){
var Todo = Backbone.Model.extend({
defaults: function() {
return {};
},
... etc
The files in SpecRunner.html look like this:
<!-- include source files here... -->
<script src="/js/vendor/jquery-1.10.2.min.js"></script>
<script src="/js/vendor/underscore.js"></script>
<script src="/js/vendor/backbone.js"></script>
<script src="/js/main.js"></script>
<!-- include spec files here... -->
<script type="text/javascript" src="spec/testSpec.js"></script>
I have written this test in testSpec.js, but it's failing with ReferenceError: Todo is not defined:
describe("Todo tests", function(){
var todo = new Todo("Get the milk", "Tuesday");
it("should be correctly defined", function(){
expect(todo).toBeDefined();
});
it("should have the correct title", function(){
expect(todo.title).toBe("Get the milk");
});
});
How can I get hold of the Todo scope? I've tried window.Todo but that doesn't help either.
As we can see at the Backbone reference.
constructor / initialize new Model([attributes], [options])
When creating an instance of a model, you can pass in the initial values of
the attributes, which will be set on the model.
The constructor needs to be initilized with a object literal(Key value pair), as follow:
new Book({
title: "One Thousand and One Nights",
author: "Scheherazade"
});
So you need to change your code to:
var todo = new Todo({
task: "Get the milk",
dayOfWeek: "Tuesday"
});
UPDATE:
Here you declared the Todo inside the jquery function scope and you're trying to access from outside.
$(function(){
var Todo = Backbone.Model.extend({
defaults: function() {
return {};
},
etc...
}
}
You have two options, declare Todo as global, not recommend, but largely used at backbone samples:
$(function(){
window.Todo = Backbone.Model.extend({
defaults: function() {
return {};
},
etc...
}
});
You could also try to call the jquery function after declaration:
$(function(){
var Todo = Backbone.Model.extend({
defaults: function() {
return {};
},
etc...
}
})();
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' });
I have a view which doesn't seem to want to render as the model's change event is not firing.
here's my model:
var LanguagePanelModel = Backbone.Model.extend({
name: "langpanel",
url: "/setlocale",
initialize: function(){
console.log("langselect initing")
}
})
here's my view:
var LanguagePanelView = Backbone.View.extend({
tagName: "div",
className: "langselect",
render: function(){
this.el.innerHTML = this.model.get("content");
console.log("render",this.model.get(0))
return this;
},
initialize : function(options) {
console.log("initializing",this.model)
_.bindAll(this, "render");
this.model.bind('change', this.render);
this.model.fetch(this.model.url);
}
});
here's how I instantiate them:
if(some stuff here)
{
lsm = new LanguagePanelModel();
lsv = new LanguagePanelView({model:lsm});
}
I get logs for the init but not for the render of the view?
Any ideas?
I guess it's about setting the attributes of the model - name is not a standard attribute and the way you've defined it, it seems to be accessible directly by using model.name and backbone doesn't allow that AFAIK. Here are the changes that work :) You can see the associated fiddle with it too :)
$(document).ready(function(){
var LanguagePanelModel = Backbone.Model.extend({
//adding custom attributes to defaults (with default values)
defaults: {
name: "langpanel",
content: "Some test content" //just 'cause there wasn't anything to fetch from the server
},
url: "/setlocale",
initialize: function(){
console.log("langselect initing"); //does get logged
}
});
var LanguagePanelView = Backbone.View.extend({
el: $('#somediv'), //added here directly so that content can be seen in the actual div
initialize : function(options) {
console.log("initializing",this.model);
_.bindAll(this, "render");
this.render(); //calling directly since 'change' won't be triggered
this.model.bind('change', this.render);
//this.model.fetch(this.model.url);
},
render: function(){
var c = this.model.get("content");
alert(c);
$(this.el).html(c); //for UI visibility
console.log("render",this.model.get(0)); //does get logged :)
return this;
}
});
var lpm = new LanguagePanelModel();
var lpv = new LanguagePanelView({model:lpm});
}); //end ready
UPDATE:
You don't need to manually trigger the change event - think of it as bad practice. Here's what the backbone documentation says (note: fetch also triggers change!)
Fetch
model.fetch([options])
Resets the model's state from the server.
Useful if the model has never been populated with data, or if you'd
like to ensure that you have the latest server state. A "change" event
will be triggered if the server's state differs from the current
attributes. Accepts success and error callbacks in the options hash,
which are passed (model, response) as arguments.
So, if the value fetched from the server is different from the defaults the change event will be fired so you needn't do it yourself. If you really wish to have such an event then you can use the trigger approach but custom name it since it's specific to your application. You are basically trying to overload the event so to speak. Totally fine, but just a thought.
Change
model.change()
Manually trigger the "change" event. If you've been
passing {silent: true} to the set function in order to aggregate rapid
changes to a model, you'll want to call model.change() when you're all
finished.
The change event is to be manually triggered only if you've been suppressing the event by passing silent:true as an argument to the set method of the model.
You may also want to look at 'has changed' and other events from the backbone doc.
EDIT Forgot to add the updated fiddle for the above example - you can see that the alert box pops up twice when the model is changed by explicitly calling set - the same would happen on fetching too. And hence the comment on the fact that you "may not" need to trigger 'change' manually as you are doing :)
The issue was resolved my adding
var LanguagePanelModel = Backbone.Model.extend({
//adding custom attributes to defaults (with default values)
defaults: {
name: "langpanel",
content: "no content",
rawdata: "no data"
},
events:{
//"refresh" : "parse"
},
url: "/setlocale",
initialize: function(){
log("langselect initing");
//this.fetch()
},
parse: function(response) {
this.rawdata = response;
// ... do some stuff
this.trigger('change',this) //<-- this is what was necessary
}
})
You don't need attributes to be predefined unlike PhD suggested. You need to pass the context to 'bind' - this.model.bind('change', this.render, this);
See working fiddle at http://jsfiddle.net/7LzTt/ or code below:
$(document).ready(function(){
var LanguagePanelModel = Backbone.Model.extend({
url: "/setlocale",
initialize: function(){
console.log("langselect initing");
}
});
var LanguagePanelView = Backbone.View.extend({
el: $('#somediv'),
initialize : function(options) {
console.log("initializing",this.model);
// _.bindAll(this, "render");
//this.render();
this.model.bind('change', this.render, this);
//this.model.fetch(this.model.url);
},
render: function(){
var c = this.model.get("content");
alert(c);
$(this.el).html(c);
console.log("render",this.model.get(0));
return this;
}
});
var lpm = new LanguagePanelModel();
var lpv = new LanguagePanelView({model:lpm});
lpm.set({content:"hello"});
}); //end ready
Something bizarre is going on. I have code which creates a Backbone.js View, except it never gets rendered, however if i run it in console it works:
function createBookingsView($container, roomID){
var BookingsView = Backbone.View.extend({
el : $container,
initialize : function(){
console.log("derp");
_.bindAll(this, "addOne", "addAll", "render");
this.bookings = new Bookings();
this.bookings.bind("change", this.addOne);
this.bookings.bind("refresh", this.addAll);
this.bookings.bind("all", this.render);
this.bookings.fetch({data : {"room" : roomID}});
console.log(this.bookings.get(1));
},
addAll : function(){
this.bookings.each(this.addOne);
},
addOne : function(booking){
var view = new BookingView({model:booking});
$(this.el).append(view.render().el);
},
render : function(){
$(this.el).html("");
this.addAll();
}
});
return new BookingsView;
};
and heres how it's called:
window.LEFT_BOOKINGS = createBookingsView($("#timetable-a .bookingContainer"), room.id);
when i manually run the above line in console, it works brilliantly. but it doesnt load in my script.
I just had some issues with Asynchronous functions not returning before I needed them.