Skip to content

Support rendering multiple routes into a single target outlet #715

@agubler

Description

@agubler

Enhancement

Change @dojo/framework/routing to support rendering multiple routes into the same outlet, an outlet is considered as a target in an application that depending on the matched route can display varying output.

The Problems

The concept of an outlet in routing doesn't map to being able to creating static layouts, instead outlets map directly to a unique route. This makes it verbose to use the Outlet widget as you have to define one for each route that could be rendered.

import { create, tsx } from '@dojo/framework/core/vdom';
import Outlet from '@dojo/framework/routing/Outlet';

const factory = create();

const App = factory(function App() {
    return (
        <div>
            <Outlet id="foo" renderer={(matchDetails) => {
                return <SomeWidget />    
            }} />
            <Outlet id="bar" renderer={(matchDetails) => {
                return <SomeWidget />    
            }} />
            <Outlet id="baz" renderer={(matchDetails) => {
                return <SomeWidget />    
            }} />
            <Outlet id="qux" renderer={(matchDetails) => {
                return <SomeWidget />    
            }} />
        </div>
    );
});

Proposal

Add an additional configuration option to the routing configuration to uniquely identify the route and re-purpose the outlet configuration to more appropriately indicate the section of the application that the route will be rendered.

For an application with a simple layout that includes a side bar and a main view, assuming that the side bar of the application is visible for all routes, with the main layout changing based on the user interaction (route change). For simple applications with few routes, this is fine using the multiple outlet configuration shown above, but as the application grows it can become messy and hard to manage.

---------------------------------------------------------
|         |                                             |
|         |                                             |
|         |                                             |
|         |                                             |
|         |                                             |
| sidebar |                  main                       |
|         |                                             |
|         |                                             |
|         |                                             |
|         |                                             |
|         |                                             |
---------------------------------------------------------

Changing the concept of an outlet to reference the location in application that should render, helps engineers to easily grok where in the application content is going to be rendered for each route and provides more control within the rendering widget itself.

const routeConfig = [
    {
        id: 'foo',
        outlet: 'main',
        path: 'foo'
    },
    {
        id: 'bar',
        outlet: 'main',
        path: 'bar'
    },
    {
        id: 'baz',
        outlet: 'main',
        path: 'baz'
    },
    {
        id: 'qux',
        outlet: 'main',
        path: 'qux'
    }
];

With a simple routing configuration shown above all the routes with an outlet of main would trigger the a single outlet to re-render, providing the required details to determine what view needs to be rendered.

import { create, tsx } from '@dojo/framework/core/vdom';
import Outlet from '@dojo/framework/routing/Outlet';

const factory = create();
const App = factory(function App() {
    return (
        <div>
            <Sidebar />
            <Outlet id="main">
            {{
                foo: () => <Foo />,
                bar: () => <Bar />,
                baz: () => <Baz />,
                qux: (matchDetails) => {
                    return <Qux params={matchDetails.params.id}/>;
                }
             }}
            </Outlet>
        </div>
    );
});

By default the keys of the children match to the route ids that are matched for the outlet id, however this could be customised using the matcher property to restrict rendering to based on the type of match (exact, partial, etc) or for a label that is not represented by the routes at all and manually determined based on any logic.

import { create, tsx } from '@dojo/framework/core/vdom';
import Outlet from '@dojo/framework/routing/Outlet';

const factory = create();
const App = factory(function App() {
    return (
        <div>
            <Sidebar />
            <Outlet id="main" matches={(defaultMatches, matchDetails) => {
                 // do stuff
                 return defaultMatches;
            }}>
            {{
                foo: () => <Foo />,
                bar: () => <Bar />,
                baz: () => <Baz />,
                qux: (matchDetails) => {
                    return <Qux params={matchDetails.params.id}/>;
                }
             }}
            </Outlet>
        </div>
    );
});

As well as taking an object for the children and matching being determined by whether there the key matches an active route id or the custom logic determined by the user defined matcher property, a function can passed that will render for any matches or the outlet.

import { create, tsx } from '@dojo/framework/core/vdom';
import Outlet from '@dojo/framework/routing/Outlet';

const factory = create();
const App = factory(function App() {
    return (
        <div>
            <Sidebar />
            <Outlet id="main">
               {(matchDetails) => <SomeWidget params={matchDetails.params} />}
           </Outlet>
        </div>
    );
});

The existing Outlet behaviour would still be available but renamed as Route, which accurately describes the behaviour.

import { create, tsx } from '@dojo/framework/core/vdom';
import Outlet from '@dojo/framework/routing/Outlet';

const factory = create();

const App = factory(function App() {
    return (
        <div>
            <Route id="foo" renderer={() => {
                return <Foo />
            }}/>
        </div>
    );
});

Note: The route id and changing Outlet to Route should be automatically migrated by the cli-upgrade-app tool.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: routingRoutingbreaking changeIndicates the issue/pull request would result in a breaking changeenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions