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.
Related
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've been learning backbone.js over the past couple of weeks and am about to start using it in anger in an app I'm writing. My question to you is about a use case for models and collections in a Bootstrap 3 navbar.
On my server side I authenticate the user and, based on their profile, assign them a role (author, editor, administrator etc.). I then construct an object that contains the appropriate menu structure for the user's role and pass that to the client using Handlebars. The intent is for the browser to construct the HTML to render the menus according to the properties (key/values) in the object using backbone.
My thoughts are that the navbar itself is a collection of models (navbar); each dropdown menu or link on the navbar is a single model (navbarItem); each of these contains a collection of menu items (navbarItemMembers), these collections being of models of each individual menu item (navbarItemMember). I can then set event listeners against each navbarItemMember to trigger an appropriate route or render action as appropriate.
So, getting to the point... am I over-complicating things? A collection containing models each containing a collection of other models, each of those mapping to a view that renders a on the main page. Seems convoluted to me, but from my (albeit limited) understanding of backbone.js it does seem the right way to do this...?
Advice much appreciated from those more experienced (battle scarred?!) than I. Thank you.
Use a collection when it's going to provide some benefit to you over a plain model. The benefits could be either
Interacting with a RESTful service where you'll want to not just get a list of data but then separately fetch/modify/add/delete individual items is that list
Defining separate Views for each item in the list rather than a having a single View that iterates over the list.
I don't think a basic navbar benefits from either of those. If your navbar has some super fancy elements beyond just links then maybe #2 applies but otherwise keep it simple, use a single model with a single view/template
I'm a little new to the AngularJS world, and I'm wondering what the best way is to show a resource detail view within a list view.
Given a path like /people, I would just show a list of people resources. If I were to navigate from one of those people to a url like people/john-doe, I can easily get a detail view and all the data associated with that person. But how can I do this without leaving the list view? I'd like to somehow append the detail to the list, or show it within the list, etc.
What you're describing is nested views, which isn't something that the regular ngRoute library does well. I advise you check out angular-ui router.
Is there a way that I can tell backbone/marionette not to automatically add a the wrapping container tag when rendering a view to a region?
For example, lets say I want to display a list of items. The way I want to do that is by having one composite view looking looks this:
<ul>
</ul>
And then a item view that looks like this:
<li><%= title %></li>
Now I can easily get the same output by changing what the tag used for the auto generated element is however the biggest reason I would not have the tag automatically generated by backbone/marionette is because I want to keep as much html in the template as possible. If for whatever reason I want to change the list to a table, if all the HTML is contained in templates, I only have to change the two template files. The other way would require me to change 2 template files and 2 javascript files (less file I have to change to make a change, the less chance for error). I just like to have that level of separation of concern.
Neither Backbone nor Marionette have built in support for this. But I imagine if you could get this behaviour with a few tweaks.
Backbone.View has a method called _ensureElement that constructs the el from tagName and className if the el is not provided.
You could override _ensureElement to render the template first an then use it's first tag name as the tagName for the view. You'd also need to override Marionette.Renderer.render to strip the template's outer tag.
There maybe a better choice of methods to override to achieve this logic. I would also advise that you provide a flag somewhere to turn this behaviour on selectively so you could mix and match as needed, which would also help in debugging.
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.