we're pretty new to backbone and building a new app with it.. we're using the standard namespacing ie:
(function($) {
window.Lara = {
Models: {},
Collections: {},
Views: {},
Events: {},
Templates: {}
};
var vent = _.extend({}, Backbone.Events);
}(jQuery));
We need to keep state of certain views so that they're accessible across the different models and views, my question is where should instances be held of all the models and views etc... I'm finding it hard to keep things within scope between all the events and different views so I could just put all the needed global instances somewhere within the App namespace... is this the right approach?
I feel like the defined Lara.Models, Lara.Views etc should be kept clean and as a template for instances... should I just create an Lara.Instances and dump them all in there?
Any suggestions would be great here!
Not quite sure about your questions, you need refs to your existing views/models?
I would keep an reference for important models like UserSession in the Application scope, for your case, it would be something like
Lara.currentUser = new Lara.Models.UserSession() # Set it when user logs in
Is this something you are looking for? Leve me the comment
Related
I am in doubt if the following design pattern would cause a memory leak.
I have been using it for some time with success, but I haven't seen this pattern used by others, so I'd like some confirmation if you see something wrong with it.
As from next month I have to start working on a large project, and I want to know for sure that I can use this without problems, or if I should use another strategy.
controller.js:
var Controller = function(options){
};
Controller.prototype.makeView = function(options){
options.controller = this;
options.otheroption = options.otheroption;
var view = new View(options);
};
Controller.prototype.getModel = function(options){
//--- Get model ---
var model = new Model();
var promise = model.fetch();
return promise;
});
view.js:
var View = Backbone.View.extend({
initialize: function(options){
this.controller = options.controller;
this.otheroption = options.otheroption;
},
getModel: function(){
var promise = this.controller.getModel();
promise.done(_.bind(function(model){
//Do something with the returned model instance
}, this));
};
});
Instantiate controller, eg. from the router, or another controller:
//--- Instantiate the controller and build the view ---//
var controller = new Controller();
controller.makeView(options)
To me, this doesn't look like a circular reference, because both the controller and view are declared as a local variable.
Yet the instantiated view can access the controller functions, which allows me to isolate the RESTful server interactions via models / collections that the view uses.
For me it would seem as if the only reference remaining would be the view that keeps a reference to the controller object.
What I do afterwards is clean up the view (I destroy the instance and its references when I don't need it anymore.
Your opinion on this pattern is highly appreciated.
My purpose is to isolate creation of views / server interactions in separate controller files: if you see holes in my method and have a better way of doing it, please share.
Thanks.
Short answer: There is no memory leak problem in the code you have posted. The view holds a reference to the controller, but not vice versa. So as long as the controller lives longer than the view, that reference does not keep your objects from being garbage-collected. I don't see a circular reference anywhere in your code.
Longer answer: The pitfalls would be in the code you haven't posted. In particular, any event handlers in your view must be cleaned up properly, otherwise your views never fade into oblivion. But you have said in your question that you clean up your view, so I guess you are aware of that sort of problem.
What controller doing is here looks like a utility to me. Could have been easily managed by a global level singleton. I see some issues in first glance.
Code repetition, assuming you would creating separate Controller for different types of Models and Views, makeView and getModel code needs to be repeated for each controller. If you extending from a BaseController, then you need to pass View and Model Class to getModel and makeView functions.
How do you handle a use-case where you have to use same model in different Views?
makeView and getModel is designed assuming for each makeView there would be a getModel call, in assumed order
I would rather write a utility function which can create and deploy views for me.
var deployView = function(view, config){
//do the view rendering
view.render();
view.$el.appendTo(config.el);
}
var createView = function(config) {
var view;
var viewType = 'model';
if (config.collection || config.Collection) {
viewType = 'collection';
}
if (viewType === 'model') {
if (config.Model) {
config.model = new config.Model(config.modelAttributes);
//fetch if needed
}
} else {
if (config.Collection) {
config.collection = new config.Collection(config.items);
//fetch if needed
}
}
var filteredConfig = _.omit(config, 'Collection', 'Model', 'View');
view = new config.View(filteredConfig);
deployView(view, filteredConfig)
}
JavaScript implementations haven't had a problem with circular references for a long time. (IE6 did have a memory leak from circular references if I recall correctly, which wasn't shared by any other major browser from that period.)
Modern JavaScript implementations perform garbage collection through a "mark and sweep" algorithm. First they scan through your web app's entire memory structure starting from the global object, and mark everything they find. Then they sweep through every object stored in memory and garbage collect anything that wasn't marked. As long as there isn't a reference to your object from the global object or any stored function, it can be garbage collected.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management#Mark-and-sweep_algorithm
You're probably thinking of a reference counting-based implementation, which does have issues with memory leaks from circular references. In that implementation as long as one object contained a reference to another, that second object can't be garbage collected. That method was once used in web browsers but not anymore.
Nowadays, most memory leaks are from globally-accessible objects you forget to clean up and accidentally retaining data in function closures (a function that creates another function and passes/saves it somewhere). Since the closure's local variables can be accessed by the function created inside of them, they have to be retained as long as that function exists.
So go ahead and add all the circular references you want. Unless you need to target IE6, your code's fine.
Consider the Backbone app listed below. When its started, it creates a restaurants collection and view of local restaurants in the users area. The user can later change their search area and then update the entire collection by submitting a search form.
The Problem: How can I reference the collection in the search method if it isn't related to the view?
Currently, I'm setting the restaurant collection and view as properties of the App view itself so that it can be easily referenced from within the method, but this seems in appropriate because the view isn't solely related to that collection.
Is it better to reference the collection as I have or is it better to reference it as App.collection, as backbone does when you in create views realted to collections with Backbone.View.extend({collection: mycollection})?
App view:
var App = new (Backbone.View.extend({
...
events: {
submit: "search",
},
start: function(){
App.render();
App.restaurants = new App.Collections.Restaurants();
App.restaurantsView = new App.Views.Restaurants({collection: App.restaurants});
App.restaurants.fetch({});
},
search: function(e){
e.preventDefault();
var payload = $( App.targets.searchForm ).serializeObject();
App.restaurants.fetch({data: payload, processData: true,});
},
...
}):
Those are big questions: How to structure a Backbone application, how to define and maintain module boundaries, and how to decouple modules while allowing notifications where needed, like between search and restaurants?
Big-picture answer: Look at something like Marionette. It's a framework built on top of Backbone, it makes it easy to follow a lot of best practices, and it does some heavy lifting for you. David Sulc has an excellent book about Marionette that covers all of those topics & more. Highly recommended.
Immediate-problem answer: Directly referencing objects outside of a single module (like search) is a bad idea. It couples together concerns that should be kept separate. At a minimum, think of adding an application-level event bus: search can trigger a search event, and the restaurant code can listen for that event and act on its collection in response. You could use something like the backbone.wreqr library's EventAggregator class, which is tailor-made for this kind of thing. In a pinch, you could use an instance of Backbone.Events to roll your own:
App.vent = _.extend( {}, Backbone.Events );
Trigger an event from search code like so:
App.vent.trigger('search', payload);
And in your start function, register to listen for that event:
App.vent.on('search', function(payload) {
App.restaurants.fetch({data: payload, processData: true,});
});
I'm really enjoying Marionette and the structure it adds to Backbone. However, I'm a little stumped with how to reuse a Module on a single page.
I have a Marionette Module that renders and handles events for a category tree. I would like to reuse this Module on the same page, that would display different collections in different regions in the page. I came to realize that the Marionette Modules are essentially singletons on the Application object, which is also a singleton. I apparently can't create a new instance of a Module to display and handle events for a new collection in a different region. Likewise, I can't register and trigger events on the Module, because there is only one and the events triggered in each region would need to be independent of each other.
Am I thinking about Modules incorrectly? How can they reused on the same page with different regions / collections?
Think of modules as namespaces. You would never try to duplicate a namespace, right? But it's very common to have a namespace expose public types that other parts of the code can create multiple instances of.
App.module('CategoryTree', function(self, App, Backbone, Marionette, $, _) {
self.Collection = Backbone.Collection.extend( ... );
self.CollectionView = Marionette.CollectionView.extend( ... );
...
});
//elsewhere...
var workingCollection = new App.CategoryTree.Collection( ... );
var workingView = new App.CategoryTree.CollectionView( ... );
var previewCollection = new App.CategoryTree.Collection( ... );
var previewView = new App.CategoryTree.CollectionView( ... );
EDIT I figured out what I don't like about this... If you need a show an unknown number of category trees (in response to an AJAX call for instance) you are dead in the water.
I found one potential solution that seems to be working. Basically, a Module definition is just a function. Instead of only using that function when adding a Module to the Application, I define the function as-is somewhere else in the global scope so I can get to it.
Project.Modules.CategoryTree = function(self, App, Backbone, Marionette, $, _) {
// ... module definition goes here ...
}
Any time I want to use this module, I essentially add it as a submodule to an existing Module or Application. Like so...
App.module("WorkingTree", Project.Modules.CategoryTree)
App.module("PreviewTree", Project.Modules.CategoryTree)
While this does reuse the module code by creating two distinct instances, it doesn't smell quite right to me. Is there a better solution out there?
I've seen backbone views (or models, collections etc), declared like this
var SomeView = Backbone.View.extend({...
I've also seen them declared like this
window.SomeView = Backbone.View.extend({...
Could someone please explain the pros/cons in each case?
Really it does the same thing: http://snook.ca/archives/javascript/global_variable
However http://documentcloud.github.com/backbone/#View-constructor example goes with the first example and would be understood by a larger audience.
Some people prefer to have only one global variable (important for libraries, not so much for normal pages) and use something like:
var MyApp = {
Models: {},
Collections: {},
Views: {}
}
and then for each view:
MyApp.Views.SomeView = Backbone.View.extend({...
Recently I'm having an argument with some co-workers about something that I
find incorrect.
We're using Backbone in a large application and my way to create views is
the 'standard' backbone way :
var MyView = Backbone.View.extend({
className: 'foo',
initialize: function() {
_.bindAll(this, 'render' /* ... more stuff */);
},
render: function() {
/* ... render, usually
using _.template and passing
in this.model.toJSON()... */
return this;
}
});
But someone in the team recently decided to do it this way :
var MyView = Backbone.View.extend( (function() {
/* 'private stuff' */
function bindMethods(view) {
_.bindAll(view, /* ... more stuff */);
};
function render(view) {
/* ... render, usually
using _.template and passing
in view.model.toJSON()... */
};
return {
className: 'foo',
initialize: function() {
bindMethods(this);
render(this);
}
};
}());
That's the idea in pseudo-code .
Having read the BB source and read tutorials, articles I find this to be a
bad practice (for me it makes no sense), but I'd love some feedback from
other Backbone developers/users
Thanks in advance
One benefit I see from using the closure is providing a private scope for variables and functions that you don't want to be accessible from code outside the view.
Even so, I haven't seen many Backbone apps use a closure to define a view/model/collection etc.
Here's an email from Jeremy Ashkenas concerning this issue as well.
Yes, using closures to create instances of objects with private variables is possible in JavaScript. But it's a bad practice, and should be avoided. This has nothing to do with Backbone in particular; it's the nature of OOP in JavaScript.
If you use the closure pattern (also known as the "module" pattern), you're creating a new copy of each function for each instance you create. This completely ignores prototypes, and is terribly inefficient both in terms of speed and especially in terms of memory use. If you make 10,000 models, you'll also have 10,000 copies of each member function. With prototypes (with Backbone.Model.extend), you'll only have a single copy of each member function, even if there are 10,000 instances of the class.
I totally agree with Paul here. Sometimes you may find it necessary to define methods and properties that are private and can't be messed with from outside. I think it depends wether you need this scoping mechanism in your class or not. Mixing both approaches, with respect to the requirements you have for the class wouldn't be so bad, would it?