BackboneJS: How to assign multiple models in a single view - javascript

How do you call multiple models in a single view
Usual case is that you create a backbone model
var myModel = Backbone.Model.extend({ url:"api/authentication"});
and then assign it to a view, making it the default model of a certain view
var LoginView = Backbone.View.extend({
model: new myModel(),
initialize: function(){
_.bindAll(this, 'render', 'render_thread_summary', 'on_submit', 'on_thread_created', 'on_error');
this.model.bind('reset', this.render);
this.model.bind('change', this.render);
this.model.bind('add', this.render_thread_summary);
},
...
});
But I have many methods in a single view that calls different models. Currently I just create an instance of a model inside an event method.
The other solution that I'm thinking would be like this
initialize: function(){
_.bindAll(this, 'render', 'render_thread_summary', 'on_submit', 'on_thread_created', 'on_error');
new.model1().bind('reset', this.render);
new.model2().bind('change', this.render);
new.model3().bind('add', this.render_thread_summary);
},
But are these good practices? What else are the other way to do this. Thanks.
UPDATE
In response to Peter, here's my actual code
var userRegModel = Backbone.Model.extend({url: fn_helper.restDomain()+'user/reg'});
var userResendActivationModel = Backbone.Model.extend({url: fn_helper.restDomain()+'user/token/activation/new'});
var userResetPassModel1 = Backbone.Model.extend({url: fn_helper.restDomain()+'user/pwdreset/token/new'});
var userResetPassModel2 = Backbone.Model.extend({url: fn_helper.restDomain()+'user/pwdreset/change'});
var userLoginModel = Backbone.Model.extend({url: fn_helper.restDomain()+'user/login'});
_view: Backbone.View.extend({
el: 'div#main-container',
initialize: function(){},
events: {
'click #user-signup': 'signupUserFn', //userRegModel
'click a#activation-resend': 'resendActivationFn',//userResendActivationModel
'click #reset-password-submit1': 'execResetPasswordFn', //userResetPassModel1
'click #reset-password-submit2': 'execResetPasswordFn', //userResetPassModel2
Each models declared above corresponds to different methods declared in events

Normally model instances should be passed to views in the options argument to the initialize method.
Generally models represent data that either is currently or will eventually be stored in database somewhere. Now, sometimes you want to create a model just for managing ephemeral browser state, which is fine, but the common case is that most read/edit/delete views don't create models unless that's their primary purpose like a "Post New Comment" form. So normally if your view has to show some models, allow them maybe to be updated and saved, they should come into the view in the options argument to the initialize function.
However, if your view really needs so many models and nothing else in your system cares about them, just create them during initialize and store them directly as properties on this (your view instance) and off you go.
If you post a more complete code sample, we can give specific recommendations, but as it is it looks like if you have a LoginView and you need more than 1 model for it, you might be off in the weeds design-wise.
Part Deux
OK, so it looks like you think every API endpoint needs a model. Just implement those as methods on a single model class that use Backbone.sync with the appropriate url option.
var Model = Backbone.Model.extend({
resendActivation: function () {
return Backbone.sync.call('update', this, {url: fn_helper.restDomain()+ 'user/token/activation/new'});
}
});
You may have to do some manual triggering of events and I'm not sure what your API returns, but fundamentally these are all dealing with a single user record and can be handled by a single model.
Misc unrelated tips:
_.bindAll(this); //don't bother listing methods out
this.model.on('reset change', this.render); //bind multiple events at once
JavaScript by vast majority convention use camelCase not lower_with_underscores

Related

When to destroy my model in case of multiple views depending on it

I have a case where I am creating multiple views for a single model. When I close a view, I am removing the model from the collection.
But in case of multiple views, there are other views that depend on the model.
So, how can I know when there are no views depending on the model? When should I destroy the model in case of multiple views?
Generally speaking the lifecycle of a view shouldn't cause a model/collection to be modified, but assuming you have a good reason for it.
Here is a slight improvement on Kenny's answer, I'd suggest to have dependentViews as an array on the object, not a value in the attributes (if Views aren't persisted, best not persist view dependencies).
var myModel = Backbone.Model.extend({
initialize: function() {
this.dependentViews = [];
},
addDependentView: function(view) {
this.dependentView.push(view);
},
closeDependentView: function(view) {
this.dependentViews = _.without(this.dependentViews, view);
if (_.isEmpty(this.dependentViews)) {
//remove model from collection
}
}
})
var view1 = Backbone.View.extend({
initialize: function() {
this.model.addDependentView(this);
}
})
var view2 = Backbone.View.extend({
initialize: function() {
this.model.addDependentView(this);
}
})
...
onCloseView: function() {
this.model.closeDependentView(this);
}
Also an array of objects might come in handy for the dependency list, that way you could if necessary in future, make calls from the model to the dependent views.
Another possible solution might be to use the internal event listener register as a means of tracking any objects listening to the model. But that would be more involved and would depend on internal functionality within Backbone.
Though this is not a proper way but still you can achieve this following
Declare dependentViews in your model's defaults
var myModel = Backbone.Model.extend({
defaults: function() {
return {
dependentViews: 0
};
}
});
In initialization of each view, increment the dependentView
var view1 = Backbone.View.extend({
initialize: function() {
this.model.set("dependentViews",
this.model.get("dependentViews") + 1);
}
});
var view2 = Backbone.View.extend({
initialize: function() {
this.model.set("dependentViews",
this.model.get("dependentViews") + 1);
}
});
On close of view decrement dependentViews and on each view destroy, just check the value of dependentViews. If it is 0, remove the model from the collection.
onCloseView: function() {
this.model.set("dependentViews",
this.model.get("dependentViews") - 1);
if (this.model.get("dependentViews") === 0) {
//remove model from collection
}
}
Having the number of views inside the data model as suggested in other answers is not a good idea.
I have a case where I am creating multiple views for a single model
So you have a "place" where you create views, and destroy these views. This could be a parent view, controller, router instance etc.
If you don't have one then you need one.
You must be having references to these view instances for future clean up.
If you don't have it then you need it.
When you're destroying a view just check the remaining number of instance, if there are none then remove the model (If the view removes itself in response to some user action then it needs to notify the parent of removal).
This is something that should take place outside the view instances and model.

Best method for rendering collection in Backbone

I'm using Backbone and I have the following model and collection
App.Models.Person = Backbone.Model.extend({
});
App.Collections.People = Backbone.Collection.extend({
model: App.Models.Person,
url: 'api/people',
});
However what I'm struggling on is the best way to render this collection. Here's how I've done it so far which works but doesn't seem to be the most elegant solution
App.Views.PeopleView = Backbone.View.extend({
el: $(".people"),
initialize: function () {
this.collection = new App.Collections.People();
//This seems like a bad way to do it?
var that = this;
this.collection.fetch({success: function(){
that.render();
}});
},
render: function () {
var that = this;
_.each(this.collection.models, function (item) {
that.renderPerson(item);
}, this);
},
I'm fairly new to Backbone but have to assign this to a different variable to I use it inside of the success function just seems like a bad way of doing things? Any help on best practices would be appreciated.
Backbone allows you to register for events that you can react to. When the collection is synchronized with the server, it will always fire the sync event. You can choose to listen for that event and call any given method. For instance ...
initialize: function () {
this.collection = new App.Collections.People();
this.listenTo(this.collection, "sync", this.render);
// Fetch the initial state of the collection
this.collection.fetch();
}
... will set up your collection so that it would always call this.render() whenever sync occurs.
The docs on Backbone Events are succinct but pretty good. Keep in mind a few things:
The method you use to register event listeners (i.e. listenTo or on) changes how you provide the context of the called function. listenTo, for instance, will use the current context automatically; on will not. This piece of the docs explains it pretty well.
If you need to remove a view, you will need to disconnect event listeners. The easiest way to do that is to use listenTo to connect them in the first place; then when destroying the view you can just call view.stopListening().
For rendering, there are a lots of suggestions for how to do it. Generally having a view to render each individual model is one way. You can also use Backbone.Collection#each to iterate over the models and control the scope of the iterating function. For instance:
render: function() {
this.collection.each(function(model) {
var view = new App.Collections.PersonView({ model: model });
view.render();
this.$el.append(view.$el);
}, this);
}
Note the second argument to .each specifies the scope of the iterator. (Again, have a look at the docs on controlling scope. If you'd rather have a framework help out with the rendering, check out Marionette's CollectionView and ItemView.
If your view is supposed to just render the collection you can send the collection to temnplate and iterate through in template, otherwise you can create another subView for that purpose or send the individual models of the collection to another subview and append to the container, hope it was helpful.

How to maintain multiple collection and view instances in backbone?

brief structure of my app
-->master view
-->model
-->Collection
-->SubView
Master view create many subview with collection instances.
masterview inside
newTable:function(){
var collection=new Collection;
var subview=new SubView({collection:collection});
}
I need to get send requests from server.
I need to update collections according to response.
for those requirements
I need to maintain the which request from which collection
which collection have to update on response.
How can I maintain requests and responses?
The point of Backbone.sync is to handle that for you. When you have a collection, you can perform a fetch on it like this:
collection.fetch()
The collection will then make an ajax request to its url via Backbone.sync and update itself when it returns. It will then trigger the "reset" event to let you know that it has been updated. Bind the subviews to the "reset" event on their respective collection like this:
SubView = Backbone.View.extend({
initialize: function(){
this.collection = this.options.collection;
this.collection.bind("reset", this.render, this);
}
})
This particular subview should be instantiated like this new SubView({collection: yourCollection}). It will then render itself when you do a yourCollection.fetch(). You can of course bind to any function you like!

Backbone JS: can one view trigger updates in other views?

In my simple project I have 2 views - a line item view (Brand) and App. I have attached function that allows selecting multiple items:
var BrandView = Backbone.View.extend({
...some code...
toggle_select: function() {
this.model.selected = !this.model.selected;
if(this.model.selected) $(this.el).addClass('selected');
else $(this.el).removeClass('selected');
return this;
}
});
var AppView = Backbone.View.extend({
...some code...
delete_selected: function() {
_.each(Brands.selected(), function(model){
model.delete_selected();
});
return false;
},
});
Thing is, I want to know how many items are selected. In this setup selecting is NOT affecting the model and thus not firing any events. And from MVC concept I understand that views should not be directly talking to other views. So how can AppView know that something is being selected in BrandViews?
And more specifically, I AppView to know how many items were selected, so if more than 1 is selected, I show a menu for multiple selection.
You might want to have a read of this discussion of Backbone pub/sub events:
http://lostechies.com/derickbailey/2011/07/19/references-routing-and-the-event-aggregator-coordinating-views-in-backbone-js/
I like to add it in as a global event mechanism:
Backbone.pubSub = _.extend({}, Backbone.Events);
Then in one view you can trigger an event:
Backbone.pubSub.trigger('my-event', payload);
And in another you can listen:
Backbone.pubSub.on('my-event', this.onMyEvent, this);
I use what Addy Osmani calls the mediator pattern http://addyosmani.com/largescalejavascript/#mediatorpattern. The whole article is well worth a read.
Basically it is an event manager that allows you to subscribe to and publish events. So your AppView would subscript to an event, i.e. 'selected'. Then the BrandView would publish the 'selected' event.
The reason I like this is it allows you to send events between views, without the views being directly bound together.
For Example
var mediator = new Mediator(); //LOOK AT THE LINK FOR IMPLEMENTATION
var BrandView = Backbone.View.extend({
toggle_select: function() {
...
mediator.publish('selected', any, data, you, want);
return this;
}
});
var AppView = Backbone.View.extend({
initialize: function() {
mediator.subscribe('selected', this.delete_selected)
},
delete_selected: function(any, data, you, want) {
... do something ...
},
});
This way your app view doesn't care if it is a BrandView or FooView that publishes the 'selected' event, only that the event occured. As a result, I find it a maintainable way to manage events between parts of you application, not just views.
If you read further about the 'Facade', you can create a nice permissions structure. This would allow you to say only an 'AppView' can subscribe to my 'selected' event. I find this helpful as it makes it very clear where the events are being used.
Ignoring the problems with this that you already mention in your post, you can bind and trigger events to/from the global Backbone.Event object, which will allow anything to talk to anything else. Definitely not the best solution, and if you have views chatting with one another then you should consider refactoring that. But there ya go! Hope this helps.
Here is my case with a similar need: Backbone listenTo seemed like a solution to redirect to login page for timed out or not authenticated requests.
I added event handler to my router and made it listen to the global event such as:
Backbone.Router.extend({
onNotAuthenticated:function(errMsg){
var redirectView = new LoginView();
redirectView.displayMessage(errMsg);
this.loadView(redirectView);
},
initialize:function(){
this.listenTo(Backbone,'auth:not-authenticated',this.onNotAuthenticated);
},
.....
});
and in my jquery ajax error handler:
$(document).ajaxError(
function(event, jqxhr, settings, thrownError){
.......
if(httpErrorHeaderValue==="some-value"){
Backbone.trigger("auth:not-authenticated",errMsg);
}
});
You can use Backbone object as the event bus.
This approach is somewhat cleaner but still relies on Global Backbone object though
var view1 = Backbone.View.extend({
_onEvent : function(){
Backbone.trigger('customEvent');
}
});
var view2 = Backbone.View.extend({
initialize : function(){
Backbone.on('customEvent', this._onCustomEvent, this);
},
_onCustomEvent : function(){
// react to document edit.
}
});
Use the same model objects. AppView could be initialized with a collection, and BrandView initialized with one model from that collection. When attributes of a branch object change, any other code that has a reference to that model can read it.
So lets so you have some brands that you fetch via a collection:
var brands = new Brands([]);
brands.fetch();
Now you make an AppView, and an array of BrandView's for each model.
var appView = new AppView({brands: brands});
var brandViews = brands.map(function(brand) {
return new BrandView({brand: brand});
});
The appView and the brandViews now both have access to the same model objects, so when you change one:
brands.get(0).selected = true;
Then it changes when accessed by the views that reference it as well.
console.log(appView.brands.get(0).selected); // true
console.log(brandViews[0].brand.selected) // true
Same as John has suggested above, the Mediator Pattern works really good in this scenario, as Addy Osmani summing this issue up again in Backbone fundamentals.
Wound up using the Backbone.Mediator plugin which is simple and great, and makes my AMD View modules working together seamlessly =)

Can't grok the process of updating UI on model change

I have a collection of models. When a model changes it triggers a change event on the collection. I watch for the collection change event and then I update the UI.
How should I go about updating the UI? Don't I need to know what models are new, so I can append, and what already exist, so I can update?
One of the reason I feel I need this granularity is because there's an animation transition, so I need to relate every model to it's previous state. Does backbone help with this or should I just build this on my own?
to know which models are new, listen to the collection's "add" event. then you can render the individual item.
MyView = Backbone.View.extend({
initialize: function(){
_.bindAll(this, "renderItem");
this.collection.bind("add", this.renderItem);
},
renderItem: function(item){
// render the new item here
},
render: function(){
this.collection.each(this.renderItem);
}
});
in this example, rendering the collection works the same as rendering an individual item - it calls the same renderItem method that the "add" event calls.
to handle the scenario where you have a sorted list... you'll have to do some logic in your renderItem method to figure out the location of the item. something like this maybe (untested code... just an idea):
MyView = Backbone.View.extend({
initialize: function(){
_.bindAll(this, "renderItem");
this.collection.bind("add", this.renderItem);
},
renderItem: function(item){
var itemView = new ItemView({model: item});
itemView.render();
return itemView;
},
render: function(){
this.collection.each(function(item){
var itemView = renderItem(item);
var itemIndex = item.get("index");
var previousItem = this.$(".myList")[itemIndex];
$(itemView.el).insertAfter($(previousItem));
});
}
});
this code assumes you have an index attribute on your models to know the order that the models are supposed to be in.
also note that there's a high likelihood that this code won't execute as-is, since i haven't tested it out. but it should give you an idea of the code you'll want to write.
You don't need to go through the collection if the model changes. Simple render a sub view for each model and in which you can handle the change event.
Imagine you have the following structure:
Collection: Tasks
Model: Task
And two views:
Tasks view (main view)
Task view (sub view)
In the #render method of the Tasks view you render e.g. a <ul> element which will hold your tasks (sub views). Then you iterate through all your collection's models and create a Task view for each, passing the model and appending it's el to your main views <ul>.
Within your Task view you bind the #render method to the models change event and each Task view will take care of itself. — Am I making sense?

Categories