/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
/** @module router */
import Backbone from 'backbone';
/**
* On route.
*
* @event Backbone.Router#route
* @param {string} name A route name.
* @param {any[]} args Arguments.
*/
/**
* After dispatch.
*
* @event module:router#routed
* @param {{
* controller: string,
* action:string,
* options: Object.,
* }} data A route data.
*/
/**
* Subscribe.
*
* @function on
* @memberof module:router#
* @param {string} event An event.
* @param {function(*): void} callback A callback.
*/
/**
* Subscribe once.
*
* @function once
* @memberof module:router#
* @param {string} event An event.
* @param {function(): void} callback A callback.
*/
/**
* Unsubscribe.
*
* @function off
* @memberof module:router#
* @param {string} event An event.
*/
/**
* Trigger an event.
*
* @function trigger
* @memberof module:router#
* @param {string} event An event.
*/
// noinspection JSUnusedGlobalSymbols
/**
* A router.
*
* @class
* @mixes Espo.Events
*/
const Router = Backbone.Router.extend(/** @lends Router# */ {
/**
* @private
*/
routeList: [
{
route: "clearCache",
resolution: "clearCache"
},
{
route: ":controller/view/:id/:options",
resolution: "view"
},
{
route: ":controller/view/:id",
resolution: "view"
},
{
route: ":controller/edit/:id/:options",
resolution: "edit"
},
{
route: ":controller/edit/:id",
resolution: "edit"
},
{
route: ":controller/create",
resolution: "create"
},
{
route: ":controller/related/:id/:link",
resolution: "related"
},
{
route: ":controller/:action/:options",
resolution: "action",
order: 100
},
{
route: ":controller/:action",
resolution: "action",
order: 200
},
{
route: ":controller",
resolution: "defaultAction",
order: 300
},
{
route: "*actions",
resolution: "home",
order: 500
},
],
/**
* @private
*/
_bindRoutes: function() {},
/**
* @private
*/
setupRoutes: function () {
this.routeParams = {};
if (this.options.routes) {
const routeList = [];
Object.keys(this.options.routes).forEach(route => {
const item = this.options.routes[route];
routeList.push({
route: route,
resolution: item.resolution || 'defaultRoute',
order: item.order || 0
});
this.routeParams[route] = item.params || {};
});
this.routeList = Espo.Utils.clone(this.routeList);
routeList.forEach(item => {
this.routeList.push(item);
});
this.routeList = this.routeList.sort((v1, v2) => {
return (v1.order || 0) - (v2.order || 0);
});
}
this.routeList.reverse().forEach(item => {
this.route(item.route, item.resolution);
});
},
/**
* @private
*/
_last: null,
/**
* Whether a confirm-leave-out was set.
*
* @public
* @type {boolean}
*/
confirmLeaveOut: false,
/**
* Whether back has been processed.
*
* @public
* @type {boolean}
*/
backProcessed: false,
/**
* @type {string}
* @internal
*/
confirmLeaveOutMessage: 'Are you sure?',
/**
* @type {string}
* @internal
*/
confirmLeaveOutConfirmText: 'Yes',
/**
* @type {string}
* @internal
*/
confirmLeaveOutCancelText: 'No',
/**
* @private
*/
initialize: function (options) {
this.options = options || {};
this.setupRoutes();
this._isReturn = false;
this.history = [];
let hashHistory = [window.location.hash];
window.addEventListener('hashchange', () => {
const hash = window.location.hash;
if (
hashHistory.length > 1 &&
hashHistory[hashHistory.length - 2] === hash
) {
hashHistory = hashHistory.slice(0, -1);
this.backProcessed = true;
setTimeout(() => this.backProcessed = false, 50);
return;
}
hashHistory.push(hash);
});
this.on('route', () => {
this.history.push(Backbone.history.fragment);
});
window.addEventListener('beforeunload', event => {
event = event || window.event;
if (
this.confirmLeaveOut ||
this._leaveOutMap.size ||
this._windowLeaveOutMap.size
) {
event.preventDefault();
event.returnValue = this.confirmLeaveOutMessage;
return this.confirmLeaveOutMessage;
}
});
/**
* @private
* @type {Map