How do I create a submenu in Backbone? I have a main router and main navigationmenu and i want to add a submenu on for example the third page, with addiotional 3-4 anchors.
how do I approach this? Do i have to create some additional routers and "import/load" them in the specific view?
On the views side, you'd handle the submenu view in your third page view.
About the routes, if they're related to your third page, then try nested urls:
routes: {
"page/3": "thirdPageHome",
"page/3/{subPage}": "thirdPageSub"
}
Then in thirdPageSub, you'd resolve the sub path, and from there I'd dispatch the call to another router method:
Backbone.Router.extend({
routes: {
"page/3": "thirdPageHome",
"page/3/{subPage}": "thirdPageSub"
},
thirdPageSub: function( subRoute ) {
if ( subRoute === "foo" ) return this.thirdPageFoo();
},
thirdPageFoo: function() {
// do your stuff
}
});
Related
I have to serve up my backbone app on the "/b" route and am having trouble hooking in my router. It works fine if I just showView a view, but my route controller functions are not firing when I hook in my router, any thoughts?
router:
define('appRouter', ['marionette', 'rootView', 'changePasswordView'], function(Marionette, rootView, changePasswordView) {
return Marionette.AppRouter.extend({
routes: {
'/b/change-password': 'showChangePassword',
'/b': 'showAccountSettings'
},
showChangePassword: function() {
this.showView(new changePasswordView());
},
showAccountSettings: function() {
this.showView(new rootView());
}
});
});
application onStart (which is confirmed firing):
var Application = Marionette.Application.extend({
...
onStart: function(options) {
console.log('on start');
var router = new appRouter(options);
/** Starts the URL handling framework */
if( ! Backbone.History.started) Backbone.history.start();
router.initialize();
},
...
});
When I visit http://localhost:8080/b (which is for all intensive purposes my index) it renders a blank page.
Default routes in Backbone are hash-based. Link to Your /b route should look like http://localhost:8080/#/b.
If You don't need hash-based links, start history with pushState: true.
Backbone.history.start({pushState: true});
EDIT:
If You serve app on /b path, then You have wrong defined routes. Routes must be defined relative to /b:
routes: {
'change-password': 'showChangePassword',
'': 'showAccountSettings'
},
And access:
http://localhost:8080/b' -showAccountSettings`
http://localhost:8080/b#change-password' -showChangePassword`
I use this routes controller in order to navigate to page A. At page A it is possible to navigate to page B.
function getRecordReportState() {
var state = {
name: 'auth.recordreport',
url: '/recordreport/:userId/:month/:year',
templateUrl: 'app/recordReport/recordReport/recordReport.html',
In page B I use this command to navigate back to page A:
function cancelAndGoBack() {
$window.history.back();
}
My question now would be if there is a possibility to navigate to page A programmatically where I also can set the parameter /:userId/:month/:year ?
Yes. You can simply do:
$state.go('auth.recordreport', {userId: 'someuser', month: 4, year: 2016});
Here is my A tag in index.html.
<div class="menu-item">Login in
If I click it, it should go to '/login' route. But URL correctly changed to localhost:3333/#login in browser address input bar, but the page content shows no change, still in landing page.
Here is my code for starting Backbone history:
new Router();
Backbone.history.start({pushState: true, root: '/'});
Here is my code for router:
var Backbone = require('backbone');
var $ = require('jquery');
Backbone.$ = $;
var _ = require('lodash');
var Marionette = require('backbone.marionette');
var OuterLayout = require('../layout/outerLayout/outerLayout');
var ol = new OuterLayout();
var AppRouter = Backbone.Marionette.AppRouter.extend({
routes : {
'': 'index',
'signup' : 'signup',
'login' : 'login'
},
index : function () {
if(_.isEmpty(ol.el.innerHTML)) {
ol.render();
}
// outerLayout.footer.show();
},
signup : function () {
if(_.isEmpty(ol.el.innerHTML)) {
ol.render();
}
var ContentSignup = require('../layout/outerLayout/view/contentSignup/contentSignup');
ol.content.show(new ContentSignup());
},
login: function () {
if(_.isEmpty(ol.el.innerHTML)) {
ol.render();
}
var ContentLogin = require('../layout/outerLayout/view/contentLogin/contentLogin');
ol.content.show(new ContentLogin());
}
});
module.exports = AppRouter;
The result is that URL changed in the browser address input field, but the page content doesn't change. Then if I hit CMD + R to refresh the page, then the content will change, correctly reflecting the route.
Also the go back button on browser doesn't work, url changes, but the content doesn't change. I think I forget to call sth in my code to "refresh" the browser?
oh, I am using httpster to start a mini http server for this front-end development.
Have you tried this:
new AppRouter();
instead of this
new Router();
Unless you actually want to hit the server (which would just be /login and you'd deal with it on the server side). You should take the pub sub approach.
So in your view you would say:
triggers
"click .menu-item": "loginClicked"
Then in your controller you can listen to that event (if it's a composite view's childview that you're in you may have to prefix this with childview:):
#listenTo loginView, "login:button:clicked", (args) ->
App.vent.trigger "login:clicked"
Then in the router
API =
login: ->
new LoginsApp.Show.Controller
App.vent.on "login:clicked", ->
App.navigate "/login"
API.login()
So you end up navigating/hitting the same action that you would by going through the router, but you don't have to rely on the routes.
If you don't want to go that route I imagine the problem is that you need to say Backbone.history.navigate({trigger: true}) to get it to actually trigger the route in the approuter.
The best approach I've found is the approuter is there when the user clicks refresh or navigates directly to the page. But everything else should be handled in app with the pub sub approach. It gives you the most control that way.
Remove the slash (/) and use only "#route" on your hrefs, to avoid the browser from fetching the default document served at "/" from the backend.
By the way, watch your use of require, you should require the constructors at the top so the requirejs optimizer can fill in the dependencies on build time.
Something like:
//this before the component definition
var MyView = require("views/myview"),
AppLayout = require("views/layout");
//... later on your view/app/model definition
function foo(){
var view = new MyView();
}
I also think having a list of required stuff at the top of any file helps understanding it later on. ;)
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;
});
Here is my application-router.js file where i'm creating Backbone.Router object with just only few routes:
var App = App || {};
App.Router = Backbone.Router.extend({
routes : {
'' : 'showDashboard', // Not shown
'*other': 'showModalError'
},
defaultRoute : function(other) { $('#modal404').modal(); }
});
In main javascript file application.js i'd like to programmatically add routes. I've tried with route() function and it doesn't work, routes are not added. It works however passing an object to the "constructor", but that will override already defined routes:
// This works and overrides all defined routes in App.Router
var router = new App.Router({ routes : { '/test/me' : 'testRoute' } });
// This is not working
router.route(ExposeTranslation.get('customers.new.route'), 'newCustomer');
router.route('/test/me/again', 'testAgainRoute');
In fact console.log(App.Router) shows:
routes Object { /test/me="testRoute"}
I suppose i'm missing something i can't figure out, i'm beginning learning this little piece of powerful javascript.
Your router.route calls are working, those calls aren't your problem. When you call route to add a new route, the new route goes at the end of the routing list. In particular, the routes that are added by your route calls go after '*other' and '*other' will match anything so your new routes will be effectively ignored.
Try removing your '*other' route from routes and adding it after your two route() calls:
routes : {
'' : 'showDashboard' // Not shown
},
router.route(ExposeTranslation.get('customers.new.route'), 'newCustomer');
router.route('/test/me/again', 'testAgainRoute');
router.route('*other', 'showModalError');
The routes aren't stored in App.Router object, they're stored inside Backbone.history:
route: function(route, name, callback) {
// ...
Backbone.history.route(route, _.bind(function(fragment) {
//...
}, this));
return this;
},
That's why your console.log(App.Router) doesn't say anything helpful.