Backbone - Reusing Views vs Creating new Views - backbone.js

Writing my first Backbone app - came across a predicament wherein i am unable to choose which is the best way to move forward.
Scenario : User clicks an edit button , a new view is loaded . Approach is as below.
renderEditView: function(){
if(my.namespace.view){
my.namespace.view.render();
}else{
my.namespace.view= new editView({model:my.namespace.model});
}
}
Basically, i am assigning my view to a namespaced variable and resuing it as required. Didn't face any problems as such.
But some advocate recreating the View again using new editView({model:xxx}); whenever the edit button is clicked . i Would like to know which one is the better practice and why?
P.S: i am aware of the 'event ghosting' problem in BB apps, and the excellent solution provided by Derick Bailey .But still would love to know the pros and cons between the approaches.

This is indeed an opinion, because either way will work if you (as you mention) take care of cleaning up previous views if you decide to instantiate a new one every time you want to re-render. It's important to avoid duplicating lingering events from every instance that you want to replace by creating a new one.
Personally I have used both strategies and never had problems with them so far.
When re-using a view, I bind the view as a property to the controller object that renders the view, pretty much the same way you do it.
Theoretically, I don't see a reason to re-instantiate a view if it was already created before. It isn't that you really require a new instance, it's just that you want to re-render it.
Sidenote
For re-rendering views, Backbone Marionette offers regions, which are convenience objects that allow you to do things like:
var myView = new MyView();
var region = new Marionette.Region({el: "#container"});
region.show(myView);
In case you would decide to instantiate a new view every time, these regions take care that previously rendered views are properly cleaned up.

Related

backbone js using multiple collection in view

I am quite new in backbone js . I was reading the documentation of backbone and I come up with this idea to use multiple collection in view.
If I have single view and I want to use more than one collection how can I achieve it?
how can the view understand multiple collection?
thanks.
Why do you ever need to use multiple collection inside a single view? Backbone's strength is it's modularity, which means you can develop the whole structure of your application by building it's components piece-by-piece. So in normal circumstances one view has one model or collection of models, but it's acceptable and it's often used the case when a collection has multiple views (for example a chat system).
To have multiple collections inside a single view is against backbone's modularity principle. Breaking up views to respond to only one model/collection turns out much more modular and reusable code.
So i suggest to break up your application into smaller pieces and operate on segment level, but if you really need to preserve the current structure you can do something like this:
var view = new MyView({
collection: {
users: new UsersCollection(),
organization: new OrganizationCollection()
}
});

ExtJS4 - Store per panel instance?

I'm really new to the MVC pattern in Ext.
I have a tabpanel with multiple instances of the same component (let's call it product), each should call the server when it's opened, with an id parameter.
Right now, in order to create these tabs - I use this in the Product controller
Which creates a new instance of a view, but I feel like it's really incorrect.
createMainView: function (opts) {
return Ext.widget("productDisplay", opts);
}
I call it from my "main" controller, like this:
var tab = this.application.getController("Products")
.createMainView({ productId : id, closable: true })
tabs.add(tab);
tabs.setActiveTab(tab);
What's the correct way to properly use multiple instances of a view, each having an instance of one store and behavior (via the controller).
Can I use one named store for them (with a js file under app/store/product.js)?
Should I manually call load on the store from the controller (to pass the productId), or is there a nicer way?
It's kind of very broad and interesting question which require big and thorough explanation (which you can find btw on the Sencha.com in their guides and manuals). I would like highlight couple points so you have something to start with:
Stores are usually global objects. You don't keep two instances of one store in general. You can use filtering (local or remote) if you need to present information from the that store in several different views. The only time you would need to clone store is if you want to present different information from that store in 2+ different views at the same time.
Controllers are usually spawned by main application object. You don't have to do anything special - just list them in the controllers: [] property. And then spawed when application is launched (before their views are created and rendered). Keep that in mind.
If you have modal view - it's ok to create it manually and either re-use it or destroy and re-create later. You can add filtering and loading to controller which creates these views. And you can re-use same view/controller objects for different tabs if you want.
If your views are presenting one instance of an object (like one product is displayed on each tab) - don't attach stores to those views. Just pass them individual model (record).
I would recommend creating the stores that relate only to that view instance inside of the view's initComponent method.
Your controller's control handlers should be coded in a way that they can differentiate which view dispatched the event. This should not be too difficult because almost all view events contain a reference to the component which fired the event. You could then use the relative query selectors, e.g.: myEventFiringComponent.up('anotherComponent') or myEventFiringComponent.down('anotherComponent') to get a handle on a different component in the same view if you need to.
Please see this post for a full explanation.

Backbone.js navigation working once but not again

I'm using Backbone.js 0.9.2 from the Backbone-on-rails gem. I'm also trying to use the new 'pushState' instead of the old hash URL.
The Problem
I'm building a standard Rails like CRUD interface to keep track of my appointments. I have a 'new' link on the main index.jst.eco page:
<h1>Appointments</h1>
<p>New Appointment</p>
I load the page and click on that 'new' link and backbone fires off the event and doesn't have to reload the whole page. Here is that event:
class BackboneOnRails.Views.AppointmentsIndex extends Backbone.View
template: JST['appointments/index'],
events: ->
'click .new': 'newAppointment'
newAppointment: ->
Backbone.history.navigate("/appointments/new", {trigger: true})
return false
# The rest of the index methods omitted for brevity
This then invokes the backbone router:
class BackboneOnRails.Routers.Appointments extends Backbone.Router
routes:
'': 'index'
'appointments': 'index'
'appointments/new': 'new'
initialize: ->
this.appointments = new BackboneOnRails.Collections.Appointments()
this.appointmentsIndexView = new BackboneOnRails.Views.AppointmentsIndex({collection: this.appointments})
this.appointmentsIndexView.render()
index: ->
$("#container").html(this.appointmentsIndexView.el)
this.appointments.fetch()
new: ->
appointments = new BackboneOnRails.Collections.Appointments()
view = new BackboneOnRails.Views.AppointmentNew({collection: appointments})
$("#container").html(view.render().el)
The problem happens when I hit the browsers back button, then try using the 'new' link again. This time around it does a full reload of the page.
What is happening to the javascript bindings when I hit back on the browser?
I have a show event for the item and with that I can go back and forth no problem. I've compared both and they look like the same sort of calls.
The problem is in your attempted re-use of the appointmentsIndexView instance. Removing the view from the DOM destroys the DOM event handlers. Re-adding the view's el to the DOM does not re-connect them.
Overview Of The Problem
When you load that view with the initialize and index methods of your router the first time, everything is fine because you have a fresh instance of the IndexView. The DOM events are attached to the view properly, and life is good.
When you hit the new route / method of your router, you're effectively trying to remove the index view from the screen and replace it with the add new view. This works from a visual stand point and from the standpoint of the add new view.
When you hit the back button, though, you're staying within the same live application instance in your browser tab. Hitting the back button with pushstate enabled tells the browser not to reload the entire app, just to update the url and fire off the router method for the index.
In this case, you're index view is not re-built from the ground up. You're re-using the same view instance, but re-loading it with data from the server. The data load works perfectly fine because your view and collection are still attached. The DOM event bindings fail, however, because they bindings were previously removed and not re-added.
2 Common Solutions
There are two common solutions for this, and many variations of these solutions.
1) Don't re-use view instances.
This is my strongly recommended suggestion. In every instance where I have tried to re-use a view instance, I have consistently run into very large problems - including the exact problem you're having.
Instead, re-create a new view instance every time you need to show the index of appointments. That means you create the new index view in your index method of the router, instead of the initialize method.
2) Clear and re-bind the DOM events
If, for some reason, you feel that you really need to re-use the view instance (which should never be true), you can solve it with some information that Tim Branyen posted on his blog a while back:
http://tbranyen.com/post/missing-jquery-events-while-rendering
I do not recommend this approach. Re-using a view instance might seem like a good idea off-hand, but it will lead down a bad path toward other problems, including bloated memory usage by leaving too many unused parts around in your app.
Side Note: Zombies And Memory Leaks
In either case - whether you decide to re-use view instances or re-create them when you need them - you're likely to run in to some memory leaks.
In the case of re-using a view, you're explicitly holding on to an object in memory when you don't need to. This isn't really a "leak" but it's an excessive use of memory. You should de-reference the object when it's not needed and re-create it when it is needed. This will cut down on memory usage and allow your app to perform better.
I have a blog post covering how this works, here: http://lostechies.com/derickbailey/2012/03/19/backbone-js-and-javascript-garbage-collection/
In the case of not re-using a view, you may wind up with a true memory leak by leaving model and collection event bindings hanging around after a view has been removed from the visible DOM. If you decide not to re-use your views, you will need to make your code that replaces the #container html more robust, and have it clean up the old view.
I've got a blog post detailing a solution for that, as well: http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/ - be sure to read the comments from Johnny Oshika in this post, as he points to a very useful StackOverflow answer where he shows a simple method of handling model and collection events.

Backbone.js - View in Collection adding item to another (sibling) Collection

In my example I have a parent model (ModelParent) with two collections (A and B), which hold ModelA's and ModelB's respectively. There are associated views for each model (ViewParent, ViewA, and ViewB)
I would like a function in ViewA to add a new item to CollectionB.
What's the best way to do this? (couple of possibilities below):
Should ViewA be passed a reference to ViewParent, when it is created? How best to do this? (as far as I know there is now in build parent reference)
Should ViewParent be stored in the window scope, so that ViewA can refrence it like window.ViewA? (This seems wrong to me)
Another alternative than listening directly to events from your collection, is the use of an eventBus. Derick Bailey from Los techies wrote a nice article where he introduces the idea of communication between different components (in his case views) via an eventBus.
If you are firm with coffeescript - and possibly even if you're not - you should also check this nice extension from Adam Thurlow.
Side note: If your situation simply requires communication between the two elements an eventBus is most likely overkill, although the idea is imho ultra simple. Apart from that, I am of the believe, that a central component for communication is worth the effort as it simplifies messaging, supports decoupling and gives your architecture a reliable consistency.
I would consider thinking about it a different way. I believe it would be cleaner if you didn't have to pass references all around your views. Instead, use backbone's built in event model and keep the "add new item to CollectionB" logic inside ViewParent. When ViewParent instantiates ViewA, you could immediately bind to an event on it:
this.viewA = new ViewA({});
this.viewA.bind("some_event_that_requires_adding_to_collection", this.onViewAEvent);
Inside ViewA, whenever you want to add to CollectionB, just trigger the event:
this.trigger("some_event_that_requires_adding_to_collection", itemIWantToAdd);
Add additional arguments to the trigger call to pass them to any callback bound to the event.

Preferred way of creating links with backbone.js

I'm trying to wrap my head around backbone.js but I'm finding it hard due to the lack of (IMO) good examples.
First of all, what is the best way of getting a link to an object.
If I want to get the edit url of an Album model I could do album.url() + '/edit', is this really the best way?
Also, I'm trying to make my application work 100% without javascript so I don't want my URLs/links to say /albums/#1/edit, I want it to be /albums/1/edit and override this in JS.
I'm thinking I create normal URLs and use jQuery.live to call router.navigate in backbone.js
I never got this to work however, when I call router.navigate('/albums/2', true) the URL changes but my show action is never called. If I refresh it's called so the route is matched.
What am I missing?
The basic answer, which is kind of frustrating, is "there is no preferred way!". Backbone.js doesn't tell you how to set up links, you can do it any way you like. I found this flexibility just as annoying as you do, at least at first.
So here's the way I'm approaching this on my current project, with the (big) caveat that this is just one of many ways to do things in Backbone:
For the most part, I don't use actual links. There's no explicit reason not to, but it means you have to keep track of a bunch of URL strings that have to be consistent. I would rather stick all the URL formatting in my routers and not deal with it elsewhere.
To open a new "top-level" view, like an editing screen, I set something that fires an event. In the application I'm currently working on, I have a global State model, and to open a new view I call state.set({ topview: MyTopView }). This causes the state object to trigger change:topview.
Any piece of the UI that needs to change when the top-level view changes has an update method bound to change:topview. When the event fires, they look at state.get('topview') and update as necessary.
I treat my routers as only marginally specialized parts of the UI - they're essentially views that render in the browser address bar, rather than the window. Like other views, they update the state object on UI events (i.e. a new URL), and like other views, they listen to the state object for changes that cause them to update. The logic that the editing screen has the URL albums/<albumid>/edit is fully encapsulated in the router, and I don't refer to it anywhere else.
This works well for me, but it adds an entirely new pattern, the global State object, to the Backbone structure, so I can hardly call this the "preferred" approach.
Update: Also note that .url(), in the Backbone idiom, refers to the model's URL in the back-end API, not the front-end URL (it's not like Django's get_absolute_url). There is no method in the default Backbone setup that gives you a user-facing URL for your model - you'd have to write this yourself.
Also, I'm trying to make my application work 100% without javascript; so I don't want my URLs/links to say /albums/#1/edit, I want it to be /albums/1/edit and override this in JS.
you can do exactly this w/ pushState. just enable it in your Backbone.history.start call:
Backbone.history.start({pushState: true})
this tells Backbone to use the HTML5 History API (a.k.a. "PushState"), which uses full URLs exactly like you're wanting.
read up on the history api here: http://diveintohtml5.ep.io/history.html
and I wrote up a 2 part series on using pushstate w/ the second part focusing on progressive enhancement in backbone, to do what you're needing:
http://lostechies.com/derickbailey/2011/09/26/seo-and-accessibility-with-html5-pushstate-part-1-introducing-pushstate/
and
http://lostechies.com/derickbailey/2011/09/26/seo-and-accessibility-with-html5-pushstate-part-2-progressive-enhancement-with-backbone-js/
hope that helps :)

Resources