JavaScript Extension Development API for Oracle Visual Builder Cloud Service - Classic Applications

Source: pages.dt/js/api/Navigation.js

define([
    'module',
    'core/js/api/ListenerBuilder',
    'core/js/api/utils/RequireUtils',
    'core/js/api/utils/StringUtils',
    'core.ui.dt/js/api/GlobalNavigation',
    'layout.dt/js/LayoutGenerator',
    'pages/js/api/Navigation',
    'viewmodel/js/api/context/ContextualData'
], function(
        module,
        ListenerBuilder,
        RequireUtils,
        StringUtils,
        GlobalNavigation,
        LayoutGenerator,
        BaseNavigation,
        ContextualData) {

    'use strict';

    /**
     * Navigation module provides design-time related navigation APIs
     * for ABCS applications.
     *
     * <p><strong>Most parts of the Navigation APIs are publicly available
     * inside the {@link module:api/js/Pages Pages} module.</strong> This module should be
     * used for design-time work only - i.e. obtaining of the active page ({@link pages.dt/js/api/Page Page}
     * object is known to the ABCS design-time only).</p>
     *
     * @AbcsExtension stable
     * @version 16.3.5
     *
     * @private
     * @singleton
     * @exports pages.dt/js/api/Navigation
     * @constructor
     *
     * @see {@link module:api/js/Pages Pages} module for the list of available public Navigation APIs
     * @see {@link pages.dt/js/api/PageUtils PageUtils} module holding helper methods for {@link pages.dt/js/api/Page Pages}
     */
    var Navigation = function() {
        AbcsLib.checkThis(this);
        AbcsLib.checkSingleton(Navigation);
        BaseNavigation.apply(this, arguments);

        this.pages = undefined;
        this.activePage = undefined;
        this.lastState = undefined;
        this._initializedListeners = false;
        this._pageNavigationPromise = Promise.resolve();
    };
    AbcsLib.extend(Navigation, BaseNavigation);

    Navigation._LOGGER = Logger.get(module.id);

    /**
     * Get the active (currently shown) {@link pages.dt/js/api/Page page}.
     *
     * <p>Unlike other public navigation methods within {@link module:api/js/Pages public Navigation API},
     * this method provides instances of the design-time specific object {@link pages.dt/js/api/Page Page}.
     * <br>{@link pages.dt/js/api/Page Page} can be used for seeking {@link viewmodel/js/api/Archetype Archetypes},
     * page's display name, its {@link pages.dt/js/api/PageType type} or also its
     * {@link pages.dt/js/api/View views}.</p>
     *
     * @AbcsExtension stable
     * @version 16.3.5
     *
     * @returns {pages.dt/js/api/Page|undefined} active page in the Page Designer, undefined if Page Designer is not visible
     *
     * @see {@link pages.dt/js/api/Page Page} class which defines the page structure
     * @see {@link pages.dt/js/api/PageUtils PageUtils} module with helper methods for {@link pages.dt/js/api/Page Page}s
     *
     * @example <caption>Example of getting active Page object information.</caption>
     * // getting the active page instance
     * var activePage = navigation.getActivePage();
     * // printing page's information into the console
     * console.log('Active page information: ' +
     *         'id' + page.getId() +
     *         'displayName=' + page.getDisplayName() +
     *         'pageType=' + page.getType());
     */
    Navigation.prototype.getActivePage = function() {
        AbcsLib.checkParameterCount(arguments, 0);
        this._checkInitialised();
        return this.activePage;
    };

    /**
     * Get ID of the active (shown) page or undefined in case Page Designer is not active.
     *
     * @return {String|undefined} active page's ID, otherwise undefined
     */
    Navigation.prototype.getActivePageId = function() {
        AbcsLib.checkParameterCount(arguments, 0);
        this._checkInitialised();
        var activePage = this.getActivePage();
        return activePage ? activePage.getId() : undefined;
    };

    /**
     * Get Display name of the active (shown) page.
     *
     * @return {String|undefined} active page's display name
     */
    Navigation.prototype.getActivePageDisplayName = function() {
        AbcsLib.checkParameterCount(arguments, 0);
        this._checkInitialised();
        var activePage = this.getActivePage();
        return activePage ? activePage.getDisplayName() : undefined;
    };

    /**
     * Get ID of the home (default) page.
     *
     * @return {String} home page's ID
     */
    Navigation.prototype.getHomePageId = function() {
        this._checkInitialised();
        return this.pages.getHomepageId();
    };

    /**
     * Navigates application to the given page ID.
     *
     * @param {String} pageId page's ID to navigate to
     * @param {ContextualData} contextualData optional contextual data of the navigation
     */
    Navigation.prototype.navigateToPage = function(pageId, contextualData) {
        this._checkInitialised();
        var self = this;

        // serialization of the navigation requests
        return self._pageNavigationPromise = self._pageNavigationPromise.then(function() {
            return self._deactivateActivePage();
        }).then(function() {
            return RequireUtils.require(['core.dt/js/Router']);
        }).then(function(Router) {
            if (self.pages.getPage(pageId) === undefined) {
                throw new Error(AbcsLib.i18n('pagesDt.api.navigation.pageNotExistsError', pageId));
            }
            return Router.navigateToPage(pageId, contextualData).then(function(pageModel) {
                self.__focusAppContent();
                return pageModel;
            });
        }).catch(function(error) {
            var message = AbcsLib.i18n('pagesDt.api.navigation.navigationFailedError', error ? ('<br>' + error) : '');
            Abcs.UI().showNotification(Abcs.UI().Notification.create({
                message: StringUtils.format(message, pageId),
                level: Abcs.UI().Notification.Level.ERROR
            }));
        });
    };

    /**
     * Navigates application back to particular page ID. Difference is that upon
     * returning to a page it is assumed that page was already initialized.
     *
     * @param {String} pageId page's ID to return to
     * @param {ContextualData} contextualData optional contextual data of the navigation
     */
    Navigation.prototype.returnToPage = function(pageId, contextualData) {
        this._checkInitialised();
        var self = this;

        return self._pageNavigationPromise = self._pageNavigationPromise.then(function() {
            return self._deactivateActivePage();
        }).then(function() {
            return RequireUtils.require(['core.dt/js/Router']);
        }).then(function(Router) {
            return Router.returnToPage(pageId, contextualData);
        });
    };

    /**
     * Navigates application to the homepage.
     */
    Navigation.prototype.navigateHome = function() {
        this._checkInitialised();
        return this.navigateToPage(this.getHomePageId());
    };

    /**
     * Refreshes the active page of the application.
     */
    Navigation.prototype.refreshActivePage = function() {
        this._checkInitialised();
        var activePage = this.getActivePage();
        if (activePage) {
            activePage.refresh();
        }
    };

    /**
     * Sets the active (shown) page.
     *
     * @param {Page} page page to set as active
     * @param {ContextualData} contextualData navigation contextual data
     * @param {Boolean} returningToPreviousPage flag whether we are returning back to previous page (using back, cancel etc.)
     * @return {Promise} returns promise of the page being activated
     * @fires pages.dt/js/api/Navigation#EVENT_ACTIVE_PAGE_SET
     */
    Navigation.prototype.setActivePage = function(page, contextualData, returningToPreviousPage) {
        AbcsLib.checkDefined(page, 'page');
        this._checkInitialised();

        //stores the last activation state
        var lastStateContextualData = contextualData;
        if (returningToPreviousPage) {
            lastStateContextualData = page.getViewModel().getContextualData();
        }
        this.lastState = new Navigation.LastState(this.activePage, page, lastStateContextualData);

        return this._setActivePage(page, contextualData, returningToPreviousPage);
    };

    /**
     * Switch the active page to the provided one.
     * It calls it with the same parameters as were used originally.
     *
     * @param {Page|undefined} pageToSwitch page instance to switch to
     */
    Navigation.prototype.switchActivePage = function(pageToSwitch) {
        return this._setActivePage(
                pageToSwitch,
                this.lastState ? this.lastState.getContextualData() : undefined);
    };

    Navigation.prototype._setActivePage = function(page, contextualData, returningToPreviousPage) {
        this._checkInitialised();
        this.activePage = page;

        //create an empty page context if not provider by the caller
        contextualData = contextualData || new ContextualData();

        if (page !== undefined) {
            //turn on/off Modal Page support within layout setup
            LayoutGenerator.applyModalPage(page.isModalPage());

            var promise = page.activated(contextualData, returningToPreviousPage);
            this.fireEvent(this.EVENT_ACTIVE_PAGE_SET, page.getId(), this.lastState && this.lastState.getPreviousPage() ? this.lastState.getPreviousPage().getId() : undefined);
            Navigation._LOGGER.debug('Set active page \'' + page.getId() + '\' with context: ' + JSON.stringify(contextualData));
            return promise;
        } else {
            Navigation._LOGGER.debug('Set active page set to undefined.');
            return Promise.resolve();
        }

    };

    /**
     * Sets the active (shown) page for the given page ID.
     *
     * @param {string} pageId ID of the page to be set as active
     * @param {ContextualData} contextualData navigation contextual data
     * @param {Boolean} returningToPreviousPage flag whether we are returning back to previous page (using back, cancel etc.)
     */
    Navigation.prototype.setActivePageId = function(pageId, contextualData, returningToPreviousPage) {
        AbcsLib.checkDefined(pageId, 'pageId');
        this._checkInitialised();
        return this.setActivePage(this.pages.getPage(pageId), contextualData, returningToPreviousPage);
    };

    /**
     * Gets last state of the navigation object.
     */
    Navigation.prototype.getLastState = function() {
        return this.lastState;
    };

    /**
     * Calls deactivation of the page and fires appropriate event.
     *
     * @returns {Promise} promise to be fulfiled once the deactivation is complete
     */
    Navigation.prototype._deactivateActivePage = function() {
        this._checkInitialised();
        var self = this;
        var page = self.getActivePage();

        if (page !== undefined) {
            return page.deactivated().then(function() {
                self.fireEvent(self.EVENT_PAGE_DEACTIVATED, page.getId(), page.getViewModel());
                Navigation._LOGGER.debug('Deactivating active page \'' + page.getId() + '\'.');
                return self._setActivePage(undefined);
            }).catch(function(error) {
                Navigation._LOGGER.debug('Page deactivation failure: \'' + error + '\'.');
                throw error;
            });
        } else {
            Navigation._LOGGER.debug('No active page to be deactivated found.');
            return Promise.resolve();
        }
    };

    /**
     * <b>To be called from Pages only!</b>
     */
    Navigation.prototype.__initialize = function(pages) {
        var self = this;
        self.pages = pages;

        if (!self._initializedListeners) {
            self._initializedListeners = true;

            // init GlobalNavigation listening
            GlobalNavigation.getInstance().addListener(new ListenerBuilder((GlobalNavigation.Events.ACTIVE_SECTION_SET), function(activeSection, previousSection) {
                self._handleGlobalNavigationEvent(activeSection, previousSection);
            }).build());
            AbcsLib.Translations.addListener(new ListenerBuilder(AbcsLib.Translations.EVENTS.CHANGED_EXTERNALLY, function () {
                var activePage = self.getActivePage();
                if (activePage) {
                    activePage.refresh();
                }
            }).build());
        }
    };

    Navigation.prototype.__focusAppContent = function() {
        Navigation._superClass.__focusAppContent.apply(this, arguments);
        var mode = this.pages.getMode();
        var $elementToScroll = mode && mode.isRuntime() ? $(window) : $('#dt-application-canvas');
        $elementToScroll.scrollTop(0);
    };

    /**
     * Checks that it's initialized from Pages module.
     */
    Navigation.prototype._checkInitialised = function() {
        if (!this.isInitialised()) {
            throw new Error('No Pages.setContext() has been called first!');
        }
    };

    /**
     * Gets a flag whether the Navigation module is initialized for the application or not.
     */
    Navigation.prototype.isInitialised = function() {
        return this.pages !== undefined;
    };

    Navigation.prototype._handleGlobalNavigationEvent = function(activeSection, previousSection) {
        var self = this;
        if (GlobalNavigation.isManagementSection(activeSection)) {
            // switching to other application
            self._setActivePage(undefined);
            self.lastState = undefined;
        } else if (GlobalNavigation.isAppDesignerSection(activeSection)) {
            if (!previousSection) {
                // no-op, activation after the browser refresh waits for the page route initialization - BUFP-10046
            } else if (GlobalNavigation.isManagementSection(previousSection)) {
                // switching to newly opened application
                self.setActivePageId(self.getHomePageId());
            } else if (GlobalNavigation.isAppDesignerSection(previousSection)) {
                // switching designer - preview, no-op
            } else {
                // switching from other management section back to designer
                if (self.lastState && self.lastState.getActivePage()) {
                    var activePage = self.lastState.getActivePage();
                    RequireUtils.require(['core.dt/js/Router']).then(function(Router) {
                        Router.updateRouter({
                            doNotDispatchSignal: true,
                            replaceLatestState: true,
                            pageId: activePage.getId()
                        });
                        self.switchActivePage(activePage);
                    });
                } else {
                    self.setActivePageId(self.getHomePageId());
                }
            }
        } else if (GlobalNavigation.isAppSetupSection(activeSection)) {
            // switching between application setup panels
            self.switchActivePage(undefined);
        }
    };

    Navigation.LastState = function(previousPage, activePage, contextualData) {
        this.previousPage = previousPage;
        this.activePage = activePage;
        this.contextualData = contextualData;
    };

    Navigation.LastState.prototype.getPreviousPage = function() {
        return this.previousPage;
    };

    Navigation.LastState.prototype.getActivePage = function() {
        return this.activePage;
    };

    Navigation.LastState.prototype.getContextualData = function() {
        return this.contextualData;
    };

    return AbcsLib.initSingleton(Navigation);

});