mangojuice-router

1.0.1 • Public • Published

MangoJuice Router

Canonical Router implementation for MangoJuice

In trems of MJS, this packages is a Block with Logic and createModel. It just converts any change of browser's History to change of Router's Model. You should work with Router's model (using built-in helper functions) to identify which route is active, which route user left and so on, to show appropreate view in your App.

Installation

npm install mangojuice-router

Quick start

Nest Router.Logic to Shared Block

As said above, this module is a Block. So you just need to nest it to your shared block like this:

// Shared.js
import Router from 'mangojuice-router';
import { MainRoutes } from './routes';

export const createModel = () => ({
  router: Router.createModel()
});

export const Logic = {
  name: "SharedBlock",

  children() {
    return
      router: this.nest(Router.createLogic(MainRoutes)).singleton()
    };
  }
};

import { MainRoutes } from './routes'; it imports a root route. It is required to create Router logic.

router: this.nest(Router.createLogic(MainRoutes)).singleton(routes) nests the Router logic to the Shared logic and bind it to the router field of the shared model. Also it makes the Router Logic to be singleton in the scope of your app. Which means that you won't need to bind Router commands explicitly to the shared.router model to execute it.

Now let's see how you can define the routes.

Define routes

// routes.js
import { route } from 'mangojuice-router';

export const NewsRoutes = {
  All: route('/'),
  Category: route('/:category')
};

export const MainRoutes = {
  Home: route('/'),
  News: route('/news', NewsRoutes)
};

Here we are defining the routes tree structure. MainRoutes defines root routes of the app. Also it defines that News have some sub-routes in the NewsRoutes object.

route function accepts three arguments. First argument is a pattern in format acceptable by url-pattern (mangojuice-router uses it internally). The second arugment is the children routes object or null. And the third argument is the options object { default: false }. You can provide { default: true } if you want to have non-/ pattern to be your home page (user will be automatically redirected)

route returns a command creator. A command created by route redirecting a user to the specific route. For exmple NewsRoutes.Category({ category: 'world-news' }) will retirect a user to the /news/world-news.

Let's talk how to use it in the next section.

Routes usage

Assume you have an app block where you are nesting Landing Block nad News Block. Also you have the routes definition like above. Then:

// MainBlock.js
import React from 'mangojuice-react';
import Router from 'mangojuice-router';
import * as News from './News';
import * as Landing from './Landing';
import { MainRoutes, NewsRoutes } from './routes';

const createWorldNewsLink = (shared) => {
  return Router.link(
    shared.router,
    NewsRoutes.Category({ category: 'world-news' })
  );
};

export const createModel = () => ({
  news: News.createModel(),
  landing: Landing.createModel()
});
export const Logic = {
  name: 'MainBlock',

  config() {
    return {
      news: this.nest(News.Logic),
      landing: this.nest(Landing.Logic)
    };
  }
};
export const View = ({ model, shared }) => (
  <div>
    <h2>Awesome Site</h2>
    <nav>
      <a onClick={MainRoutes.Home}>Home</a><br />
      <a {...createWorldNewsLink(shared)}>
        World news
      </a>
    </nav>
    {Router.isActive(shared.router, MainRoutes.Home) &&
      <Landing.View model={model.landing} />}
    {Router.isActive(shared.router, MainRoutes.News) &&
      <Landing.View model={model.news} />}
    {Router.isNotFound(shared.router) &&
      <div>404</div>}
  </div>
);

By default each App block updates when Shared Model get updated. So, when user change the URL from / to /news, the Router will catch this event, update router model and the App will be re-rendered.

Router.isActive(shared.router, MainRoutes.Home) check that current route is MainRoutes.Home and if so we are rendering Landing's View.

Router.isActive(shared.router, MainRoutes.News) check that current route is MainRoutes.News. But as you remember our news route have children routes. It means that when you open /news then router will set as acrive more than one route, in this case MainRoutes.News and NewsRoutes.All. For /news/3123 will be active MainRoutes.News and NewsRoutes.Category routes.

In case when NewsRoutes.Category is active, you can access parameter from the pattern in router's model by shared.router.params.category. Because of all parameters of all routes stored in one object, parameter names should be unique across all routes.

<a onClick={MainRoutes.Home}>Home</a> renders a link to /. One of the most beautiful thing in mangojuice-router is that each route is a command. So, when user click to the Home link, MainRoutes.Home command will be executed, which will change the route according defined pattern.

Router.link is a helper function which returns an object { onClick, href } where onClick equals to a command you passed as a second argument, and href equals to an URL that will be set when a user click to the link. You can use this helper to create Search Crawler friendly links.

NewsRoutes.Category({ category: 'world-news' }) creates a command for redirecting user to /news/worl-news page. Each route command accepts three arguments: (1) object with url parameters (from pattern), (2) object with query parameters and (3) options object { keep: false, replace: false }. keep to not remove all existing query parameters (but extend it) and replace to replace current History State instead of pushing the new one.

Router.isNotFound returns true when current URL not match any of registered routes.

Extend the Router

Sometimes you might need to have a full control over the routing. For example to redirect a user to login page when some route is protected and user not logged in. Or to redirect some old urls to new ones.

With mangojuice-router and MJS architecture you can do it pretty easily. By creating your own Router. Let's say you want to limit the user with available categories of News and redirect him to "world-news" when the category is out of defined set:

// ExtendedRouter.js
import { Cmd } from 'mangojuice-core';
import Router from 'mangojuice-router';
import { NewsRoutes } from './routes';

const AvailableCategories = new Set([
  'worl-news', 'it', 'politics'
]);

const Logic = {
  ...Router.Logic,

  @Cmd.batch
  UpdateRouter({ model }, newModel) {
    if (
      Rotuer.isActive(newModel, NewsRoutes.Category) &&
      !AvailableCategories.has(newModel.params.category)
    ) {
      return NewsRoutes.Category({ category: 'world-news' });
    }

    return Router.Logic.UpdateRouter(...args);
  }
};

export default { ...Router, Logic };

Then you will just need to import Router from './ExtendedRouter'; instead of import Router from 'mangojuice-router'; in your Shared block.

Here we are using a very nice MJS feature – extendibilty of Blocks. We are creating the new logic based on the original Router's logic and overriding the UpdateRouter command. In the new command we are checking current active route and category and deciding should we actually update the route or redirect to another route instead.

Note that with this implementation we are catching router's model update before it is actually updated, so App Views won't blink or anything with wrong category route. The App will be re-rendered only when original Router.Logic.UpdateRouter will be executed.

Conclusion

That's basicaly it. mangojuice-router is really simple but yet powerfull implementation of SPA routing. If you have any questions or doubts fill free to ask in slack.

API Reference

TODO

Readme

Keywords

none

Package Sidebar

Install

npm i mangojuice-router

Weekly Downloads

19

Version

1.0.1

License

MIT

Last publish

Collaborators

  • artem.artemev