Backbone general router vs. separate routing files? - backbone.js

I found that most tutorials use one big router.
For example: https://github.com/thomasdavis/backboneboilerplate/blob/gh-pages/js/router.js
Wouldn't it be better to separate the routes (controllers) into separate files?
If yes how can I combine this with requirejs?

I think this is a question of preference. If you're doing a ginormous application with gazillion routes, then dividing your routers up is sensible. For small applications having just one big router is just fine.
If you decide to have multiple routers, make sure you don't have conflicting routes, so there won't be any unexpected behavior or errors.
So with requireJS: I think the best way would be to define each router in it's own file like this
define([blaa, blaa], function(Blaa, Blaa) {
var SubRouter1 = Backbone.Router.extend({
// work your routing magic here, remember to make no conflicting routes
});
return SubRouter1;
});
When you have all your desired routers set up you can bundle them up in the app.js
define([...,'subrouter1', 'subrouter2', ... , 'subrouterN', ...],
function(..., SubRouter1, SubRouter2, ... , SubRouterN, ...) {
// work your app magic here
initialize: function() { // or wherever you start your application
subrouter1 = new SubRouter1();
subrouter2 = new SubRouter2();
...
...
subrouterN = new SubRouterN();
Backbone.history.start(); // remember to start the history
},
// maybe work some more magic?
});
I've never done this myself, but I don't see why it wouldn't work if you keep the routes from conflicting. Hopefully this clears stuff for you.

check Backbone.js "fat router" design conundrum out : you can find #jakee answer there and some more options

Related

Multilingual with multiple app and 1 translationService

I'm currently make a html page by using angularjs.
I have 1 html page, with 1 sidebar, 1 navigation bar and 1 content area.
Like this : AdminLTE
I've follow this instruction : this
And successfully, my app works ok.
But I don't know how to apply multilingual function to my every app.
For example : Navigationbar is 1 app, sidebar is 1 app and main content is 1 app.
How can I apply 1 translationService to 'em without downloading json again and again ?
Can anyone help me please ? Thank you.
I still think it would be better to have one application for the whole page, but have separate controllers for the nav, sidebar, and main content. That way they all work separately, but you don't have the awkwardness of dealing with separate apps. The only reason I can think of to have separate apps is if you want to make sure that services are NEVER shared between the different parts. However, in your case, it looks like you WANT to share the translate service, so I think it makes sense to use one app.
If you really want to have multiple apps, it is still possible. You can load the translations asynchronously, then when this is done, you call angular.module() for each app and inject the translations as a constant. Then, when you configure the translate provider, you can inject your translation constant just like you would inject any service.
I have done this in an application before, but I don't have access to the code right now. I did it for a single application, but you can easily extend it to multiple applications. I believe it looked similar to this:
var $http = angular.injector().get('$http');
var $q = angular.injector().get('$q');
var promises = [
$http.get('path/to/translations/en.json'),
$http.get('path/to/translations/fr.json'),
];
$q.all(promises)
.then(function(translations) {
angular.module('app', [])
.constant('translations_en', translations[0])
.constant('translations_fr', translations[1])
.config(['$translateProvider', 'translations_en', 'translations_fr',
function($translateProvider, translations_en, translations_fr) {
$translateProvider.translations('en', translations_en);
$translateProvider.translations('fr', translations_fr);
}]);
angular.bootstrap(element, ['app']);
});
For multiple apps, you would need to run the angular.module block once for each app.
Alternatively, you could define separate modules for each part, then define a parent module that depends on the other mini-modules, i.e.
angular.module('navigation', []);
angular.module('sidebar', []);
angular.module('mainPage', []);
angular.module('app', ['navigation', 'sidebar', 'mainPage']);
angular.bootstrap(element, ['app']);
I believe that all modules would share the same translate service in this case.

the role of backbone router

This is a quote from Derick Baileys blog ( creator of Backbone.Marionette):
But a router should never instantiate a view and manipulate the DOM
directly with jQuery or placing the view in to the DOM
But I keep seeing code like this:
var Router = Backbone.Router.extend({
routes: {
"":home
},
home: function(){
// instance of a view object in a router
var homeView = new HomeView({model:model});
// do something with homeView
})
Can this be considered anti-pattern, although it is widely used ?
From my perspective, it is a good idea to separate definiton from instantiation of objects (calling new HomeView() later). But does this is to be found in router ?
Someone else asked the same question in the comments of the article you are referring to. Here is the answer Derick gave to which I agree, I am copying it in order to keep things centralized.
These two examples are functionally the same:
example 1
Backbone.Router.extend({
routes: {
"foo": "showFoo"
},
showFoo: function(){
var fooView = new FooView();
fooView.render();
$("#someEl").html(fooView.el);
}
});
example 2
Backbone.Router.extend({
routes: {
"foo": "showFoo"
},
showFoo: function(){
FooApp.show();
}
});
FooApp = {
show: function(){
var fooView = new FooView();
fooView.render();
$("#someEl").html(fooView.el);
}
}
The difference between the two is coupling, cohesion, encapsulation, separation of concerns and the single responsibility principle. If I want to change how the FooApp shows the application's views and make things work, I shouldn't have to change the Router. These two concerns should be separated. I should have "one reason to change" for the router and for the FooApp (high level application object).
For a small demo app, #1 is ok. Anything more than 1 or 2 views, though, and the code becomes unmaintainable quickly. Having to sift through all of the cruft of doing the actual display so that you can see what the router is doing as a whole is a really bad idea in my experience.
For a larger example of this at play, check out my BBCloneMail sample project: http://github.com/derickbailey... - look at the BBCloneMail.Router.js file.
I prefer initialize all view only once in router.initialize() and then just render them in routing methods. But I don't think it make such a big difference.

DomReady with backbone.js

I am using backbone.js to create a single page app. I am new to backbone, so please forgive any wrong semantics.
My Problem is when rendering the views.
Initially, I have a javascript in my index.html that executes the some dom manipulation(image slider).
The JS is wrapped in $(window).load() so all is fine on initiation.
The code obviously doesn't execute unless the page is loaded from url. the code will not run from backbone views or router. So the page loads without the dom manipulation.
I have tried to insert my code into the render and initialize function in the view, but to no avail. Should I add this code to the router? that seems to be a bit of a hack.
Where should I include the "dom ready" code?
and / or is there a better way to manage views and their dom elements on load in backbone?
the code:
home.js
window.HomeView = Backbone.View.extend({
initialize:function () {
this.render();
},
render:function () {
$(this.el).html(this.template());
this.startOrbits();
return this;
},
startOrbits:function(){
$(window).load(function() {
$('#orbit-main').orbit({ fluid: '16x6', swipe:true });
$('#orbit-news').orbit({ fluid: '8x6', bullets: true, directionalNav:false, captions:true, advanceSpeed: 9000});
});
},
});
But when I go to another view, then back, the code obviously doesn't
excite
I'm not quite sure what that means. Leaving the "excite" part aside, you don't "go to" views; views are just ways of adding elements to the page, or adding logic to existing elements.
If I had to guess though, I'd imagine that you're using the Backbone router to move between virtual "pages" (and you use views to make those pages). If that's the case, you need to look at the Backbone router events:
http://documentcloud.github.com/backbone/#Router
http://documentcloud.github.com/backbone/#FAQ-events
Specifically, I think you want to bind an event handler (on your router) to "route:nameOfYourRoute", or just :route" (if you want to trigger your logic on every virtual page load).
Hope that helps, and if my guesses are wrong please edit your question to clarify.
I was able to find a solution.
After commenting out the if statement in my router function, things went smoothly.
home: function () {
// if (!this.homeView) {
this.homeView = new HomeView();
// }
$('#main-content').html(this.homeView.el);
this.homeView.startOrbits();
this.headerView.selectMenuItem('home');
},
I do realize that this means I create a new view on every rout trigger.
Please feel free to offer more optimal solutions.

Backbone history start not working

I am trying to start history with backbone however I get the error:
Cannot call method 'start' of undefined
Here is a link to the full code : http://pastebin.com/pNsYghgE
I have jquery, underscore, and backbone js include before this code so I would imagine this should work based off the documentation. I am using backbone 0.9.2. What am I doing wring here?
EDIT: ANSWER
I want not creating an instance of my routers so I added this code to before I called Backbone.history.start():
//initialize all routes
_(this.modules()).each(function(module, moduleName)
{
_(module.routers).each(function(router, routerName)
{
new router();
});
});
Backbone.history can only be started after one or more routers have been defined with routes:
http://backbonejs.org/docs/backbone.html#section-113
You can see here, that the Backbone.history object is created when routes are defined. I don't see any routers or routes being defined in the posted code, so I'm guessing that this is the problem.
When the line is executed Backbone is still not loaded..
Use the
$(function() {
// ...
});
For this part of the code as you have done for other blocks.
It seams okey, can you provide more code, and the part of the html you load jquery, undescore and backbone.
inspect the backbone object before: Backbone.history.start(this.options.historyOptions);
Did you create routers before trying to start the history? (Derick Bailey)

multiple matching routes

I've got a backbone.js application that defines two controllers, and the controllers both define route patterns which match the location.hash. I'm having trouble getting both of them to fire - e.g.
ManagerController = Backbone.Controller.extend({
routes: {
":name": "doStuff"
},
doStuff : function(name) {
console.log("doStuff called...");
}
});
Component1Controller = Backbone.Controller.extend({
routes: {
"xyz123": "doMoreStuff"
},
doMoreStuff : function() {
console.log("doMoreStuff called...");
}
});
so if the url is "http://mysite.com/#xyz123", then I am seeing 'doStuff()' called, or if I comment out that route, then 'doMoreStuff()' is called. But not both.
I'm using this architecture because my page is highly component oriented, and each component defines its own Controller. A 'component manager' also defines a Controller which does some house keeping on all routes.
Should I be able to configure two controllers that both respond to the same route? Cheers,
Colin
Short answer: No, you can't do that. One Controller per page.
Long answer: When you instantiate a new Controller, it adds its routes to the History singleton. The History singleton is monitoring the hash component of the URL, and when the hash changes, it scans the routes for the first expression that matches its needs. It then fires the function associated with that route (that function has been bound to the controller in which it was declared). It will only fire once, and if there is a conflict the order in which it fires is formally indeterminate. (In practice it's probably deterministic.)
Philosophical answer: The controller is a "view" object which affects the presentation of the whole page based on the hash component of the URL. Its purpose is to provide bookmark-capable URLs that the user can reach in the future, so that when he goes to a URL he can start from a pre-selected view among many. From your description, it sounds like you're manipulating this publicly exposed, manually addressable item to manipulate different parts of your viewport, while leaving others alone. That's not how it works.
One of the nice things about Backbone is that if you pass it a route that's already a regular expression, it will use it as-is. So if you're trying to use the controller to create a bookmarkable description of the layout (component 1 in the upper right hand corner in display mode "A", component 2 in the upper left corner in display mode "B", etc) I can suggest a number of alternatives-- allocate each one a namespace in the hash part of the URL, and create routes that ignore the rest, i.e.
routes: {
new RegExp('^([^\/]*)/.*$'): 'doComponent1stuff',
new RegExp('^[^\/]*/([^\/]*)\/.*$': 'doComponent2stuff',
}
See how the first uses only items after the first slash, the second after the second slash, etc. You can encode your magic entirely how you want.
I suggest, though, that if you're going to be doing something with the look and feel of the components, and you want that to be reasonably persistent, that you look into the views getting and setting their cookies from some local store; if they're small enough, cookies will be enough.
I have a very similar issue. At present, backbone stops after the first matching route. I have a dirty workaround where I am overriding the loadUrl method of Backbone History. Here I am iterating through all of the registered routes and triggering callback for all of the matching routes .
_.extend(Backbone.History.prototype, {
loadUrl : function() {
var fragment = this.fragment = this.getFragment();
var matched = false;
_.each(this.handlers, function(handler) {
if (handler.route.test(fragment)) {
handler.callback(fragment);
matched = true;
}
});
return matched;
}
})
Philosophically, I am fine with having single controller per page. However, in a component based view framework, it will be nice to have multiple views per route rendering different parts of a view state.
Comments are welcome.
I've used namespacing to deal with a similar problem. Each module comes with it's own module controller, but is restricted to handle routes that start with /moduleName/ this way modules can be developed independently.
I haven't fully tested this yet, if you take a look at the Backbone.js source, you can see this at line 1449:
// Attempt to load the current URL fragment. If a route succeeds with a
// match, returns `true`. If no defined routes matches the fragment,
// returns `false`.
loadUrl: function(fragment) {
fragment = this.fragment = this.getFragment(fragment);
return _.any(this.handlers, function(handler) {
if (handler.route.test(fragment)) {
handler.callback(fragment);
return true;
}
});
}
The any method will stop as soon as it matches a handler route (with the "return true"), just comment the return and the short-circuit will never happend, and all the handlers will be tested. Tested this with a marionette app with two modules, each one having it's own router and controller, listening same routes anb both fired up.
I think this is the simplest way of resolving it
routes: {
'': 'userGrid',
'users': 'userGrid',
}

Resources