How to define/use several routings using backbone and requirejs - backbone.js

I divided my app in several apps.
main.js
app.js
app1/
|- routing
|- controller
|- app
app2/
|- routing
|- controller
|- app
1) When I try to use the routers in app1, they work.
2) When I try to use the routers in app2, they don't work.
3) If I comment the line 'js/app1/routing', in main.js the routers in app2 work.
Why do I get this behaviour?
Is there some example of app using multiple routing and requirejs on github?
thanks.
Here's my code:
** main.js **
define([
'js/app',
'js/app1/routing', // the routers in this app work
'js/app2/routing' // the routers in this app do not work but
// if I comment the previous line (js/app1/routing',)
// they works
],
function (App)
{
"use strict";
App.initialize();
});
** app.js **
define([],
function ()
{
"use strict";
var app = new Backbone.Marionette.Application();
return app;
});
** app1/rotuing **
define(['backbone','app1/controller'], function(Backbone, controller)
{
"use strict";
var Router = Backbone.Marionette.AppRouter.extend({
appRoutes: {
'*defaults': 'index1'
}
});
return new Router({
controller: controller
});
});
** app2/routing.js **
define(['backbone','app2/controller'], function(Backbone, controller)
{
"use strict";
var Router = Backbone.Marionette.AppRouter.extend({
appRoutes: {
'app2': 'index2'
}
});
return new Router({
controller: controller
});
});

The problem is likely caused by the order in which the router files are loaded, and the routers are created.
Backbone's history object is responsible for executing routes. It collects all routes defined on all routers, when the routers are instantiated. Then it monitors the browser's url for changes. When it sees a change, it will take the first available matching route and fire that one route, skipping anything else.
When you have a *defaults route defined, everything matches this. Therefore, if this route is loaded first, nothing else will hit. So, you would need to be more explicit in your route parameters so that this one route doesn't hit all the time, or you would need to ensure that this router is loaded last.

Related

How to create a global state change handler with ui-router

I'm building my first angular app and many of the major features are separated into their own modules. Each module registers its routes in config with $stateProvider.
I want to be able to handle $stateChangeStart/$stateChangeError in one place for my whole app so I can evaluate authorization and handle errors. How do I do this?
Currently I have this handler registered on $rootScope on my main module/app but if I navigate to a route from another module and then navigate to another route it does not fire. Why? In fact even $urlRouterProvider.otherwise("xyz"); no longer works.
I don't like copy pasting code so any solution that avoids putting this logic in every module would be appreciated.
You can try something like:
var app = {};
// Route Config for a "some" module.
function someModuleConfig($stateProvider) {
$stateProvider.
state('something', {
url: '/something',
templateUrl: 'path/to/something/something.html',
});
}
// Module definition for "some" module.
app.somemodule.module = angular.module('app.somemodule', []).
config(someModuleConfig);
// Route Config for the main module.
function appConfig($stateProvider, $urlRouterProvider) {
// Set the default redirect for any missing route.
$urlRouterProvider.otherwise('xyz');
$stateProvider.
state('home', {
url: '/',
templateUrl: 'path/to/home/home.html'
});
};
// Module definition for the main module.
app.module = angular.module('myapp', [
'ui.router',
app.somemodule.name
]).config(appConfig);
The key is that you can register your routes with the main module, and subroutes can be registered in other modules. You could also register all your routes in the main app module as well, depending on how you structure your code.

Why is my $templateCache setup not working with new Angular router?

I cannot understand why my $templateCache setup is not working...
See this plnkr. I used it with the new Angular router, where I have my home component template in the $templateCache. The app.js file looks like this:
var app = angular.module('app', ['ngNewRouter','app.home']);
app.run(['$templateCache', function($templateCache) {
$templateCache.put('components/home/home.html', '<h2>The home page</h2>{{home.test}}');
}]);
app.controller('AppController', ['$router','$templateCache', AppController]);
function AppController($router,$templateCache) {
var vm = this;
console.log("The template looks like this:",$templateCache.get('components/home/home.html'));
$router.config([{
path: '/',
component: 'home'
}]);
}
My components/home/home.js file:
angular.module('app.home', [])
.controller('HomeController', [HomeController]);
function HomeController() {
var vm = this;
vm.test = "String from the home controller";
}
The error message I get is:
http://run.plnkr.co/OFd5k46BCkBdnfkK/components/home/home.html 404 (Not Found)
Any ideas?
I'm not familiar with ngNewRouter, and it seems that it uses some convention-over-configuration approach in looking up a template (I don't see anywhere else where the template URL is specified), but if you look closely at the error message, it says:
Failed to load template: ./components/home/home.html (HTTP status: 404 Not Found)
(Notice the preceding ./) So, change the cache key accordingly:
$templateCache.put('./components/home/home.html', '<h2>The home page</h2>{{home.test}}');
EDIT:
And, in fact, the documentation of ngNewRouter does specify the default (i.e. conventional) behavior:
The default behavior is to dasherize and serve from ./components. A component called myWidget uses a controller named MyWidgetController and a template loaded from ./components/my-widget/my-widget.html.

Routing in Backbone.js / Marionette.js - no hashtags, route list and sub-routers

I have three questions about routing in Backbone.js / Marionette.js :
1) How can I get a list of all the routes my application's routers have registered ?
For example for Express.js (in Node.js) it would be app.routes.
I'm trying to do the same with Backbone.js / Marionette.js but couldn't find any property or method that did this.
2) I want to clean-up my URLs and remove the hashtag "#" in front of them, I know that they trigger the Routers so how can I manage to do this ?
I found the following script that prototypes the Backbone router, but it's more of a hack than a stable solution : Simple backbone routing without hash URLs
3) Is is possible to have sub-routers in Backbone.js / Marionette.js ?
What I mean by sub-router is a router which only handles a part of a url, e.g :
var AppRouter = Backbone.Router.extend({
routes: {
'articles' : 'MyArticleRouter'
}
});
var MyArticleRouter = Backbone.Router.extend({
routes: {
'science' : 'someMethod',
'literrature' : 'someOtherMethod'
}
});
This would categorise my URLs a little bit more by letting me define the main routes in AppRouter and all the subroutes (part after the second slash "/") in category-specific sub-routers.
So for the following URL : "hostname/articles/science", the routing process would look something like this :
1) pass "/articles/science" to AppRouter
2) AppRouter splits the URI and takes the "/articles" part
3) AppRouter finds the registered "/articles" route
4) AppRouter recognises that MyArticleRouter is bound to that URI element
5) AppRouter forwards the routing to that router and only passes the "/science" element as a route
6) MyArticleRouter routes "/science" to the someMethod() and runs it
Thank you in advance !
Answer for #1:
All the routes are registered in Backbone.history.handlers.
Answer for #2:
You can add a handler to every link in your site:
var application = new Backbone.Marionette.Application();
application.addInitializer(function(options) {
// Add class to target a browser, not as standalone app.
if(window.navigator.standalone != true) {
$('body').addClass('no-standalone');
}
// Prevent internal links from causing a page refresh.
$(document).on('click', 'a', function(event) {
var fragment = Backbone.history.getFragment($(this).attr('href'));
var matched = _.any(Backbone.history.handlers, function(handler) {
return handler.route.test(fragment);
});
if (matched) {
event.preventDefault();
Backbone.history.navigate(fragment, { trigger: true });
}
});
});
Of course make sure you use pushState:
if (!Backbone.history.started) {
Backbone.history.start({ pushState: true });
}
That last snippet must be run after you have initialized all your routers.
Answer for #3:
This may work a little to split your routes:
define([
'backbone',
'underscore',
'routers/dashboard',
'routers/anotherroute1',
'routers/anotherroute2'
],
function(Backbone, _, DashboardRouter, AnotherRoute1, AnotherRoute2) {
'use strict';
var application = new Backbone.Marionette.Application();
application.addInitializer(function () {
_.each([DashboardRouter, AnotherRoute1, AnotherRoute2], function(router) {
new router();
});
if (!Backbone.history.started) {
Backbone.history.start({ pushState: true });
}
});
return application;
});

Where to put onload code in RequireJs and Backbone App

Doing my first backbone app and I'm using a structure somewhat like this tutorial
I'm wondering where the correct place for me to put my onload code, such as setting up onclick listeners etc would be?
I have:
A simple Bootstrap
require.config({
paths: {
jquery: 'libs/jquery/jquery',
underscore: 'libs/underscore/underscore',
backbone: 'libs/backbone/backbone'
}
});
require([
// Load our app module and pass it to our definition function
'app',
], function(App){
// The "app" dependency is passed in as "App"
App.initialize();
});
The App.js
define(['routers/search'], function(router){
var initialize = function(){
this.router = new router();
}
return { initialize: initialize};
});
And then a simple router that calls the relevenent function in the router also defined as a module that calls the relevent function on the router depending on the page.
My feeling is that this function in the router is where I should be putting my onload code. Is that correct?
One possibility is to use the RequireJS domReady plugin (it's available for download from their short plugins list): http://requirejs.org/docs/api.html#pageload
Here's the example they give:
require(['domReady'], function (domReady) {
domReady(function () {
//This function is called once the DOM is ready.
//It will be safe to query the DOM and manipulate
//DOM nodes in this function.
});
});
So then you can just incorporate it into your normal RequireJS structure, knowing that both the DOM is loaded plus any additional dependencies you might have listed alongside it.

How should I bootstrap my web app?

I am looking for the best way to bootstrap my web app using Backbone.Marionette, Backbone.Router and Requirejs.
The following implementation works but I would like to know if this is the right way to make things.
Here's some of my code (*).
My questions are:
1) Is right the following data flow (index.html -> conf.js -> router.js -> app.js) ?
2) The Backbone.View for each region (header, sidebar .....) should be instantiate in router.js or app.js or booths according the context?
// index.html
<!doctype html>
<html lang="en">
<head>
<!-- Load the script "/js/conf.js" as our entry point -->
<script data-main="js/conf" src="js/vendor/require.js"></script>
</head>
<body>
</body>
// js/config.js
require.config({
// some code
});
require(['app']); // instead of require(['conf']);
// router.js
define([
'app',
// others modules
],
function(App, $, _, Backbone, Marionette){
"use strict";
var AppRouter = Backbone.Marionette.AppRouter.extend({
routes: {
test: test,
"*defaults": "home"
}
var initialize = function ()
{
var app_router = new AppRouter;
};
return {
initialize: initialize
};
});
// js/app.js
define(
[
// some modules
],
function ($, _, Backbone, Router, Mustache, Layout, SidebarView) {
var MyApp = new Backbone.Marionette.Application();
MyApp.addInitializer(function () {
$('body').html(Layout);
MyApp.addRegions({
header: '#header',
sidebar: '#sidebar',
mainColumn: '#main-column',
rightColumn: '#right-column'
});
});
MyApp.addInitializer(function () {
var sidebarView = new SidebarView();
MyApp.sidebar.show(sidebarView);
});
MyApp.on("initialize:after", function () {
// Router.initialize();
});
MyApp.start();
return MyApp;
});
This looks pretty good overall. There are a few things I might change, but these are mostly personal preferences:
1) Invert the relationship between the router and app files, and use an initializer to start the router.
Right now you have a circular dependency between the router and the app files, and that's never a good thing. Even though RequireJS can handle that fine, it's a bad idea in many other ways as it can lead to code that doesn't quite work the way you expect.
2) In your router file, set up an initializer that instantiates the router.
3) Don't start backbone.history from the router file.
It's common, and suggested, to have multiple routers in a project. But you can only call Backbone.History.start() once. Start that in the app.js file, using the "after:initialize" event of the router
MyApp.on("after:initialize", function(){ Backbone.History.start(); }
4) Extract your initializer callbacks in to functions that are called from a single initializer
While there's nothing technically wrong with using multiple initializers - and you will need multiple initializers across multiple modules - I suggest using a single initializer within a single module, and have that one initializer call other functions defined in your module.
5) Call addRegions outside of initializers
There's no guarantee that your initializers will run in the order you add them. It depends on how the individual browser handles things.
For example, your app.js file could look like this:
// js/app.js
define(
[
// some modules
],
function ($, _, Backbone, Router, Mustache, Layout, SidebarView) {
var MyApp = new Backbone.Marionette.Application();
MyApp.addRegions({
header: '#header',
sidebar: '#sidebar',
mainColumn: '#main-column',
rightColumn: '#right-column'
});
MyApp.addInitializer(function(){
showLayout();
initSidebar();
});
MyApp.on("initialize:after", function(){
Backbone.History.start();
});
function initSidebar() {
var sidebarView = new SidebarView();
MyApp.sidebar.show(sidebarView);
}
function showLayout() {
$('body').html(Layout);
}
MyApp.start();
return MyApp;
});
...
alright, that looks like a lot more changes than I originally thought. :) But like I said, your set up looks fine over all. These are things that I would do, but are not necessarily requirements to make your app work.

Resources