I am thinking about developing a backbone application, but I am not sure if I am thinking about it the right way.
This will be my first backbone application. Basically, I want to develop a simple app that will have a google map as its "main" view.
The idea is to fetch location information from an API into a model, let's say Marker, and these markers will be shown on the map.
I am not sure how to connect the markers to the map view:
Should the map view have an addMarker() function?
Or should the marker's render method add the marker to the map?
How about creating a Collection of markers and passing it to the initialize function in your view?
This would allow you to bind the change event to update the map whenever the marker collection changes. Then, whenever you add a marker to the collection, the map view will catch the change in the collection and be able to add the marker and adapt itself as needed to accomodate.
For instance, create a view on these lines:
// in Backbone.View
initialize: function (opts) {
_.bindAll(this);
this.markers = opts.markers;
this.markers.on('change', this.render);
},
render: function () {
// create or update the map
}
When you want to initialize the view, you could now use something like this:
var markers = new MyApp.MarkerCollection(),
view = new MyApp.MapView({ markers: markers });
Related
Question
What is a good way to run some code only after a Backbone or Marionette Collection item has displayed onto the page?
More info
The reason I need to do this is because I am using google charts. The charts do not render quite right unless they are actually visible on the page when they are being rendered (ie: not hidden explanation). It appears that when the collection item's onRender function is called, the view is not quite rendered completely on the page.
The "fix" I'm currently is to use setTimeout to render the charge a half second after onRender is called. This works, but is an ugly hack:
Backbone.Marionette.ItemView.extend({
...
onRender: function(){
var chart = ...;
var chart_data = ...;
var options = ...;
setTimeout(function(){
chart.draw(chart_data, options)
},
500
);
}
...
})
I've read in other places that onShow works, but if I understand correctly, this method only is called when working Marionette regions.
Unfortunately, Marionette's onRender method doesn't mean that the object is actually added to the DOM.
If you were using Marionette's regions, then onShow would probably do the job because of the timing. But as it can be detached from the DOM there is no guarantee that views are actually displayed.
The actual clean solution is to use the onAttach event added to Marionette v2.3. Hope this will solve your problem — but I'm confident with that =)
otherwise, you can use a plugin like jquery.inview to get a inview event once a given element becomes visible:
Backbone.Marionette.ItemView.extend({
events:{
'inview #chartContainer': 'renderChart'
},
renderChart: function(){
var chart = ...;
var chart_data = ...;
var options = ...;
chart.draw(chart_data, options)
},
...
})
Hi I'm learning backbone and I am having trouble with binding events to views. My problem is that I have a view constructor that when called, binds all views to a button press event that is only part of one view. I would like the button press event to be bound to only the 1 view that contains the button.
http://jsbin.com/tunazatu/6/edit?js,console,output
click on all of the view buttons
then click back to view 1
click the red button (all view's models console.log their names)
So I've looked at the code from this post mutliple event firing which shows that you can have multiple views that have the same el thru tagName but map events only to their html elements. This is also what is done in the localtodos example from Jérôme Gravel-Niquet
I have also tried not declaring el /tunazatu/7/edit?js,console,output but then it seems like no event gets bound.
var AppView = Backbone.View.extend({
tagName:"div", //tagName defined
getName:function(){
console.log(this.model.get('name'));
},
initialize:function(options){
this.listenTo(this.model, 'change', this.render);
var temp_mapper = {appView1:'#route1',appView2:'#route2',appView3:'#route3'};
var m_name = this.model.get('name');
this.template = _.template($(temp_mapper[m_name]).html()); //choose the correct template
},
render:function(){
var temp = this.template(this.model.toJSON()); //populate the template with model data
var newElement = this.$el.html(temp); //put it in the view's tagName
$('#content').html(newElement);
},
events:{
"click button":"log"
},
log:function(){
this.getName();
}
});
Your problem is that your AppView really looks like this:
var AppView = Backbone.View.extend({
el: "#content",
//...
Every time you create a new AppView, you bind another event delegator to #content but you never remove those delegations. If you create three AppViews, you end up with three views listening to click button inside #content.
I would recommend two things:
Avoid trying to re-use views, create and destroy them (via View#remove) as needed. Views should be lightweight enough that putting them together and tearing them down should be cheap.
Don't bind multiple views to the same el. Instead, let each view create its own el and then let the caller put that el inside some container.
If you do both of those things then your problem will go away. Your AppView would look more like this:
var AppView = Backbone.View.extend({
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this; // Common practise, you'll see why shortly.
},
// As you already have things...
});
Then your router methods would look more like this:
view1: function() {
if(this.appView)
this.appView.remove();
this.appView = this.createView('appView1');
$('#content').html(this.appView.render().el);
// that `return this` is handy ----------^^
},
If you must stick with your current approach then you'll have to call undelegateEvents on the current AppView before you render another one and delegateEvents on the new AppView after you render it.
But really, don't be afraid to destroy views that you don't need right at this moment: destroy any view that you don't need on the page right now and create new instances when you need them. There are cases where you don't want to destroy your views but you can usually avoid it.
Is there any way I can find out the rendered view in backbone? I have 4 views
Login, Contact, Home and About
I would like to find out which view currently is rendered.
Assuming you're rendering all the views into the same element (otherwise you could know what the view is from the element id), you might add a property like 'class' to the view when you create it. Then that property can be accessed through view.options.
For example:
var LoginView = Backbone.View.extend();
var loginView = new LoginView({ model: new Backbone.Model, el: 'body', class: 'login' });
loginView.render = function() { var content = 'login'; this.$el.html(content) };
loginView.render();
console.log(loginView.options.class) // 'login'
Obviously this is an oversimplified example but the general idea should work. More details or a code sample would help if you need a more specific answer.
If you also wanted to make sure the view is actually rendered, just write a method that checks if content of the view is what you expect it to be
I have the following problem with backbone and I'd like to know what strategy is the more appropriated
I have a select control, implemented as a Backbone view, that initially loads with a single option saying "loading options". So I load an array with only one element and I render the view.
The options will be loaded from a collection, so I fire a fetch collection.
Then I initialize a component that is in charge of displaying in line errors for every field. So I save a reference of the dom element of the combo.
When the fetch operation is finally ready, I rerender the control with all the options loaded from the collection.
To render the view I user something like this:
render: function() {
this.$el.html(this.template(this.model.attributes));
return this;
}
pretty standard backbone stuff
the problem is that after rendering the view for the second time the reference of the dom is no longer valid,
perhaps this case is a bit strange, but I can think of lots of cases in which I have to re-render a view without losing their doms references (a combo that depends on another combo, for example)
So I wonder what is the best approach to re-render a view without losing all the references to the dom elements inside the view...
The purpose of Backbone.View is to encapsulate the access to a certain DOM subtree to a single, well-defined class. It's a poor Backbone practice to pass around references to DOM elements, those should be considered internal implementation details of the view.
Instead you should have your views communicate directly, or indirectly via a mediator.
Direct communication might look something like:
var ViewA = Backbone.View.extend({
getSelectedValue: function() {
return this.$(".combo").val()
}
});
var ViewB = Backbone.View.extend({
initialize: function(options) {
this.viewA = options.viewA;
},
doSomething: function() {
var val = this.viewA.getSelectedValue();
}
});
var a = new ViewA();
var b = new ViewB({viewA:a});
And indirect, using the root Backbone object as a mediator:
var ViewA = Backbone.View.extend({
events: {
"change .combo" : "selectedValueChanged"
},
selectedValueChanged: function() {
//publish
Backbone.trigger('ViewA:changed', this.$('.combo').val());
}
});
var ViewB = Backbone.View.extend({
initialize: function(options) {
//subscribe
this.listenTo(Backbone, 'ViewA:changed', this.doSomething);
},
doSomething: function(val) {
//handle
}
});
var a = new ViewA();
var b = new ViewB();
The above is very generic, of course, but the point I'm trying to illustrate here is that you shouldn't have to worry whether the DOM elements are swapped, because no other view should be aware of the element's existence. If you define interfaces between views (either via method calls or mediated message passing), your application will be more maintainable and less brittle.
I am trying to use iScroll4 inside a backbone.js application. I have several dynamically loaded lists, and I want to initialize iScroll after the appropriate view has loaded.
I'm trying to call 'new iScroll' when the list view finishes loading, but cannot for the life of me figure out how to do this.
Has anyone gotten these two to work together? Is there an example out there of a backbone view initializing a scroller once its element has loaded?
you are correct, you have to load the view first,
or defenately refresh iscroll afterwards
in our applications, we usually use the render method to render the view
and have a postRender method that handles initialization of these extra plugins like iscroll
of course you need some manual work to get it done but this is the gist of it:
var myView = Backbone.View.extend({
// more functions go here, like initialize and stuff... but I left them out because only render & postRender are important for this topic
// lets say we have a render method like this:
render: function() {
var data = this.collection.toJSON();
this.$el.html(Handlebars.templates['spotlightCarousel.tmpl'](data));
return this;
},
// we added the postRender ourself:
postRender: function() {
var noOfSlides = this.collection.size();
$('#carouselscroller').width(noOfSlides * 320);
this.scroller = new IScroll('carouselwrapper', {
snap: true,
momentum: false,
hScrollbar: false
});
}
});
now the calling of these methods
we did this outside our view as we like some view manager to handle this
but it boils down to this
var col = new myCollection();
var view = new myView({ collection: col });
$('#wrapper').html(view.render().$el); // this chaining is only possible due to the render function returning the whole view again.
// here we always test if the view has a postRender function... if so, we call it
if (view.postRender) {
view.postRender();
}