Backbone partial view not rendering the latest model - javascript

I am relatively new to Backbone and I am running into this problem.
I am using Backbone with DustJS
My template looks something like this - index.dust
<div id="parentView">
<div class="section">
{>"app/inc/responseMessage" /}
<div class="userDetails">
{! A button to get user details !}
</div>
</div>
</div>
This is my partial below - responseMessage.dust
<div id="responseMessage">
{#eq key="{data.success}" value="true"}
<div class="alert alert-success success" role="alert">success</div>
{/eq}
</div>
My JS looks like this
initialize: function() {
this.responseMessageView = this.responseMessageView ||
new ResponseMessageView({
model: new Backbone.Model()
}); // View is for the partial
this.model = this.model || new Backbone.Model(); //View for the whole page
},
Below function is called when an event occurs and it does a POST and returns successfully.
primaryViewEventTrigger: function(event){
//Button click on `index.dust` triggers this event and does a POST event to the backend
this.listenToOnce(this.model, 'sync', this.primaryViewSuccess);//this will render the whole view.
this.listenToOnce(this.model, 'error', this.primaryViewError);
this.model.save({data: {x:'123'}});
}
responseViewEventTrigger: function(event){
//Button click on `responseMessage.dust` triggers this event and does a POST event to the backend
this.listenToOnce(this.responseMessageView.model, 'sync', this.responseViewSuccess);//it should only render the partial view - responseMessage.dust
this.listenToOnce(this.responseMessageView.model, 'error', this.primaryViewError);
this.responseMessageView.model.save({data: {x:'123'}});
}
primaryViewSuccess: function(model,response){
this.model.set('data', response.data);
this.render();
}
responseViewSuccess: function(model,response){
this.responseMessageView.model.set('data', response.data);
console.log(this.responseMessageView.model);
this.responseMessageView.render(); // Does not work in some cases
}
My implementations of the callback function
exports.sendEmail = function sendEmail(req, res){
req.model.data.success = true;
responseRender.handleResponse(null, req, res);
};
this.model belongs to the model of the whole page. Whereas this.responseMessageView.model is the model for the partial.
Question: This works perfectly fine in most of the cases. There is one case where it does not render the partial with the latest model values. When I click on the button on index.dust and primaryViewSuccess is executed. After which I click on another button and trigger responseViewEventTrigger. It does the POST successfully and it comes to responseViewSuccess and stores it in the model too. But it does not show it on the frontend. data.success is still not true whereas console.log(this.responseMessageView.model) show that attributes->data->success = true
But the same behavior when I refresh the page it all works perfect. Its just that when primaryViewSuccess is called and then responseViewSuccess its not taking the latest model changes. In other words model is being updated but the DOM remains the same.
What am I missing here? Thanks for your time!

You're hitting the classic Backbone render-with-subviews gotcha: When you render the index view, you are replacing all of the DOM nodes. That includes the DOM node that your responseMessageView instance is tied to. If you inspect the responseMessageView.el you'll actually see that it has been updated with the new model data, however the element isn't attached to the index's DOM tree anymore.
var Parent = Backbone.View.extend({
template: _.template(''),
render: function() {
this.$el.html(this.template());
},
initialize: function() {
this.render();
this.child = new Child();
this.$el.append(child.el);
}
});
Here when you manually call render, child.el will no longer be in the
parent.el (you can check using the inspector).
The simplest fix here is to call child.setElement after the parent renders with the newly rendered div#responseMessage element. The better fix is to detach the responseMessageView's element beforehand, and reattach after:
var Parent = Backbone.View.extend({
render: function() {
this.responseMessageView.$el.detach();
this.$el.html(this.template(this.model.toJSON()));
this.$el.find('#responseMessage').replaceWith(this.responseMessageView.el);
}
});

Try to use success callback and let me know I think your problem may come from here:
this.model.save( {att1 : "value"}, {success :handler1, error: handler2});
Also are you sure you want to use listenToOnce ? instead of listenTo ??

Try doing this.delegateEvents() after your render (http://backbonejs.org/#View-delegateEvents).

Related

Backbone Marionette ItemView return html

I want to render marionette ItemView and append result html() to my div.
My code:
var ProjectItemView = Backbone.Marionette.ItemView.extend({
template: "#ProjectItem",
tagName: 'div',
initialize: function () {
this.model.on('change', this.life, this);
this.model.on('change', this.render, this);
},
life: function () {
alert(JSON.stringify(this.model));
}
});
var tmp = new Project({project_id: 1});
tmp.fetch();
$('#container').append(new ProjectItemView({model: tmp}).$el);
alert in life: function shows model right. It means fetch works fine.
The question is - how to get html as result of view.
I also tried $('#container').append(new ProjectItemView({model: tmp}).render().el);
The problem was with the REST service that I use to populate collections/models. It returns array that contains one element - not plain object directly.
You have to react to render event from marionette.
...
onRender : function() {
$('#container').append(this.$el);
}
...
new ProjectItemView({model: tmp});
tmp.fetch();
If you want to get uncoupled, fire a distinct app event from within your view handler to the outside world. Radio might be worth considering if you're not already.
I think your problem is only the order of operations. Try this:
$('#container').append((new ProjectItemView({model: tmp})).render().el);
The way you had it before, you were invoking .render() on the constructor. With the extra parenthesis above, .render() is called on the instance.
pass element to view:
new ProjectItemView({model: tmp, el:'#container'}).render();

Create a view without a defined el property with events - Backbone

I've been trying to learn Backbone, and I'm developing an app now. But I have a problem with a view's events: App.views.ChannelView should have a click event, but it is not firing.
Here's the code:
http://pastebin.com/GgvVHvtj
Everything get rendered fine, but events won't fire. Setting the el property in the view will work, but I can't use it, and I've seen on Backbone's todo tutorial that it is possible.
How do I make events fire without a defined el property?
You must define the el element to be an existing element in your DOM. If you do not define it, fine, it will default to a div, but when you render the view, the html generated must be appended/prepended whatever, you get the point, to an existing DOM element.
Events are scoped to the view, so something's wrong with your scope. From the code you provided I can't reproduce the problem, so if you might, please provide a live example on jsfiddle/jsbin etc in order to fully understand the issue.
Demo ( in order to demonstrate the view render )
var App = {
collections: {},
models: {},
views: {},
};
App.models.Channel = Backbone.Model.extend({
defaults: {
name: '#jucaSaoBoizinhos'
}
});
App.views.ChannelView = Backbone.View.extend({
el:$('#PlaceHolder'),
events: {
"click .channel": "myhandler"
},
render: function() {
this.$el.html('<div class="channel"><button>' + this.model.get('name') + '</button></div>');
return this;
},
myhandler: function(e) {
alert(e);
console.log(this.model.get('name'));
},
});
var chView = new App.views.ChannelView({model: new App.models.Channel()});
//console.log(chView.render().el) //prints div#PlaceHolder
//without the el specified in the view it would print a div container
//but i would have to render the view into an existing DOM element
//like this
//$('#PlaceHolder').html(chView.render().el)
chView.render()
Can you try doing a
events: {
"all": "log"
}
log: function(e) {
console.log e;
}
That should log out every event that's getting fired. I find it super helpful when troubleshooting.
backbone view events can work without dom element specified. If you can't use any element at the view createion (initialization) moment, then you can use it's 'setElement' method, to attach your view to specified dom element. Here is description.
Be the way your view render method will not work also without specified 'el'.

backbone.js subview not shown on initial render

I have a view:
App.Views.Rebill = App.Views.baseView.extend({
view: 'requests._rebill_line',
tag: 'div',
className: 'row row-spaced',
render: function() {
var self = this;
App.get_view(self.view).done(function(data){
var view = Mustache.render(data, {
text: self.model.get('text'),
cost: self.model.get('cost')
});
self.$el.append(view);
var $selectVat = self.$el.find('select[name="vat"]');
var vatPicker = new App.Views.VatPicker({
model: self.model,
el: $selectVat
});
vatPicker.render();
});
return self;
}
});
this view is created in the parent:
addRebillLine: function(rebillModel){
var self = this;
var rebillView = new App.Views.Rebill({
model: rebillModel
});
self.$el.after(rebillView.render().$el);
},
I have linked this to a button, which creates an empty model, and calls the addRebillLine function. This works fine, the new Rebill view appears in the DOM when I click the button.
However, on rendering the parent view, I run through a json array, create a model with each line, and call the addRebillLine function with that model. This runs, but the Rebill views are not added to the DOM.
Further up, the parent view is itself a child view, and is attached to its parent like so:
this.$el.find('[data-role="repair-items"]').append(itemView.render().$el);
The parent and grandparent views are rendered synchronously, and the child view asynchronously (App.get_view() is basically a call to $.ajax, hence the .done())
The wierd thing is that I do the same thing with the App.Views.VatPicker view, in several other places, and that works just fine. The only difference is that I pass an element to attach to into the VatPicker view. But if I pass the parent $el element in, and run this.$parent.after(self.$el) in my done() callback, that doesn't work either.
When you call this line:
this.$el.find('[data-role="repair-items"]').append(itemView.render().$el);
it assumes your render code is synchronous (as it should be) , but you render code is not.
When render return the $el is not set yet, this is a problem in your design.
You should solve this design issue by making your templates available when the view needs them. Don't invent the wheel here, take a look at the TodoMVC backbone example.
If you want your templates to be loaded aysnc, use requirejs.

Calling another render method to render a subview in backbone

So I have two templates, one is the parent template, and the other is a template that has the subviews in it. My first parent view extends backbone's view and its render method looks like
render: function () {
this.$el.append(templates["template-parent-test"](this.model));
return this;
}
This parent view has a button on it. The button right now I use to populate my view. It basically does this:
populateView: function () {
// create some dummy test data to match the web service
_.each(myModel, function (theModel) {
var testView = new childView({ model: theModel });
this.$('div-in-parent-view').append(testView.render().$el);
});
}
This backbone view extends the Backbone.View and its render method looks like:
render: function () {
console.log("rendering child view");
this.$el.html(this.template({ data: this.model.toJSON() }));
return this;
}
So this works. But I don't want to populate the parent view on a button press. I want to populate it when I show it for the first time and have the button do what its actually supposed to do. I would think that I could just call this.populateView() from my render function in the parent view, but nothing actually gets rendered. But if I do this.populateView() in my button event, it gets rendered. Why is there a difference here? Thanks.
Try adding this as a third parameter to each. That sets the context. When you don't specify the context, this will be window. And since your html is not yet on the page during the parent view's render, it wont find this.$('#div-in-parent-view') in order to add the html to it.

Why is the click event triggered several times in my Backbone app?

I'm making a Backbone.js app and it includes an index view and several subviews based on id. All of the views have been bound with mousedown and mouseup events. But every time I go from a subview to the index view and then go to any of subviews again, the mousedown and mouseup events in the current subview will be triggered one more time, which means when I click the subview, there will be several consecutive mousedown events triggered followed by several consecutive mouseup events triggered.
After looking through my code, I finally found that it's the router that causes this problem. Part of my code is as follows:
routes: {
"": "index",
"category/:id": "hashcategory"
},
initialize: function(options){
this._categories = new CategoriesCollection();
this._index = new CategoriesView({collection: this._categories});
},
index: function(){
this._categories.fetch();
},
hashcategory: function(id){
this._todos = new TodosCollection();
this._subtodolist = new TodosView({ collection: this._todos,
id: id
});
this._todos.fetch();
}
As you can see, I create the index collection and view in the initialize method of the router, but I create the subview collection and view in the corresponding route function of the router. And I tried to put the index collection and view in the index function and the click event in index view will behave the same way as subviews. So I think that's why the mousedown and mouseup will be triggered several times.
But the problem is, I have to use the id as one of the parameters sent to subview. So I can't create subview in the initialize method. What's more, I've already seen someone else's projects based on Backbone and some of them also create sub collection and view in the corresponding route function, but their app runs perfectly. So I don't know what is the root of my problem. Could someone give me some idea on this?
Sounds like you're having a delegate problem because:
all sub views all use the a same <div> element
Backbone views bind to events using jQuery's delegate on their el. If you have a view using a <div> as its el and then you use that same <div> for another view by replacing the contained HTML, then you'll end up with both views attached to that <div> through two different delegate calls. If you swap views again, you'll have three views receiving events through three delegates.
For example, suppose we have this HTML:
<div id="view-goes-here"></div>
and these views:
var V0 = Backbone.View.extend({
events: { 'click button': 'do_things' },
render: function() { this.$el.html('<button>V0 Button</button>'); return this },
do_things: function() { console.log('V0 clicked') }
});
var V1 = Backbone.View.extend({
events: { 'click button': 'do_things' },
render: function() { this.$el.html('<button>V1 Button</button>'); return this },
do_things: function() { console.log(V1 clicked') }
});
and we switch between them with something like this (where which starts at 0 of course):
which = (which + 1) % 2;
var v = which == 0
? new V0({el: $('#view-goes-here') })
: new V1({el: $('#view-goes-here') });
v.render();
Then you'll have the multi-delegate problem I described above and this behavior seems to match the symptoms you're describing.
Here's a demo to make it easy to see: http://jsfiddle.net/ambiguous/AtvWJ/
A quick and easy way around this problem is to call undelegateEvents on the current view before rendering the new one:
which = (which + 1) % 2;
if(current)
current.undelegateEvents(); // This detaches the `delegate` on #view-goes-here
current = which == 0
? new V0({el: $('#view-goes-here') })
: new V1({el: $('#view-goes-here') });
current.render();
Demo: http://jsfiddle.net/ambiguous/HazzN/
A better approach would be to give each view its own distinct el so that everything (including the delegate bindings) would go away when you replaced the HTML. You might end up with a lot of <div><div>real stuff</div></div> structures but that's not worth worrying about.

Categories