Backbone View won't render on first page load - javascript

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();
});

Related

Backbone: Render view that has been created before

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.

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

Old Backbone View Causing extra Event Triggers

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.

Is correct replace content in the same place without remove the previous content model using Backbone.js?

I have a web application developed with Backbone.js. In the application, there are some buttons that remove the content view, but not the content model when pushed. For example, If I push the same button multiple times, the content is replaced, but the model of that content isn't removed.
How can I remove it?
I know how to remove the content with other different button, but I don't know how to remove the content if the same button (or other button not destined to delete but to add) is pushed.
The example code:
HTML:
<button class="ShowCam"></button>
<button class="camClose"></button>
<button class="anotherButton"></button>
JS:
var camContent = Backbone.View.extend({
el: "body",
events: {
"click .ShowCam": "addContentCam",
"click .anotherButton": "otherAddContentFunction"
},
initialize: function() {
_.bindAll(this);
this.model = new ContentCollection();
this.model.on("add", this.contentAdded);
this.model.on("remove", this.removeContentCam);
},
addContentCam: function(event) {
this.model.add({ modelName: "IPcam"});
contentAdded: function(content) {
if (content.view == null) {
var templ_name = 'cam';
content.view = new ContentView({
model: content,
template: $.trim($("[data-template='"+ templ_name +"'] div").html() || "Template not found!")});
$("div.camBox").empty();
this.$el.find(".content").find("div.camBox").append(content.view.render().el);
}
},
removeContentCam: function(content) {
if (content.view != null) {
content.view.remove();
}
content.clear(); //Clear the properties of the model
}
});
var ContentView = Backbone.View.extend({
tagName: "div",
template: null,
events: {
"click .camClose": "removeView"
},
initialize: function() {
_.bindAll(this);
this.template = this.options.template;
},
render: function() {
this.$el.html(Mustache.render(this.template, this.model.toJSON()));
return this;
},
removeView: function() {
this.model.collection.remove(this.model); //Remove the model of the collection
}
});
Javascript uses a garbage collection system to do its memory management. What this means is that you can delete anything simply by removing all references to it (well, technically it doesn't actually get deleted until the garbage collector gets to it, but essentially it's deleted).
So, if you want to make sure that a model gets removed, you don't need to call any special methods, you just need to do delete someView.model on every view (or other place in your code) that references that model.
You can actually see all this in practice if you look at the remove method on Backbone.View. You'll find that all it really does (besides triggering events) is call an internal method _removeReference. And what does _removeReference do? This:
if (this == model.collection) {
delete model.collection;
}
// (there's also an event-related line here)
Now, all that being said, if you're making a new view to replace an old one, and they both have the same model ... well you likely shouldn't be making a new view in the first place. The more standard Backbone way of handling such situations is to just re-call render on the view (instead of making a new one).

Using Backbone.Router for a slideshow

I'm creating a slideshow using Backbone.js. My slideshow view is finished, each slide is a model and all the models are inside a collection. Now I want to apply a little hashbang magic to my slideshow :-)
This is my code structure
application.js
models/slideshow/slide.js
collections/slideshow/slides.js
views/slideshow.js
In application.js I create my router:
var App = {};
App.Modules = {
Views: {},
Models: {},
Collections: {}
};
App.slideshow = undefined; // Use this to maintain state between calls.
App.router = (function() {
var Router = Backbone.Router.extend({
routes: {
'slideshow/:id/:page': 'slideshow'
},
slideshow: function(id, page) {
// Whenever this route handler triggers, I want to either:
// 1) Instantiate the slideshow, or:
// 2) Change the page on an already instantiated slideshow
if (App.slideshow && App.slideshow.options.id === id) {
App.slideshow.goToPage(page);
} else {
App.slideshow = new App.Modules.Views.Slideshow({
id: id,
page: page
});
}
}
});
return new Router;
})();
// Using jQuery's document ready handler.
$(function() {
Backbone.history.start({
root: '/'
});
});
This works as I expect. My slideshow works as an overlay so no matter what page it's instantiated on, it will just show itself on top of the existing document.
My first question is how do I close the slideshow (App.slideshow.close()); when the user hits the browser back button or navigates to another hashbang, which doesn't follow the /slideshow/:id/:page syntax?
My last question has to do with the 'navigate' method in Routers. In my slideshow view, I make sure to update the hash fragment whenever the page changes. This is what I do in my view:
pageChange: function(page) {
App.router.navigate('slideshow/' + this.options.id + '/' + page, false);
}
This makes sure the fragment gets updated so that the user at any point can copy the URL and it will open on the same page. The problem is that my 'slideshow' method in my instantiated router triggers even though I pass false in the second 'navigate' parameter (triggerRoute). Why is this?
So, I think I've figured it out. Please let me know if there are cleaner ways to do this.
After reading http://msdn.microsoft.com/en-us/scriptjunkie/hh377172 I saw you can do this in Backbone.js:
var router = Backbone.Router.extend({
routes: {
'*other': 'defaultRoute'
},
defaultRoute: function() {
if (App.slideshow) App.slideshow.close();
}
};
This makes sure everything that doesn't match /slideshow/:id/:page will close the slideshow if it's been instantiated.
With regard to 'navigate' apparently it's because I did App.vent = _.extend({}, Backbone.events); Apparently, I have to do:
App.vent = {};
_.extend(App.vent, Backbone.events);

Categories