Skip to content
This repository has been archived by the owner on Dec 6, 2019. It is now read-only.

Page Manager

Vojtěch Šimko edited this page Nov 19, 2019 · 3 revisions

Diagram

Page Manager is an essential part of IMA.js. It's something like a puppeteer that manipulates with pages and views. Once a router matches URL to one of route's path the page manager takes care of the rest.

Managing process

If the new matched route has onlyUpdate option set to true and the controller and view hasn't changed the route transition is dispatched only through update method of the controller.

In every other case the manager goes through it's full process:

1. Unload previous controller and extensions

To make room for the new, manager has to get rid of the old controller and extensions. First calls deactivate method on every extension registered in the old controller and then the same method on the controller itself. Same process follows with destroy method.

2. Clear state and unmount view

After unloading controller and extensions the page state is cleared and view (starting from ManagedRootView) is unmounted. However if the DocumentView, ViewAdapter and ManagedRootView are the same for the new route the view is cleared rather then unmounted. This way you can achieve component persistency.

3. Loading new controller and extensions

After the manager is done with clearing previous resource it initialises the new ones. First the init method is called on controller then on every extension (Extensions may be initialised during the controllers init method call).

When the initialisation is complete manager starts loading resources via load method of the controller and extensions. For detailed explanation see the load method documentation.

4. Rendering new view

After the load method has been called a view for the controller is rendered. It doesn't matter if all promises returned by the load method have been resolved. The process of handling promises is described in the load method documentation. Following rendering process is described on a page Rendering process and View & Components.

Intervene into the process

It's possible for you to intervene into the process before it starts and after it finished. One way is to listen to BEFORE_HANDLE_ROUTE and AFTER_HANDLE_ROUTE dispatcher events. However from inside event listeners you cannot intercept or modify the process. For this purpose we've introduced PageManagerHandlers in v16

PageManagerHandlers

PageManagerHandler is a simple class that extends ima/page/handler/PageHandler. It can obtain dependencies through dependency injection. Each handler should contain 4 methods:

init()

For purpose of initialising.

handlePreManagedState()

This method is called before the page manager start taking any action. It receives 3 arguments managedPage, nextManagedPage and action. managedPage holds information about current page, nextManagedPage about following page. Each of the "managed page" arguments has following shape:

{{
    controller: ?(string|function(new: Controller)), // controller class
    controllerInstance: ?Controller, // instantiated controller
    decoratedController: ?Controller, // controller decorator created from controller instance
    view: ?React.Component, // view class/component
    viewInstance: ?React.Element, // instantiated view
    route: ?Route, // matched route that leads to the controller
    options: ?RouteOptions, // route options
    params: ?Object<string, string>, // route parameters and their values 
    state: {
        activated: boolean // if the page has been activated
    }
}}

and finally the action is an object describing what triggered the routing. If a PopStateEvent triggered the routing the action object will look like this: { type: 'popstate', event: PopStateEvent } otherwise the event property will contain MouseEvent (e.g. clicked on a link) and type property will have value 'redirect', 'click' or 'error'.

handlePostManagedState()

This method is a counterpart to handlePreManagedState() method. It's called after page transition is finished. It receives similar arguments (managedPage, previousManagedPage and action). previousManagedPage holds information about previous page.

Note: handlePreManagedState() and handlePostManagedState() methods can interrupt transition process by throwing an error. The thrown error should be instance of GenericError with a status code specified. That way the router can handle thrown error accordingly.

destroy()

For purpose of destructing

Registering PageManagerHandlers

PageManagerHandlers have their own registry PageHandlerRegistry. Every handler you create should be registered as a dependency of this registry.

// app/config/bind.js
import PageHandlerRegistry from 'ima/page/handler/PageHandlerRegistry';
import Window from 'ima/window/Window';

import MyOwnHandler from 'app/handler/MyOwnHandler';

export let init = (ns, oc, config) => {
    // ...

    if (oc.get(Window).isClient()) { // register different handlers for client and server
        oc.inject(PageHandlerRegistry, [MyOwnHandler]);
    } else {
        oc.inject(PageHandlerRegistry, []);
    }
};

Note: Handlers are executed in series and each one waits for the previous one to complete its task.

PageNavigationHandler

With introduction of PageManagerHandlers in v16 we've moved some functionality to predefined handler PageNavigationHandler. This handler takes care of saving scroll position, restoring scroll position and settings browser's address bar URL. You're free to extend it, override it or whatever else you want.

PageNavigationHandler is registered by default, but when you register your own handlers you need to specify PageNavigationHandler as well.

import PageHandlerRegistry from 'ima/page/handler/PageHandlerRegistry';
import PageNavigationHandler from 'ima/page/handler/PageNavigationHandler';

import MyOwnHandler from 'app/handler/MyOwnHandler';

export let init = (ns, oc, config) => {
    // ...
    oc.inject(PageHandlerRegistry, [PageNavigationHandler, MyOwnHandler]);
};