Multiple routers vs single router in BackboneJs - javascript

All examples on Backbone I've seen use one router for the whole application, but wouldn't it make sense to have a router for each single part of your app (header, footer, stage, sidebar)? Has anyone built apps with more than one router and what are your experiences?
Let's think about a complex app with nested views: Wouldn't it be better when a view has its own router that handles the display of the subviews, than having one big router that has to inform the main view to change its subviews?
The background of this question: I've see a lot of parallels of the router in backbone and the ActivityMapper in GWT. The ActivityMapper is only responsible to get the right presenter for a given route and a given container in the DOM.

i wrote an app (still writing) with multiple routers in it.
however it is not like you think, it is more module based and not a router per view or anything like that.
for example,
say i got two big modules in my app, 1 handling all books, and 1 for the users.
books have multiple views (as do users), a list view, detail view, edit view, etc etc...
so each module has its own router,
which stands for its own set of urls:
// user module...
var userRouter = Backbone.Router.extend({
routes: {
"users": "loadUsers",
"users/add": "addUser",
"user/:id": "loadUser",
"user/:id/edit": "editUser"
}
// ... rest dropped to save the trees (you never know if someone prints this out)
});
// book module
var bookRouter = Backbone.Router.extend({
routes: {
"books": "loadBooks",
"books/add": "addBook",
"book/:name": "loadBook",
"book/:name/edit": "editBook"
}
// ... rest dropped to save the trees (you never know if someone prints this out)
});
so, it is not like my two routers are competing for the same route, they each handle their own set of routes.
edit
now that I had more info via Elf Sternberg, I know it isn't possible by default to have multiple routers match on the same route. without a workaround like overriding the backbone history or using namespaces in routes and regexes to match these routes.
more info here: multiple matching routes
thanks Elf Sternberg for the link.

I just wrote a blog post on Module-Specific Subroutes in Backbone, which allow a "subroute" to be defined which pays attention to everything after the prefix for that route.
Check out the blog entry for more explanation: http://www.geekdave.com/?p=13
This means you don't have to redundantly define the same prefix over and over, and you can also lazy-load subroutes as modules are accessed. Feedback is most welcome!

There is a limited but important case when it makes sense to use multiple Routers. If you need to expose only a subset of your application's routes & views based on data collected at runtime (perhaps login credentials - e.g., manager vs. staff can see & navigate between different sets of views) you could instantiate only the appropriate Router & View classes. This is significant because routes can be bookmarked and sent from user to user. Of course, you still need checks on the server to ensure that an unauthorized user isn't issuing requests after navigating to a view they arrived at via a bookmark sent by an authorized user. But it's better to design the application so the unauthorized view is just not generated.

Related

How to build large scale express.js sites

I am pretty new to the node.js and express.js landscape. However I have been trying to wrap my head around the code behind creating a site (20+ pages for example) in express.js without the code getting pretty large.
A route, when using a view engine, points the incoming request to index or whatever page they requested. After running the default express-generator with the view engine set to HBS you get something like
app.use('/', index);
This points to the index.js file under the routes folder that contains something like
router.get('/', function(req, res, next){
res.render('index', {title: 'Express'});
});
And that then renders the index.hbs file to show what I want on the page.
My problem is this. Would you have to have a route for each page you wanted like a profile page, login page, about page, etc? If so wouldn't this create a lot of lines of code in routes if you had 20+ routes? Example would be in the app.js
app.use('a', a);
app.use('b', b);
...
app.use('z', z);
then each would require a corresponding route js file.
I assume there is a cleaner way of doing routing or perhaps I am over thinking this?
Any light on this concern/question of mine would be amazing.
In my express servers I break routes into different sections called "components" Each components can correspond to a give page if you're doing server-side rendering or it can correspond to a set of API routes.
Each component can have controllers to handle each route, and each controller can borrow from a handful of reusable actions.
Here's an example of an express server component I made(this example is for a set of API routes but the same architecture can be used for sets of hbs server-side rendering routes):
https://github.com/AkyunaAkish/react-redux-node-express-todo-list-boilerplate/tree/master/server/components/todos
It depends on how similar your routes are:
If they basically all have the same functionality, I'd put them in the same file.
If there are slight variations, I'd create a separate class containing the core functions, and then call to what ever is needed separately.
If they are completley different, put them all in separate files
This will give you a solid outline of how to do each:
How to include route handlers in multiple files in Express?
Node.js: Configuration and routes in a different file
How to include route handlers in multiple files in Express?
Generally, you make route files around areas of concern, though a given router can define whatever endpoints make sense. So for example, let's say your site has several pages that are fairly static (e.g. your "about" and "index" examples, and then several that are all based on blog entries (creating, listing, viewing, etc) and then several around users (user profiles and so on). You would probably create one router for each set of things, e.g.:
// ./routes/index.js
router.get('/', (req, res) => { res.render('index', {title: 'Express'}); });
router.get('/about', (req, res) => {res.render('about', {title: 'Express'});});
// etc
conceptually, you could use that more simply to pull the "index" or "about" values from the URI, but I'm assuming you'll do other things like assigning variables and such.
Then in another file
// ./routes/blog.js
router.get('/blog', (req, res) => {
// do whatever to fetch info fromt eh DB and render it...
});
And so on.

Getting initial state using HTML5 history api

Everything I've been searching for is just a tutorial how to use pushState, replaceState, history.state, etc. Those concepts are simple but one thing I'm wondering how people solve is how to know what the initial state is.
Say you SPA is hosted at https://example.com/en-us/myapp/. Go there and your home page of the app is loaded, click around and it does a pushState to see you to https://example.com/en-us/myapp/get/users. Great, now you see a list of users and thanks to the history api, it wasn't an actual page load.
But now let's pretend a user had that https://example.com/en-us/myapp/get/users state bookmarked and the started the app off at this URL. Ok, so your server listens to that and serves up the app. My question is, how do you know that get/users is the current state and you need to show the associated view? Do you just know that your app is hosted at https://example.com/en-us/myapp/ and so you get whatever is after that to know?
Something like this:
function getState (uri) {
return uri.match(/^https:\/{2}(?:w{3}\.)?example.com\/en-us\/myapp\/?(.*)/i)[1];
}
var state = getState(location.href);
and if state is falsey then load the initial view, otherwise handle the state and show the list of users when state === 'get/users'?
Yes, that is quite right. However, you could try using location.pathname to fetch the state, so that your regex does not need to include the domain name.
For example:
function getState (uri){
var path = uri.split("myapp", 2)[1]; // This will split the pathname after 'myapp'
console.log(path) // Just for debugging purposes
// Now we can decide what to do with the path (i.e. "/get/users")
// For example, we can use a switch or a simple if statement
if (path === '/get/users'){
return true
} else {
return false
}
}
var state = getState(location.pathname);
That is just a simple example of a router. You can now try building your very own router for your SPA. Also, there are many libraries out there for you to use if you would like a different approach. You can take a look at these ones if you would like.
navigo
router.js
Also, if you are using a framework to build your SPA, they often have their own routing ability built in. These are just some of the many frameworks that have routers built in. (Sorry, I've <10 reputation so I'm not allowed more than two links).
Vue.js — vuejs.org/v2/guide/routing.html
Mithril.js — mithril.js.org/#routing
Ember.js — guides.emberjs.com/v2.13.0/routing/
Of course, it is ultimately your choice which to use. You could expand upon the example I've provided, by simply implementing a switch for different links/pages in your SPA. I wish you the best with your app!

Storing data among different routes in SPA (React, Redux)

If there is SPA application (usingReact, Redux & react-router but I guess this problem/sollution is not limited to this stack) what is the best way to maintain data between different routes (URLs) to avoid unnecessary requests ?
Example:
I have 2 views which display car entity:
CarList # /cars/
CarDetail /car/:car_id
Clicking on a representation of car from CarList navigates to /cars/:selected_car_id
When rendering CarList I have already loaded the necessary data for CarDetail of any car shown at that view.
Similar problem is with wizard-like navigation with routes like:
/do-something-on-entity/:entity_id/step-1/
/do-something-on-entity/:entity_id/step-2/
/do-something-on-entity/:entity_id/step-3/
What is the best way to avoid that additional requests ?
Is usage of routes good idea in such a wizard ?

.redirect() from mounted middleware with relative destination fails

I want to restrict a certain subtree only to authenticated users. The basic setup is as follows (fat removed):
app.use(express.bodyParser())
.use(express.cookieParser('MY SECRET'))
.use(express.cookieSession())
.use('/admin', isAuthenticatedHandler)
.use('/admin', adminPanelHandler);
Where the handler functions is:
isAuthenticatedHandler = function(req, res, next) {
if (!req.session.username) {
res.redirect('login');
} else {
next();
}
};
The problem is that even though I provide the redirect destination as a relative path 'login', it doesn't lead to <mount_point>/login i.e. /admin/login but to /login which of course throws a 404.
From the expressjs API reference:
This next redirect is relative to the mount point of the application.
For example if you have a blog application mounted at /blog, ideally
it has no knowledge of where it was mounted, so where a redirect of
/admin/post/new would simply give you `http://example.com/admin/post/new`,
the following mount-relative redirect would give you
`http://example.com/blog/admin/post/new`:
res.redirect('admin/post/new');
Am I misreading this?
The issue here is that while you are using your middleware off of /admin, your app itself is not mounted at /admin. Your app is still off of the root, and your configuration simply says to only use your isAuthenticatedHandler middleware if the request comes in off the /admin path.
I whipped together this gist. Notice how it uses 2 Express applications, one mounted inside the other (line 23 pulls this off). That is an example of mounting the application at a different point rather than just putting a given middleware at a given point. As presently written, that example will give you an endless redirect, since the isAuthenticatedHandler fires for everything off of / in the child application, which equates to /admin overall. Using 2 separate applications might introduce other issues you're not looking to deal with, and I only include the example to show what Express means when it talks about mounting entire applications.
For your present question, you'll either need to follow what Yashua is saying and redirect to /admin/login or mount your admin interface as a separate Express application.
What are you trying to achieve? Why not just redirect to '/admin/login' ? And the mount point they are talking about is the place where your Express app is located, not necessarily the current URL. So /blog might be setup on your server to be the root of your app while / might be a totally different app. At least that's the way I read this.

Marionette building re-usable sub-apps and modules

I am currently building a complex marionette application and have been follow a great book as a reference. However, I am a sucker for reusable code and like to keep things flexible as possible.
My application has various screens and modules, one of which is a dashboard. The dashboard has its own set of responsibilities, so I made the conclusion that it should be a sub-app.
The dashboard is split 50/50 and there are two components inside. I have identified that each of these components (lets say compA and compB) each have their own set of responsibilities aswell and should be their own sub apps. However, I will have the use case where compA will be used in another area of the application.
My first thought with re-use in mind was to simply re-use the views and create a new module where ever this particular view was needed. However, the unique events and actions that come with this view are stored in the controller and API to interact with the module.
So I have ended up with the following structure:
application.js
apps
--dashboard
--compA
--compB
and I have implemented something like the following to reuse functionality from compA
Controller = {
getView: function () {
return new Show.MyView();
}
}
API = {
getMyView: function () {
return Controller.getView();
}
}
App.reqres.setHandler('compa:get:view', function () {
return API.getMyView();
});
Doing this allows me to request a new instance of the view to display and keep the same action logic. However, this means that there is no separation between each section (compA in the dashboard, and compa in another section of the app). So if I were to stop the compa module it would not have the desired results.
Is there a better approach to re-usable modules with minimal duplication of code?
My thought was to extract the Controller logic into a controller object that I can extend, and then creating a new 'sub app' when I would like to re-use the features.
application.js
apps
--dashboard
--compA-dashboard // new instance of Controller
--compA-somewhereelse // new instance of Controller
--compB
It seems as though I may be over-complicating my design pattern.
Allow me to rename your "component" as "widget" at first. In my opinion "component" is better for things more general than the widgets in your case.
I would arrange those widgets as following:
app.js
/app
/dashboard
/show
show_controller.js
dashboard_app.js
/other
/components
/widgets
/widgetA
widgetController.js
widgetView.js
/widgetB
Since widgetA is dependent from dashboard and is supposed to be use elsewhere, it should be fully decoupled from dashboard.
The 'show' View of Dashboard should have a Layout to define where to show widgetA and widgetB.
Then, in your DashBoardApp, set an appRoute to respond to some route, and then call the controller.
The controller will initialize the Layout.
The Layout will ask for show of RegionA and RegionB.
Listen to 'show' events of RegionA and RegionB, to ask for an App wide request
this.listenTo(regionA, 'show', function(){
App.request 'widget:a:show'
});
Then in Widget module, respond to the App events and deliver the view
App.reqres.setHandler('widget:a:show, function(){
API.getWidgetAView();
});
The later part of my answer is a bit vague with less code. The basic idea is, DashBoardApp should finish his job by sending App request. And then it's components job to deliver the view upon App request, which is fully decoupled.

Categories

Resources