Backbone Zombie View Solution - backbone.js

So, I have been working with Backbone for a while without any major issue. Though I have read a lot about the problem of Zombie Views and the memory leaks they generate. After some research I implemented a way of displaying the views that, hopefully, will prevent this issue. I have currently two apps working with this method and all seems to be going well but I would like to be sure that this method works.
All these functions are working on a main application object called "App" where the namespace for the app is defined and the application is initialized and then started.
Firs of all, my current solution works like this: when a Backbone router catches a url it instantiate all the models, collections and views that are needed to display that page but the router doesn't render the view. All it does it's append the view to a "contentView" array inside the App object and it delegates the rendering of the page also to App using the App.setAndRenderContentViews function.
On the App object I have two arrays containing all the views that are currently active. They are called "contentViews" and "appendedViews". On the first one are the main Views who may have some "appended Views" to display some models or collections. These last Views are stored in the appendedViews array.
Every time the router calls the App.setAndRenderContentViews functions, the applications run a series of methods to correctly close all the "oldViews" and then render the new Views. So, here is the code I'm using so far:
window.App =
Models: {}
Collections: {}
Views: {}
Routers: {}
contentViews: []
appendedViews: []
setContentViews: (views) ->
#closeViews() unless #contentViews == []
#closeAppendedViews() unless #appendedViews == []
for view in views
#contentViews.push view
return #contentViews
closeViews: ->
for oldView in #contentViews
#closeView(oldView)
#contentViews = []
closeAppendedViews: ->
for oldView in #appendedViews
#closeView(oldView)
#appendedViews = []
closeView: (view) =>
view.unbind()
view.remove()
view.model.off unless view.model == undefined
view.collection.off unless view.collection == undefined
renderContentView: (view) =>
$('#content-layout').append(view.render().el)
renderContentViews: (renderViews) ->
for view in renderViews
#renderContentView(view)
return 1
setAndRenderContentViews: (views, everybody = false) ->
if everybody == false
return Backbone.history.navigate('', trigger: true) unless #session.load().authenticated()
renderViews = #setContentViews(views)
#renderContentViews(renderViews)
pushToAppendedViews: (view) ->
#appendedViews.push view
I don't know if this is a good way to handle this problem. Also, maybe there is a better way of handling the Zombie Views. I hope one of you can help me with this. Thank you!

Related

Marionette 2.0 - Uncaught ViewDestroyedError: View has already been destroyed and cannot be used.

Struggling a bit to get my head round a problem in Marionette 2.
In various parts of my app I have the following trigger so when the currency is changed the views are rendered.
App.on("currency:change", function() {
cartView.render();
totalsView.render();
});
This works fine on the individual pages if loaded by refreshing the page, but on navigating between 2 pages with this I get the error.
Uncaught ViewDestroyedError: View has already been destroyed and cannot be used.
On investigation it looks it's trying to render a view from the previous page.
Each page is defined by a module which is started and stoped as loaded or closed, and the views seem to being destroyed, so a bit confused as to how this is happening. My first assumption is the views from the previous page haven't been fully removed, but I thought this was something Marionette did automatically.
I looks like you are using references to views that have been already removed. My first assumption is that you should recreate views:
App.on("currency:change", function() {
cartView = new CartView();
totalView = new TotalView();
cartRegion.show(cartView)
totalRegion.show(totalView);
});
I'm not sure how your application works but I can guess that you have something like currencyCollection or currencyModel. If yes your views should listen to changes on that model and refresh themselves and then "currency:change" event will be not needed anymore.

What's a good Backbone pattern for managing model instances?

I'm trying to minimize server calls by avoiding any requests I can.
Let's say, for the sake of a example, I have a collection of Matchboxes which belong to Users and have Tags assigned, and then also have a collection of Tags and a collection of Users as part of other pages. Getting matchboxes retrieves the user and tag info, so that I can instantiate all required models with one request, accessing the Tags and Users pages retrieves similar collections (only they deal only with their respective models).
My problem: if matchboxes is one page, and tags and users are two other pages, what's a good way to make sure only one model is ever instantiated for any given entity, ie. if I go into users or tags and edit an entry associated with a matchbox the matchbox entry should have the same entry assigned allowing it to listen and react to the updates with out requiring sending requests when going back to the matchbox page in the example.
I've looked over Backbone.relational but it doesn't seem to do what I need, and would rather not wall myself into a framework. So solutions involving patterns are preferable.
Ended up using http://pathable.github.io/supermodel/ which uses the pattern of overwriting the model attribute on collections with a custom function which calls a special Model.create that itself returns an existing (updated with the new values if necessary) instance of said model. The Model.create call has to be used everywhere else in code for unique models.
So essentially every model has a all() method which is a collection of all instances by id. Whenever a model is added it checks it against the collection and returns an existing object if it exists; the data used to instantiate the duplicate is used to update the existing object ensuring data is not stale (which is a nice bonus to the uniqueness I wanted).
The cleanest method seems to be to just wrap the model function into a function that returns it for clearer use; then for every collection that needs to have unique models wrap said model in the function. I came up with this at the moment:
app.single = function (modelPrototype) {
return function (attrs, options) {
return modelPrototype.create(attrs, options);
};
};
(app there is just a scope global, tied to a particular namespace)
So in collections instead of,
model: app.Model
I would then use
model: app.single(app.Model),
Whenever I update a entry in one part of the application the change will trickle down to every other collection/model since if it's the same instance from the user's perspective it's the same instance in code too.
That's about all I could tell from reading the pattern though the code and documentation. Which is sufficient for my own uses.
I suspect this solution would still have some issues if you're caching renders but I haven't found a use for that (prefer to re-render whenever I can to avoid dealing with various artifacts) so it's all good for me.
Unfortunately the codebase seems to be partially abandoned, so while it works with Backbone 1.0.0 (as far as unique models go), I may need to re-create/fork the pattern in future projects.
I think you should think twice about nesting your models and collections in this way, especially if it's primarily for the purpose of easing the bootstrapping of your app. Instead, try to use id's for inter-referencing between models as much as possible. This design problem you have is most likely only the first of many to come if you structure your model/collection tree in a certain way now, only to find it too inflexible later.
That being said, If all you need is for models referencing other models/collections to be able to refer to the same model/collection instance, then simply instantiating them during bootstrap and passing them in to their respective parent models would be sufficient. You could either load some bootstrap data in one request, or preferably inline that data in the HTML:
<script>
var bs_data = {
users : [
...
],
tags : [
...
],
matchboxes : [
...
]
};
</script>
And then instantiate the corresponding models or collections using the bootstap data.
var matchboxes = new Matchboxes();
matchboxes.set(bs_data.matchboxes);
var users = new Users({matchboxes:matchboxes});
users.set(bs_data.users);
The bootstrap data would come from the same backend so your models and collections would already be in sync without having to fetch anything.
As for design patterns; passing dependencies as constructor arguments is actually the dependency injection pattern, albeit more automated solutions to do so exist.
To make sure only one model is ever instantiated, and it is shared among the other elements that use it, being able to listen and update when any of the elements make a change to it, you can use a Singleton pattern. You can read more about it here
If you use Requirejs you can get same effect if you always return the model instantiated. For example:
// the shared model
define([
'jquery',
'underscore',
'backbone'
], function ($, _, Backbone) {
'use strict';
var Model = Backbone.Model.extend({
// ...
});
// return instantiated, so we'll get the same object back whenever we use this model (singleton)
return new Model();
});
// a view using the model
define([
'jquery',
'underscore',
'backbone',
'model'
], function ($, _, Backbone, modelInstance) {
'use strict';
var View = Backbone.View.extend({
initialize: function () {
// listen to what other elements do
this.listenTo(modelInstance, 'eventFromOtherElement', this.doSomething);
// when this element does something, other elements should be listening to that event
modelInstance.trigger('thisViewEvent');
},
doSomething: function () {
// ...
}
});
return View;
});

Whats the Advantage of Marionette AppRouter+Controller over Backbone.Router?

From my understanding, the differences is the callback functions to events on an AppRouter should exist in the Controller, instead of the same Router object. Also there is a one-to-one relationship between such AppRouter & Controllers, all my code from Router now moves to Controller, I don't see too much point of that? So why use them? I must be missing something?
The way I see it is to separate concerns:
the controller actually does the work (assembling the data, instanciating the view, displaying them in regions, etc.), and can update the URL to reflect the application's state (e.g. displayed content)
the router simply triggers the controller action based on the URL that has been entered in the address bar
So basically, if you're on your app's starting page, it should work fine without needing any routers: your actions (e.g. clicking on a menu entry) simply fire the various controller actions.
Then, you add on a router saying "if this URL is called, execute this controller action". And within your controller you update the displayed URL with navigate("my_url_goes_here"). Notice you do NOT pass trigger: true.
For more info, check out Derick's blog post http://lostechies.com/derickbailey/2011/08/28/dont-execute-a-backbone-js-route-handler-from-your-code/ (paragraph "The “AHA!” Moment Regarding Router.Navigate’s Second Argument")
I've also covered the topic in more length in the free preview of my book on Marionette. See pages 32-46 here: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf
I made some override for the router. And currently use it in this way (like Chaplin):
https://gist.github.com/vermilion1/5525972
appRoutes : {
// route : controller#method
'search' : 'search#search'
'*any' : 'common#notFound'
},
initialize : function () {
this.common = new Common();
this.search = new Search();
}

How can I run the same setup function on every view?

Many of the views in my application need to be "collapsible". To the user this means that you can click an arrow to collapse or expand the view's contents.
When creating a view I need to be able to easily say, "This view should be collapsible," and then run the appropriate setup code (which essentially means adding the .collapsible class to the view's wrapper and inserting a dom element that looks like this: <div class="toggle"></div>
Suggestions on ways to pull this off seamlessly? I'm currently using Backbone, Backbone.Marionette, and Underscore.
I do this with another application that doesn't use Backbone. In that application every action results in a page refresh, so I just use jQuery to look for all elements with the .collapsible class and do my setup that way.
EDIT:
I'm using Backbone.Marionette.CompositeView for these particular views, if that helps.
I've done similar thing in my project by extracting such functionality into mixins. There're different approaches to implementing mixins in Backbone. Take a look here or here
You can create parent view that extends from Marionettes compositeView and add your common functionallity there, and have your project views extend from this parent view.
var CollapsibleView = Backbone.Marionette.CompositeView.extends({
variable1: 1,
var2: true,
initialize : function() {
// your code here
},
helperfunction : function () {
// other helpful function
}
});
var MySpecificView = CollapsibleView.extends({
mySpecificFunction : function () {
// some specificView functionality
}
});
var myProjectView= new MySpecifcView();
myProjectView.helperfunction(); /// function from the parent
myProjectView.mySpecificFunction(); /// function from the specificView
/// you also have the functionality added on the initialization of the collpasibleView

Backbone Marionette using Require.js, Regions and how to set up

I'm currently writing a Backbone Marionette app which ultimately amounts to about 6 different "screens" or pages which will often times share content and I am unsure of how to best structure and access Regions.
I am using the app/module setup described here: StackOverflow question 11070408: How to define/use several routings using backbone and require.js. This will be an application which will have new functionality and content added to it over time and need to be scalable (and obviously as re-usable as possible)
The Single Page App I'm building has 4 primary sections on every screen: Header, Primary Content, Secondary Content, Footer.
The footer will be consistent across all pages, the header will be the same on 3 of the pages, and slightly modified (using about 80% of the same elements/content) on the remaining 3 pages. The "morecontent" region will be re-usable across various pages.
In my app.js file I'm defining my regions like so:
define(['views/LandingScreen', 'views/Header', 'router'], function(LandingScreen, Header, Router) {
"use strict";
var App = new Backbone.Marionette.Application();
App.addRegions({
header: '#mainHeader',
maincontent: '#mainContent',
morecontent: '#moreContent',
footer: '#mainFooter'
});
App.addInitializer(function (options) {
});
App.on("initialize:after", function () {
if (!Backbone.History.started) Backbone.history.start();
});
return App;
});
Now, referring back to the app setup in the aforementioned post, what would be the best way to handle the Regions. Would I independently re-declare each region in each sub-app? That seems to be the best way to keep modules as independent as possible. If I go that route, what would be the best way to open/close or hide/show those regions between the sub-apps?
Or, do I keep the Regions declared in app.js? If so, how would I then best alter and orchestrate events those regions from sub-apps? Having the Regions defined in the app.js file seems to be counter-intuitive to keeping what modules and the core app know about each other to a minimum. Plus, every example I see has the appRegions method in the main app file. What then is the best practice for accessing and changing those regions from the sub-app?
Thanks in advance!
I actually have a root app that takes care of starting up sub-applications, and it passes in the region in which they should display. I also use a custom component based off of Backbone.SubRoute that enables relative routing for sub-applications.
check out this gist: https://gist.github.com/4185418
You could easily adapt it to send a "config" object for addRegions that defines multiple regions, instead of the region value I'm sending to the sub-applications' start method
Keep in mind that whenever you call someRegion.show(view) in Marionette, it's going to first close whatever view is currently being shown in it. If you have two different regions, each defined in its own app, but both of which bind to the same DOM element, the only thing that matters is which region had show called most recently. That's messy, though, because you're not getting the advantages of closing the previous view - unbinding Event Binders, for example.
That's why, if I have a sub-app that "inherits" a region from some kind of root app, I usually just pass in the actual region instance from that root app to the sub-app, and save a reference to that region as a property of the sub-app. That way I can still call subApp.regionName.show(view) and it works perfectly - the only thing that might screw up is your event chain if you're trying to bubble events up from your region to your application (as the region will belong to the root app, rather than the sub-app). I get around this issue by almost always using a separate instance of Marionette.EventAggregator to manage events, rather than relying on the built-in capabilities of regions/views/controllers/etc.
That said, you can get the best of both worlds - you can pass the region instance into your sub-app, save a reference to it just so you can call "close", then use its regionInstance.el property to define your own region instance pointing to the same element.
for(var reg in regions) if regions.hasOwnProperty(reg) {
var regionManager = Marionette.Region.buildRegion(regions[reg].el,
Marionette.Region);
thisApp[reg] = regionManager;
}
It all depends on what your priorities are.
I personally prefer to use the modules in my Marionette application. I feel it removes the complexity that require.js adds to your application. In an app that I am currently working on, I've created one app.js file that defines my backbone application but I am using a controller module that loads my routes, fills my collections and populates my regions.
app.js ->
var app = new Backbone.Marionette.Application();
app.addRegions({
region1: "#region1",
region2: "#region2",
region3: "#region3",
region4: "#region4"
});
app.mainapp.js ->
app.module('MainApp', function(MainApp, App, Backbone, Marionette, $, _) {
// AppObjects is an object that holds a collection for each region,
// this makes it accessible to other parts of the application
// by calling app.MainApp.AppObjects.CollectionName....
MainApp.AppObjects = new App.AppObjects.Core();
MainApp.Controller = new Backbone.Marionette.Controller.extend({
start: function() {
// place some code here you want to run when the controller starts
} //, you can place other methods inside your controller
});
// This code is ran by Marionette when the modules are loaded
MainApp.addInitializer(function() {
var controller = new MainApp.Controller();
controller.start();
});
});
You would then place your routes inside another module that will be accessed in the controller.
Then in the web page, you would start everything by calling.
$(function () {
app.start();
});
Marionette will automatically run and load all of your modules.
I hope this gets you started in some direction. Sorry I couldn't copy and past the entire application code to give you better examples. Once this project has been completed, I am going to recreate a demo app that I can push to the web.

Resources