I have the shell view that contains the navbar and the map. To this view is rendered other views that use the map previous rendered. When I'll go to the view perfil the map is removed, but the navbar is maintained, so far so good. My problem is when turn back to the home, the map doesn't appear only appears the div that contains the map. Bellow show the example:
View Shell and view Home:
go to view Perfil:
turn back to home:
heres my code:
app.js
var ev = new Application();
ev.Router = Backbone.Router.extend({
routes: {
"": "home",
"evento/:id" : "evento",
"criarevento" : "criarevento",
"perfil" : "perfil"
},
home: function(){
setTimeout(function(){
$('#rightcolumn').html(new ev.views.Home(ev.shell.map).el);
}, 0);
},
... // other views
perfil: function(){
setTimeout(function(){
$('#home').html(new ev.views.Perfil(ev.shell.template).el);
}, 0);
}
});
$(document).on('ready', function() {
ev.user = new ev.models.Person(); // Holds the authenticated Facebook user
// Load HTML templates for the app
ev.templateLoader.load(['shell', 'home', 'search_keyword', 'evento', 'login', 'quemvai', 'criar_evento', 'home_criar_evento', 'perfil'], function () {
ev.shell = new ev.views.Shell({el: "#shell", model: ev.user});
ev.router = new ev.Router();
Backbone.history.start();
});
});
perfil.js
ev.views.Perfil = Backbone.View.extend({
initialize: function(temp, model){
var that = this;
that.template = _.template(ev.templateLoader.get('perfil'));
that.template2 = temp;
//console.log(this.view);
ev.router.on("route", function(route, params) {
that.$el.html(that.template2());
});
that.render();
},
render: function(map){
this.$el.html(this.template());
return this;
}
});
So far I created an event that when route changes, the shell template that I step to the view perfil is called. But it's not working. What I'm doing wrong?
EDITS:
I change my constructor in view perfil, so that when route changes only fire once and call the render function of ev.shell
ev.views.Perfil = Backbone.View.extend({
initialize: function(){
var that = this;
that.template = _.template(ev.templateLoader.get('perfil'));
ev.router.once("route", function(route, params) {
ev.shell.render();
});
that.render();
},
render: function(map){
this.$el.html(this.template());
return this;
}
});
It looks like you have a document ready even that loads the shell including the map. When you go to the profile page you replace the contents of the #home element. Then when you go back to home you replace the contents of the #rightcolumn element. You never re-render the map.
I think you need to put the map rendering code into the home function of the router as well.
As a side note I noticed you are using setTimeout function. If you are using this so that something renders because it's waiting on something else to load then you should probably get rid of it and listen to an event.
Related
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.
I have created an extensive app using Backbone. So far, everything works very well. However, when I refresh/reload a page on a given hash (e.g. myapp/#dashboard), the view is rendered twice and all events are bound twice. If I go to another section and come back. everything is working normally.
I use a subrouter that looks like this:
var DashboardRouter = Backbone.SubRoute.extend({
routes : {
/* matches http://yourserver.org/books */
"" : "show",
},
authorized : function() {
// CODE TO RETRIEVE CURRENT USER ID ...
return (lg);
},
show : function() {
var usr = this.authorized();
if (this.dashboardView) {
this.dashboardView.undelegateEvents();
}
switch(usr) {
case 2:
this.dashboardView = new StudentDashboard();
break;
case 3:
this.dashboardView = new CounsellorDashboard();
break;
case 4:
this.dashboardView = new AdminDashboard();
break;
default:
location.replace('#signout');
}
},
});
I have checked within the console, and the events here are called only once. The student dashboard looks like this (extract)
DashboardView = Backbone.View.extend({
el : "#maincontent",
template : tpl,
model : new Student(),
events : {
"click #edit-dashboard" : "editDashboard",
"click #add-grade" : "addGrade",
"click #add-test" : "addTest",
"click #add-eca" : "addECA"
},
initialize : function() {
// BIND EVENTS
_.bindAll(this, 'render', 'editDashboard', 'renderGrades', 'renderTests', 'renderECAs', 'renderPreferences');
this.model.on("change", this.render);
this.model.id = null;
this.model.fetch({
success : this.render
});
},
render : function() {
$(this.el).html(this.template(this.model.toJSON()));
// set location variables after main template is loaded on DOM
...
if (!this.gradeList) { this.renderGrades(); };
if (!this.testList) { this.renderTests(); };
if (!this.ecaList) { this.renderECAs(); };
if (!this.preferencesView) { this.renderPreferences(); };
this.delegateEvents();
return this;
},
From the console logs I know that all the subviews are rendered normally only once, but twice when I refresh the page, and I have no idea why.
You need to make sure all events on your view are undeligated before re-rendering it.
Add following function inside your views.
cleanup: function() {
this.undelegateEvents();
$(this.el).empty();
}
Now, in your router before rendering the view, do the cleanup if the view already exists.
if (this.myView) { this.myView.cleanup() };
this.myView = new views.myView();
OK, I've done some reading on this and I'm pretty sure I know what the problem relates to I Just don't know the best way to fix it. I've got the standard backbone router that sets me up with an item details view, then when I click on a button called "start" it creates a new view which takes me to a sort of a game that people can play with some buttons on the bottom that have "click" events attached. This second view is not called through the router but directly from the first view.
The problem is the second time someones goes back to the homescreen and does it again, this time there are two events attached to each button. The third time there are three events. Obviously the original views are still listening to these buttons. I've read about this and calling the Remove() method but is this what I need to do? If so where do I call remove? Relevant Code below:
1ST VIEW
window.GameDrillView = Backbone.View.extend({
initialize: function () {
this.render();
},
render: function () {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
events: {
"click .start" : "startGameDrill",
},
startGameDrill: function () {
var start = $('#start').val();.
var stop = $('#stop').val();.
var StartView = new GameDrillStartView({model: this.model, el: $('#content')[0], start: start, stop:stop});
}
});
START VIEW
window.GameDrillStartView = Backbone.View.extend({
// declare variables
initialize: function () {
this.render();
},
events: {
"click .nextstage" : "nextstage", // 2ND TIME THROUGH GETS CALLED TWICE
},
nextstage: function () {
// Do some stuff //
this.render(); //Re-render
},
render: function () {
// Do some variables stuff
this.$el.html(this.template(jQuery.extend(this.model.toJSON(), extended_options)));..
return this;
}
});
When changing view you need to call undelegateEvents() method from the Backbone.View. It disable listening all the elements events mentioned in events { } block. Also if you need to destroy old view you can call remove() method of the view which will call undelegateEvents() internally.
update (example from official site)
var Workspace = Backbone.Router.extend({
routes: {
"help": "help", // #help
"search/:query": "search", // #search/kiwis
"search/:query/p:page": "search" // #search/kiwis/p7
},
help: function() {
if (this.currentView)
this.currentView.undelegateEvents();
this.currentView = new HelpView();
},
search: function(query, page) {
if (this.currentView)
this.currentView.undelegateEvents();
this.currentView = new SearchView();
}
});
An option is to create only one instance of the view:
if(_.isUndefined(this.StartView))
this.StartView = new GameDrillStartView({model: this.model, el: $('#content')[0], start: start, stop:stop});
else
this.StartView.render();
In the render method of GameDrillStartView add the empty method
this.$el.html(this.template(jQuery.extend(this.model.toJSON(), extended_options)))
In this way you won't add more event listeners but you'll update the page everytime the user presses the button.
You can manage the life cycle of StartView in GameDrillView since it seems like a better place to do so.
Got same trouble. Messy solution:
var current_view = false;
var prev_view = false;
var AppRouter = Backbone.Router.extend({
routes: {
"events/:id": "viewEvent",
}
});
var app_router = new AppRouter;
app_router.on('route:viewEvent', function (event_id) {
var _event = new Event({id:event_id});
current_view = new EventView({
model: _event,
});
});
//Will be called after route:viewEvent
app_router.on('route', function () {
if(prev_view) {
prev_view.undelegateEvents();
}
prev_view = current_view;
});
Not sure, how to make it without having current_view and prev_view out of router scope.
I have this strange issue where a view doesn't show up when I go to the page. However, if I refresh the page, it'll appear.
In my router, I tried to render 2 views like so:
tags: function(tags) {
self = this;
self.multipleTags = tags.split('/');
self.tagsArray = $.grep(self.multipleTags, function(item,index) {
return (item != '');
});
var browseHeader = new BrowseHeader;
var content = new tagsView({query:self.tagsArray});
},
I'm having trouble with my BrowseHeader though but the tagsView works fine. I did try removing my tagsView to see if maybe they were conflicting. However, even with a single view rendering, the header still wouldn't show up until I refresh the page.
Here is what I'm doing in my BrowseHeader view:
var browseHeader = Backbone.View.extend({
initialize: function() {
this.render();
},
template: function() {
dust.render('dust/browseHeader','', function(error, output) {
$('#wrapper').append(output);
});
},
render: function() {
this.template();
},
el: '#wrapper',
events: {
'click .academy_filter' : "click_filter"
},
click_filter: function(event) {
target = event.target;
$('.academy_filter').removeClass('active');
$(target).addClass('active');
EventBus.trigger('header:click_filter', target);
}
});
When I console.log the output, it does display the html for the output despite it not being shown on the page. So I know my dust template is working. When I simplify my BrowseHeader render function to just $('#wrapper').append("this"); I still experience the same issue.
Any ideas?
Update: Apparently it has something to do with browser and pushState because when I changed my router to the following, it worked fine.
Backbone.history.start({pushState: true});
As far as I can tell, there's nothing wrong with your view. This is most likely a timing issue. Your view is probably being initialized (and therefore rendered) before #wrapper exists in the DOM. My guess is that if you try the following, the output will be 0:
dust.render('dust/browseHeader','', function(error, output) {
console.log($('#wrapper').length);
$('#wrapper').append(output);
});
Make sure the view is being created after the DOM has finished loading, like so:
$(document).ready(function() {
var header = new browseHeader();
});
I've looked around at the various tutorials and documentation, and I'm still having some trouble getting the backbone router to work. I'm running the code in my Sites folder on OS X (the url is http://localhost/~plebrun). Neither http://localhost/~plebrun/#foo nor http://localhost/~plebrun/#type/books works. Thoughts?
(Note: the data_* variables contain json data)
/****************************/
/********** MODELS **********/
/****************************/
var Category = Backbone.Model.extend();
var Phrase = Backbone.Model.extend();
/****************************/
/******** COLLECTIONS *******/
/****************************/
var Type = Backbone.Collection.extend({
model: Category
});
/****************************/
/********** VIEWS ***********/
/****************************/
var TypeView = Backbone.View.extend({ /* a Type is a list of Categories */
el: $('#categories'),
initialize: function() {
_.bindAll(this, 'render');
this.render();
},
render: function() {
var ul_list = "";
_(this.collection.models).each(function(category) {
ul_list += '<li><h1>' + category.get('en') + '</h1><p>' + category.get('es') + '</p></li>';
});
$(this.el).append(ul_list);
}
});
/****************************/
/********* ROUTER ***********/
/****************************/
var AppRouter = Backbone.Router.extend({
routes: {
"/type/:src": "showType",
"/foo": "foo"
},
locate_data: {
"books": data_books,
"conversations": data_conversations,
"phrases": data_phrases
},
initialize: function() {
_.bindAll(this, 'showType');
},
foo: function() {
alert("foo")
},
showType: function(src) {
console.log(src);
var types = new Type(this.locate_data[src]);
new TypeView({ collection: types });
}
});
/****************************/
/********** INIT ************/
/****************************/
var foo = new AppRouter();
Backbone.history.start({pushState: true, root: "/~plebrun/"});
You don't need the first slash at the beginning of the route. So the routes should be
routes: {
"type/:src": "showType",
"foo": "foo"
}
What doesn't work? Does the app initialize? Does the URL change when you click a link?
One thing you need to be aware of when using pushState, is that when you click on a working link, the page will be requested from the server. In other words, you need to hijack links so that backbone's router processes them instead of calling the server. To achieve this, you can use a function like this one:
MyApp.Support = {
// navigate to CRUD actions when the links are clicked
navigateLink: function (e) {
var target = $(e.currentTarget);
if( ! target.attr('data-method')){ // don't change delete links
e.preventDefault();
AppRouter.navigate(target.attr('href'), { trigger: true });
}
}
}
Then, you can have something like this in your view:
events: {
'click a[data-method!="destroy"]': "navigateLink"
}
In other words, when you click on a link, Backbone's router will navigate to it, instead of fetching the page from the server.
Turn out routing wasn't the problem... I had neglected to wrap the code in $('document').ready(). I didn't think I needed to since the scrip tag reference to the js file was included at the bottom of the web page.