Backbone.js - passing 2 models to 1 view - backbone.js

I'm trying to pass 2 models to the view, but it seems it is not working. Here is my example: http://jsfiddle.net/kahhor/jp4B6/14/ As you can see second alert is showing undefined...
May be I have wrong approach. What I'm trying to do is: in View1 bind event 'change' to Model1... Than by clicking button in View2, call function in Model1 which changes value, and automatically render View1, since it was binded to change event.
But don't forget that View2 has also its own Model2, which I created outside the view and than passed it like new View2({model:Model2});.
It might looked confusing at first, but I think it is simple thing that backbone can do. I just don't know how to do it :)
Thanks,

you can access custom parameters (options) from
window.PopupView = new PopupView({ model: LeftNotificationM, model2: PopupM});
like this:
window.PopupView = Backbone.View.extend({
// code left out
initialize: function () {
this.model.bind('change:notification_num', this.render);
alert(this.model);
// your model2 option:
alert(this.options.model2);
},
// code left out
});
Conclusion: "unrecognized options" of a view can be found in this.options

I just found this question and Sled's answer via Google and it helped me a lot - but just to update this 2 year old question and perhaps save some other Googlers the headache:
Backbone Views no longer automatically attach options passed to the
constructor as this.options, but you can do it yourself if you prefer.
Vitaliy's Answer on a similar question shows you how:
initialize : function (options) {
this.options = options || {};
}

Add this patch and pass any options directly to view.
Backbone.View.prototype._configureWithoutThis = Backbone.View.prototype._configure;
Backbone.View.prototype._configure = function(options) {
this._configureWithoutThis(options);
_.extend(this, this.options);
}

Related

How to avoid duplicate code and logic in templates for similar views in Backbone.js

I am building an app using backbone.js and find myself with a view with a lot of conditional logic in the templating of the Model's View. The type property of the model is used to determine which html to render. I would like to avoid this logic if possible, because it is hard to read. There are a couple of ways that I think I could deal with this (Note that the actual templates I've got here are very much simplified):
1. Conditional Logic in Collection View rather than in Model View - Multiple Subviews
I could put the conditional logic that acts on each model's type into the Collection View:
var CollectionView = Backbone.View.extend({
....
render: function() {
this.collection.each(function(thing) {
if(thing.get("type") === "wotsit") {
this.$el.append(new WotsitView({ model: thing });
} else if(thing.get("type") === "oojamaflip") {
this.$el.append(new OojamaflipView({ model: thing });
}
}, this);
},
....
}
Pros
This way I could have each subview with a template method that had no logic in it, but rather just builds html.
var WotsitView = new Backbone.View.extend({
....
template: _.template('<h2>{{ title }}</h2>');
});
var OojamaflipView = new Backbone.View.extend({
....
template: _.template('<h3>{{ title }}</h3>');
});
Cons
The thing is, the things in the collection are all very similar. The events for each thing are likely to be the same or very similar and I can see there being a lot of code duplication. I really only want the actual template for these subviews to be different with everything else the same.
2. Conditional Logic in Model View - Multiple Template Methods
var ModelView = Backbone.View.extend({
....
render: function() {
if(this.model.get("type") ==== "wotsit") {
this.$el.html(this.wotsitTemplate(this.model.attributes));
} else if(this.model.get("type") === "oojamaflip") {
this.$el.html(this.oojamaflipTemplate(this.model.attributes));
}
},
wotsitTemplate: _.template('<h2>{{ title }}</h2>'),
oojamaflipTemplate: _.template('<h3>{{ title }}</h3>')
});
Pros
There is only one view for the model.
All of the events etc are handles in one view rather that being duplicated.
Cons
I actually quite like this way, but I would be very interested to hear some other peoples options on it.
Option #1 = polymorphism
Option #2 = switch statement
Switch statements are useful if you never (or rarely) have to add new types, but you might want to add new methods. Imagine you want to add a validate method to ModelView, which checks view input for that kind of model for correctness and reports the results to the user. With option #2, you'd just add one new method that switches on the model type (just like the render method) to handle validation.
Now let's assume we already have a render method and a validate method, and we want to handle a new type of model, a thingamajig. With option #2, you'd have to add logic to both render and validate. Now imagine we don't have just 2 methods, but 10 — option #2 gets real complicated real quick when you have to handle new types. But if we followed option #1, then no matter how many methods there were, we'd only have to create a new view in one place, and update CollectionView to map the new type to the new view.
Most of the time polymorphism (option #1) is the clean way to go. It lets you separate all logic specific to a given type and put it one place, making it easy to handle new types.
Keeping DRY
If you're worried with option #1 that you'll end up with a lot of duplicate code between all the views, don't forget that it's easy to make Views that inherit from other Backbone Views:
var ItemView = Backbone.View.extend({
initialize: function() {
/* some common logic for all views */
}
});
var WotsitView = ItemView.extend({
template: _.template('<h2>{{ title }}</h2>')
});
var OojamaflipView = ItemView.extend({
template: _.template('<h3>{{ title }}</h3>')
});

How do I design MarionetteJS ItemView to properly show loading message?

First off - I am a MarionetteJS noob.
I am having trouble making an ItemView display a loading message or throbber while it is being fetched. This is especially problematic when this ItemView is being displayed from a deep link in Backbone's history (i.e. the ItemView is the first page of the app being displayed since the user linked directly to it). I want to indicate that the page is loading (fetching), preferably with a simple view, and then show the real templated view with the fetched model.
I have seen other answers on SO like Marionette.async, (which has been deprecated) and changing the template during ItemView.initalize().
Anybody (Derrick?) got any suggestions or best practices here?
UPDATE:
I am getting the model from the collection using collection.get(id), not using model.fetch() directly.
After thinking about this, the real question is where should this be implemented:
I could change my controller to see if the model exists in the collection (and if the collection is loaded) and decide which view to show accordingly. this seems like a lot of boilerplate everywhere since this could happen with any ItemView and any controller.
I could change my ItemView initialize to test for existence of the model (and a loaded collection), but same comment here: every ItemView could have this problem.
UPDATE 2:
This is what I ended up with, in case anybody else want this solution:
app.ModelLayout = Backbone.Marionette.Layout.extend({
constructor: function(){
var args = Array.prototype.slice.apply(arguments);
Backbone.Marionette.Layout.prototype.constructor.apply(this, args);
// we want to know when the collection is loaded or changed significantly
this.listenTo(this.collection, "reset sync", this.resetHandler);
},
resetHandler: function () {
// whenever the collection is reset/sync'ed, we try to render the selected model
if (this.collection.length) {
// when there is no model, check to see if the collection is loaded and then try to get the
// specified id to render into this view
this.model = this.collection.get(this.id);
}
this.render();
},
getTemplate: function(){
// getTemplate will be called during render() processing.
// return a template based on state of collection, and state of model
if (this.model){
// normal case: we have a valid model, return the normal template
return this.template;
} else if (this.collection && this.collection.isSyncing) {
// collection is still syncing, tell the user that it is Loading
return this.loadingView;
} else {
// we're not syncing and we don't have a model, therefore, not found
return this.emptyView;
}
}
});
And here is how to use it:
// display a single model on a page
app.Access.Layout.CardLayout = app.ModelLayout.extend({
regions: {
detailsRegion:"#detailsRegion",
eventsRegion:"#eventsRegion"
},
template:"CardLayout", // this is the normal template with a loaded model
loadingView:"LoadingView", // this is a template to show while loading the collection
emptyView:"PageNotFoundView", // this is a template to show when the model is not found
onRender : function() {
this.detailsRegion.show( blah );
this.eventsRegion.show( blah );
}
});
thanks!
For the ItemView
I think you can add a spinner in your initialize function, I really like spin.js http://fgnass.github.io/spin.js/ because its pretty easy and simple to use, and you can hide the spinner in the onRender function of the Itemview
For The CollectionView
in the CollectionView you could handle it like this....
Take a look at the solution that Derick posted..
https://github.com/marionettejs/backbone.marionette/wiki/Displaying-A-%22loading-...%22-Message-For-A-Collection-Or-Composite-View
I'd suggest using jQuery deferreds:
Start fetching your data, and store the return value (which is a jQuery promise)
Instanciate your content view
Show your loading view
When the promise is done, show the view containing the content
I've talked about implementing this technique on my blog:
http://davidsulc.com/blog/2013/04/01/using-jquery-promises-to-render-backbone-views-after-fetching-data/
http://davidsulc.com/blog/2013/04/02/rendering-a-view-after-multiple-async-functions-return-using-promises/
The issue with the solution linked by Rayweb_on, is that your loading view will be displayed any time your collection is empty (i.e. not just when it's being fetched). Besides, the ItemView doesn't have an emptyView attribute, so it won't be applicable to your case anyway.
Update:
Based on your updated question, you should still be able to apply the concept by dynamically specifying which template to use: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.view.md#change-which-template-is-rendered-for-a-view
Then, when/if the data has been fetched successfully, trigger a rerender in the view.

Binding Backbone change event to attribute of model in a collection

I have a view that takes a collection like below:
MyView = Backbone.View.extend({
initialize: function() {
this.collection.on("change://an attribute of a model in aCollectionToRender", someAction);
}
});
var MyViewInstance = new MyView({
collection: aCollectionToRender
});
Basically, I don't want MyView to listen for changes on the entire collection, only a single attribute of the model that the collection contains.
I realize there are a number of alternatives to this:
Create views for each model of aCollectionToRender and attach .on("change") events in those views. I don't want to do this because it's not the right thing to do for my situation
Just have a this.collection.on("change") event and have the event handler filter based on my requirements. This is less elegant, and if Backbone already allows me to write event filters as I asked above, this is duplicate code
I was just wondering if there's any way to write an event listener that already does the filtering. This question may also be a duplicate; I looked but I didn't find anything exactly like this, however, there are a lot of Backbone questions
Maybe I am misunderstanding your question, but you can do exactly what you show. Check out the Backbone documentation.
MyView = Backbone.View.extend({
initialize: function() {
this.collection.on("change:attributeName", this.someAction /*function to call*/, this);
},
someAction: function(model, value, options){
}
});

View + Sub Views in backbone/backbone-relational app

I've been learning a lot about backbone, backbone-relational and building web apps from reading through stackexchange - so first a thank you to the community.
Now, I am stuck at trying to understand this current issue involving nested models and sub views in what seems to me to be is a pretty common usecase.
I am trying to extend this tutorial to learn about backbone-relational, views/subviews and event handling by keepin track of "CheckIns" for each wine.
I have extended the server side to return appropriate JSON and backbone-relational model for checkIns like so:
window.CheckInModel = Backbone.RelationalModel.extend({
defaults:{
"_id":null,
"Did":"true",
"dateOf":"",
}
});
window.CheckInCollection = Backbone.Collection.extend({
model : CheckInModel
});
And the Wine Model like so:
relations: [{
type: Backbone.HasMany,
key:'CheckIn',
relatedModel: 'CheckInModel',
collectionType: CheckInCollection,
}]
I've created a CheckInListView and CheckInItemView (same as WineListView and WineListItemView) and use the WineView Render function to render the CheckIns like so:
render:function (eventName) {
console.log("wine View Render");
$(this.el).html(this.template(this.model.toJSON()));
this.myCheckInListView = new CheckInListView({model: this.model.attributes.CheckIn});
this.$el.append(this.myCheckInListView.render().el);
return this;
},
I've also created a new function within wineview that creates a checkin and associated with the given event:
logcheckin: function (event){
var todate = new Date();
newCheckIn = new CheckInModel ({'Did':"true", 'dateOf': todate.toISOString()});
console.log ("Logcheckin - About to push newCheckIn onto Model.");
this.model.attributes.CheckIn.push (newCheckIn);
console.log ("Just pushed newCheckIn onto Model.");
this.saveWine();
}
Ok - if you haven't TL/DRed yet - This all seems to work fine from a UI perspective -ie. everything renders correctly and saves to the Db.
But I notice in the console that when I push a new CheckIn (between the console.logs above) the CheckInListView's Add binding gets called multiple times for wach button press - Which makes me think something is wrong with how I'm doing views or that I am not understanding something fundamental about event propagation.
Why is this happening ? Is it expected behavior ? Am I approaching what I am trying to do correctly ?
Thanks for reading if not your help.
==
Here are the relevant parts of the CheckinListView and CheckInList Item views that are bound to the add (and other) events.
window.CheckInListView = Backbone.View.extend({
initialize:function () {
this.model.bind("reset", this.render, this);
this.model.bind("change", this.render, this);
var self = this;
this.model.bind("add", function (CheckIn) {
console.log ("Adding to CheckInListView - a CheckIn List Item", CheckIn);
self.$el.append(new CheckInListItemView({model:CheckIn}).render().el);
});
},
close:function () {
$(this.el).unbind();
$(this.el).remove();
}
});
window.CheckInListItemView = Backbone.View.extend({
initialize:function () {
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
});
==============================================
The comment about event binding and closing views were the right hints for debugging this.
1) I was not closing and unbinding the nested views properly which left some ghost event consumers even though there was nothing in the DOM
2) You only need to bind events if we want to do something only in the subview.
For Example - If I have checkbox in a subview I can bind a subview change event in the main view and handle the event there since the mainview has model there anyway. I don't know if this is the "right" way but it works for what I need to do. (mm.. spaghetti code tastes so good)
3) Struggling with this helped me think through the UX some more and helped me simplify UI.
4) I was trying to "save" calls to the server by nesting all the data into on JSON call. And if I were to re-do this - I would not nest the data at all but handle it on the back end by associating the wine ID with checkIn ID and then having a separate collection that gets populated with the collection once a task is selected - I thought this would not be a the preferred way but it seems to be the way that a lot of people.
Still welcome any thoughts on the "right" way questions above or if anyone can point to a tutorial that goes beyond the "simple backbone app"
I'm not sure about everything that's happening, but, I've run into the problem of events firing multiple times before. If you're rendering multiple models using the same view, there's a chance that they're all being bound to the same event.
Perhaps this answer might apply:
Cleaning views with backbone.js?
If not, you should respond to Edward M Smith's comment and show how your events are being bound.

Is it possible to dynamically define a comparator function for a collection?

I have a basic collection :
myCollection = Backbone.Collection.extend({
model: myModel
url: '/theurl',
initialize: function(){
this.fetch();
})
})
When initialized, the collection receives items ordered by date.
I would like to be able to dynamically reorder the collection, using another model attribute (name, rating, etc.).
In the view associated with the collection, I tried to bind a click event to a callback like this :
reorderByName: function(){
myCollection.comparator = function(item){
return item.get('name');
});
this.render();
})
Unfortunately, that does not seem to work. Any suggestions as to how I could do it ?
Thanks.
It looks like you've only done half of what you need to do. You've given the collection its comparator, but you've not told it to resort itself. So you need to add something like this statement right before you render:
myCollection.sort();
Note that this will trigger a reset event which would be received by any object that you've bound this event to. If you wish to suppress triggering that event, you could make the call this way:
myCollection.sort({silent: true});
Hope this helps.
I found this while looking for a solution to my own issues with comparator. I thought I would add, in case anyone else finds this question, that if the models in your collections have undefined values for the property by which your comparator is sorting, the sort won't happen. Be sure to use defaults on your model if they're not required and you're gonna sort by 'em. ;)

Resources