I'm trying to learn Backbone from someone else's Backbone app in combination with the documentation. There are two things I don't understand about it that I can't figure out from the documentation. Hope someone might be able to explain...
Gist of the app
It's an app where you can drag and drop company names into boxes.
The app has a Company View, a Company Model and a Companies Collection.
It also has a Bucket View, a Bucket Model, and Buckets Collection
It also has a general App View that's not associated with any model.
Problems...
First- Is it weird that a view would never be rendered?
The App View is instantiated like this
window.App = new AppView();
and it gets different things going in the app (populating the buckets with company names etc and setting up some events). However, the App View is never rendered. The Bucket View and the Company View, on the other hand, are both rendered at different points in the code. For example,
var view = new BucketView({model: bucket});
this.$("#bucket-list").append(view.render().el);
But never that App View. This App View also has no render function. So is it weird that it has a View that's never rendered? Should its code be somewhere else?
Second Considering that App view is never rendered, is there any reason why they would have to change the default setting of el to the name of the main container div in the layout? For example, in the App view, they do this...
el: $("#mainapp"),
However, if this view's never rendered, is there any reason to change the default setting of el?
When you create a Backbone.View instance normally, it will immediately generat an el on the view for you. This is used as the placeholder for all of the HTML that the view will manage, and you generally populate the view's el with your HTML in the render method, as you've already noted.
In some cases, though, you don't want to render a completely new set of HTML elements. Instead, you'll want to manage some existing HTML that's already part of the DOM. In this case, you can easily attach a backbone view to the existing DOM element by specifying the el in the constructor, as you've shown with the el: $("#mainapp") line.
When a Backbone view sees that it already has an el, it does not generate it's own. Instead, it uses what it was handed.
So, to directly answer your two questions:
1) First- Is it weird that a view would never be rendered?
No, because ...
2) if this view's never rendered, is there any reason to change the default setting of el?
The question is slightly off in a manner that that can't be directly answered. In reality, the view is never rendered because they are setting the el in the call to the constructor.
Related
I've been studying this Backbone tutorial and came across this bit of code:
var LibraryView = Backbone.View.extend({
el:$("#books"),
initialize:function(){
this.collection = new Library(books);
this.render();
},
...
});
The author explains that this.render() makes the view render when its constructor is called.
When would you not want the view to self-render?
The render method does nothing more than adding HTML to the DOM by using jquery's html or append methods. Where you choose to call the render method on the view is an architectural choice.
Backbone is said to apply the MV* pattern where models are bound directly to the view.
As such, the view would render itself upon instantiation, and have the ability to render itself when the model changes.
However, Backbone leave a lot of decisions to the developer and is a flexible library. There is nothing that technically prevents you to use control objects that manage the flow of the views; as such, another object can instantiate and re-render the view as well.
var view = new Bb_View();
view.render();
In the end, render is just a method on the view, and you can define your own methods as well. An example reason why I would call custom methods on the view from outside the view, is when I keep reference to an array of views.
As an event occurs, I loop through the views, I might add some conditions, and then call the custom method on particular views based on the condition.
How to update the view with the model.fetch(), i.e when i fetch the model, the view shouldn't be destroyed or re-rendered. It should just update the new model into view with reseting the previous model.
this.model.fetch({success: this.render.bind(this)});
This code is re-rendering my view..How can i just update the view with new model
Thanks.
There are multiple ways of updating the view based on your need.
If the view is fairly simple, and all that is needed is to automatically update the view on model's fetch, rendering the view again is the simplest(Backbone's sync event on the model can be used, and model events can be handled declaratively using Marionette View's modelEvents hash -http://marionettejs.com/docs/marionette.view.html#viewmodelevents-and-viewcollectionevents)
modelEvents: {'sync': 'render'}
If you have few model attributes that change in a complex view, you could directly update the dom elements via jquery by listening to change events on the model attributes:
modelEvents: {'change:name':'nameChanged'},
nameChanged: function(model, value){ this.$('#name').val(value);}
If Two way data binding between view and model is needed, frameworks like Backbone.stickit can be used to this purpose - https://github.com/NYTimes/backbone.stickit#usage
Whenever you establish a double binding with your model attributes to the templates, your views need to be rendered again to show the changes. So, you can't avoid rendering to show the updated status of your model.
But what I would suggest you to do is to divide your view into subviews. Since, you are using marionette, you can create a main layout which contains all the regions and for each small region, you can define a view .
For example , suppose I have a basic form with my name, current time and age . All of these variables are stored in a model . So, you have a scenario where your name and age hardly changes but the current time is changing every millisecond causing your whole view to re-render which is bad in terms of performance as well as to the eyes.
So, in order to solve the above scenario, if you could create a separate view for the current-time element, you can render is separately and other elements don't need to be rendered again and again. You can always define a separate view for a single button element if you think that its functionality can be abstracted.
What is the best way to switch to a different view when user navigates to a different url. In angular there is ng-view that takes care of this and inserts corresponding templates and in ember its all route based.
Is it better to just hide other views elements on routing using css or destroying other views and inserting current view?
EDIT
It would be great if someone could give an example how to re-render the view on navigating back to it again and restoring its previous state.
Eg.
if you have a check-box in a view that user can select to add some item to the cart , but in the middle he/she moves to some other url and then comes back, that check-box should be checked.
I would have a main content view with subviews and call remove on it, which is responsible for cleaning up any subviews too (calling remove on them first and going up the hierarchy tree). The concept of subviews doesn't come for free with backbone but isn't hard to implement. And finally attach a new content view.
This ensures you can cleanup and the browser is using a consistent amount of resources.
I would abstract this into some kind of layout view which has a content subview and a function like setContent(view) which handles the remove of any existing content view and the attach of the new one.
Personally I would have a router with sub routers in modules, e.g. a main router which finds a route starting with "checkout" and passes it over to a sub router in the checkout module which is responsible for attaching a new content view.
In Backbone the implementation is up to you which is both good and bad, depending on how nice you do it ;)
Always remove the view as opposed to just hiding it. If you don't remove (and unbind) your views properly, all bindings, handlers and references to models/DOM elements will linger around.
Depending on the size of your app, you can have a module that handles layouts (as suggested by dominic-tobias), or have a method on the router that takes care of this for you. At its most basic, this method (let's call it _switchView) takes a view and holds onto an instance of the currentView. Upon view change, it removes the current view, sets the new view to the current view and then renders it to the DOM.
Something like this:
_switchView(view) {
this.currentView && this.currentView.remove();
this.currentView = view;
this.$rootEl.html(view.render().$el);
}
I'm working my way through my first attempt at using Backbone.Marionette and wondered if there is any reason to use a Backbone.Marionette.ItemView when a simple Backbone.View will suffice?
Thanks!
Marionette's ItemView provides a default render and close method which do a few things for you.
render does the following (amongst other things):
1) Marks view as !this.isClosed (related to showing views in Regions, another Marionette concept)
2) Triggers before and after render events
3) Calls a serializeData method to get data (defaults to data from either this.model or this.collection.
4) Gets the template (possibly from TemplateCache) and renders via Marionetter.Renderer
5) Binds UI elements
In addition to this "free" render method, you also get close functionality.
Of course, if you are using a CollectionView/ItemView combination (which is very powerful) or a CompositeView/ItemView, you HAVE to use an ItemView.
Outside of those cases, one big benefit is that you KNOW your view is going to be compatible with Marionette Regions. Ideally, in a Marionette app, you use separate regions to show your views, like so:
var myView = new MyView();
// render and display the view
MyApp.mainRegion.show(myView);
// closes the current view
MyApp.mainRegion.close();
If MyView is a Marionette ItemView, this will work like a charm. If it is just a raw Backbone View, you will probably need to do some work to make sure that it works as planned.
In my apps, I typically just opt for using ItemView as the basis for all of my non-Collection and non-Composite Views.
More than a specific answer I'm trying to get recommendations on best practices when deciding how to structure view code. In my current project I have a clear/clean mapping of models to views but all these are presented inside a pretty standard container with global navigation elements. For the sake of this question, picture it this way:
Header
tabs
cards
card 1
card 2
card 3
You click on a header tab and a card slides in. the cards themselves have a back button that slides it out to reveal the previously displayed card. The actual content of the card is generated by another view which likely maps to a model. I guess it's not that much different than common interface patterns in iOS or Android.
Anyways, I want to have a view class called "Card" with a template with the navigation for the card, and whatever else goes in every card. Then I'd like to somehow pass the content of the card to it.
Can I create a Card class and then extend it to create the subclasses (ie: subviews)? So, say I have a model called UserModel and a view called UserView that takes care of the form for that model, I want to do something like:
var Card = Backbone.View.extend();
var UserView = Card.extend();
Then when rendering have that result in, say:
<!-- code from Card -->
<div class="card">
<nav></nav>
<!-- code from UserView -->
<div class="user">
...
</div>
<!-- END code from UserView -->
</div>
<!-- code from Card -->
I realize a quick way to do this would be to simply manually grab the Card template from every view that I need wrapped, etc... but that feels wrong.
Does that make sense?
Oh... keep in mind that part of the advantage that I'm hoping this has is that I can interact with the UserView instance and have those trigger the necessary methods in the parent class. So...
var uv = new UserView();
uv.render() // <- should render the user view with the card wrapped around it.
I don't want to have to manually render the card, of course.
How would you structure it?
Thanks!
Since you asked specifically for best practices and it seems like you're starting into backbone let's first start with some basics.
Backbone has views that are defined with a template engine. The default template engine is underscore.js but a far better template engine is mustache.js or better yet it's higher level wrapper handlebar.js (HandleBarJS Website). This let's you structure your view and subview exactly like you described below all into one template with markers for being filled in with logic and thus bringing you down to only one big render event.
Now that you've got a good template to inject your model into you still want to make that render event perform well so let's make sure you precompile your template. For a good reason to understand why this is necessary read this article (Performance benefit of Precompiling templates). Handlebar.js has a method to precompile your templates so I won't give samples here.
Now the last step is build up your model. If you build it in JSON format you can simply just 'execute' your template with the model.
var context = {title: "My New Post", body: "This is my first post!"}
var html = template(context);
I have used Handlebar for illustrative purposes as the template creation is much easier IMHO but really anything will work with this model.
Hope that get's you on the right path.