framework7
Version:
Full featured mobile HTML framework for building iOS & Android apps
1,144 lines (1,056 loc) • 38 kB
JavaScript
import { window, document } from 'ssr-window';
import $ from 'dom7';
import PathToRegexp from 'path-to-regexp'; // eslint-disable-line
import Framework7Class from '../../utils/class';
import Utils from '../../utils/utils';
import History from '../../utils/history';
import SwipeBack from './swipe-back';
import { refreshPage, forward, load, navigate } from './navigate';
import { tabLoad, tabRemove } from './tab';
import { modalLoad, modalRemove } from './modal';
import { backward, loadBack, back } from './back';
import { clearPreviousHistory, clearPreviousPages } from './clear-previous-history';
import appRouterCheck from './app-router-check';
class Router extends Framework7Class {
constructor(app, view) {
super({}, [typeof view === 'undefined' ? app : view]);
const router = this;
// Is App Router
router.isAppRouter = typeof view === 'undefined';
if (router.isAppRouter) {
// App Router
Utils.extend(false, router, {
app,
params: app.params.view,
routes: app.routes || [],
cache: app.cache,
});
} else {
// View Router
Utils.extend(false, router, {
app,
view,
viewId: view.id,
params: view.params,
routes: view.routes,
$el: view.$el,
el: view.el,
$navbarEl: view.$navbarEl,
navbarEl: view.navbarEl,
history: view.history,
scrollHistory: view.scrollHistory,
cache: app.cache,
dynamicNavbar: app.theme === 'ios' && view.params.iosDynamicNavbar,
separateNavbar: app.theme === 'ios' && view.params.iosDynamicNavbar && view.params.iosSeparateDynamicNavbar,
initialPages: [],
initialNavbars: [],
});
}
// Install Modules
router.useModules();
// Temporary Dom
router.tempDom = document.createElement('div');
// AllowPageChage
router.allowPageChange = true;
// Current Route
let currentRoute = {};
let previousRoute = {};
Object.defineProperty(router, 'currentRoute', {
enumerable: true,
configurable: true,
set(newRoute = {}) {
previousRoute = Utils.extend({}, currentRoute);
currentRoute = newRoute;
if (!currentRoute) return;
router.url = currentRoute.url;
router.emit('routeChange', newRoute, previousRoute, router);
},
get() {
return currentRoute;
},
});
Object.defineProperty(router, 'previousRoute', {
enumerable: true,
configurable: true,
get() {
return previousRoute;
},
set(newRoute) {
previousRoute = newRoute;
},
});
return router;
}
animatableNavElements(newNavbarInner, oldNavbarInner, toLarge, fromLarge, direction) {
const router = this;
const dynamicNavbar = router.dynamicNavbar;
const separateNavbar = router.separateNavbar;
const animateIcon = router.params.iosAnimateNavbarBackIcon;
let newNavEls;
let oldNavEls;
function animatableNavEl($el, navbarInner) {
const isSliding = $el.hasClass('sliding') || navbarInner.hasClass('sliding');
const isSubnavbar = $el.hasClass('subnavbar');
const needsOpacityTransition = isSliding ? !isSubnavbar : true;
const $iconEl = $el.find('.back .icon');
let isIconLabel;
if (isSliding && animateIcon && $el.hasClass('left') && $iconEl.length > 0 && $iconEl.next('span').length) {
$el = $iconEl.next('span'); // eslint-disable-line
isIconLabel = true;
}
return {
$el,
isIconLabel,
leftOffset: $el[0].f7NavbarLeftOffset,
rightOffset: $el[0].f7NavbarRightOffset,
isSliding,
isSubnavbar,
needsOpacityTransition,
};
}
if (dynamicNavbar) {
newNavEls = [];
oldNavEls = [];
newNavbarInner.children('.left, .right, .title, .subnavbar').each((index, navEl) => {
const $navEl = $(navEl);
if ($navEl.hasClass('left') && fromLarge && direction === 'forward' && separateNavbar) return;
if ($navEl.hasClass('title') && toLarge) return;
newNavEls.push(animatableNavEl($navEl, newNavbarInner));
});
if (!(oldNavbarInner.hasClass('navbar-master') && router.params.masterDetailBreakpoint > 0 && router.app.width >= router.params.masterDetailBreakpoint)) {
oldNavbarInner.children('.left, .right, .title, .subnavbar').each((index, navEl) => {
const $navEl = $(navEl);
if ($navEl.hasClass('left') && toLarge && !fromLarge && direction === 'forward' && separateNavbar) return;
if ($navEl.hasClass('left') && toLarge && direction === 'backward' && separateNavbar) return;
if ($navEl.hasClass('title') && fromLarge) {
return;
}
oldNavEls.push(animatableNavEl($navEl, oldNavbarInner));
});
}
[oldNavEls, newNavEls].forEach((navEls) => {
navEls.forEach((navEl) => {
const n = navEl;
const { isSliding, $el } = navEl;
const otherEls = navEls === oldNavEls ? newNavEls : oldNavEls;
if (!(isSliding && $el.hasClass('title') && otherEls)) return;
otherEls.forEach((otherNavEl) => {
if (otherNavEl.isIconLabel) {
const iconTextEl = otherNavEl.$el[0];
n.leftOffset += iconTextEl ? (iconTextEl.offsetLeft || 0) : 0;
}
});
});
});
}
return { newNavEls, oldNavEls };
}
animate(oldPage, newPage, oldNavbarInner, newNavbarInner, direction, callback) {
const router = this;
if (router.params.animateCustom) {
router.params.animateCustom.apply(router, [oldPage, newPage, oldNavbarInner, newNavbarInner, direction, callback]);
return;
}
const dynamicNavbar = router.dynamicNavbar;
const ios = router.app.theme === 'ios';
// Router Animation class
const routerTransitionClass = `router-transition-${direction} router-transition`;
let newNavEls;
let oldNavEls;
let fromLarge;
let toLarge;
let oldIsLarge;
let newIsLarge;
if (ios && dynamicNavbar) {
oldIsLarge = oldNavbarInner && oldNavbarInner.hasClass('navbar-inner-large');
newIsLarge = newNavbarInner && newNavbarInner.hasClass('navbar-inner-large');
fromLarge = oldIsLarge && !oldNavbarInner.hasClass('navbar-inner-large-collapsed');
toLarge = newIsLarge && !newNavbarInner.hasClass('navbar-inner-large-collapsed');
const navEls = router.animatableNavElements(newNavbarInner, oldNavbarInner, toLarge, fromLarge, direction);
newNavEls = navEls.newNavEls;
oldNavEls = navEls.oldNavEls;
}
function animateNavbars(progress) {
if (!(ios && dynamicNavbar)) return;
if (progress === 1) {
if (toLarge) {
newNavbarInner.addClass('router-navbar-transition-to-large');
oldNavbarInner.addClass('router-navbar-transition-to-large');
}
if (fromLarge) {
newNavbarInner.addClass('router-navbar-transition-from-large');
oldNavbarInner.addClass('router-navbar-transition-from-large');
}
}
newNavEls.forEach((navEl) => {
const $el = navEl.$el;
const offset = direction === 'forward' ? navEl.rightOffset : navEl.leftOffset;
if (navEl.isSliding) {
if (navEl.isSubnavbar && newIsLarge) {
$el[0].style.setProperty('transform', `translate3d(${offset * (1 - progress)}px, calc(-1 * var(--f7-navbar-large-collapse-progress) * var(--f7-navbar-large-title-height)), 0)`, 'important');
} else {
$el.transform(`translate3d(${offset * (1 - progress)}px,0,0)`);
}
}
});
oldNavEls.forEach((navEl) => {
const $el = navEl.$el;
const offset = direction === 'forward' ? navEl.leftOffset : navEl.rightOffset;
if (navEl.isSliding) {
if (navEl.isSubnavbar && oldIsLarge) {
$el.transform(`translate3d(${offset * (progress)}px, calc(-1 * var(--f7-navbar-large-collapse-progress) * var(--f7-navbar-large-title-height)), 0)`);
} else {
$el.transform(`translate3d(${offset * (progress)}px,0,0)`);
}
}
});
}
// AnimationEnd Callback
function onDone() {
if (router.dynamicNavbar) {
if (newNavbarInner) {
newNavbarInner.removeClass('router-navbar-transition-to-large router-navbar-transition-from-large');
newNavbarInner.addClass('navbar-no-title-large-transition');
Utils.nextFrame(() => {
newNavbarInner.removeClass('navbar-no-title-large-transition');
});
}
if (oldNavbarInner) {
oldNavbarInner.removeClass('router-navbar-transition-to-large router-navbar-transition-from-large');
}
if (newNavbarInner.hasClass('sliding')) {
newNavbarInner.find('.title, .left, .right, .left .icon, .subnavbar').transform('');
} else {
newNavbarInner.find('.sliding').transform('');
}
if (oldNavbarInner.hasClass('sliding')) {
oldNavbarInner.find('.title, .left, .right, .left .icon, .subnavbar').transform('');
} else {
oldNavbarInner.find('.sliding').transform('');
}
}
router.$el.removeClass(routerTransitionClass);
if (callback) callback();
}
(direction === 'forward' ? newPage : oldPage).animationEnd(() => {
onDone();
});
// Animate
if (dynamicNavbar) {
// Prepare Navbars
animateNavbars(0);
Utils.nextFrame(() => {
// Add class, start animation
animateNavbars(1);
router.$el.addClass(routerTransitionClass);
});
} else {
// Add class, start animation
router.$el.addClass(routerTransitionClass);
}
}
removeModal(modalEl) {
const router = this;
router.removeEl(modalEl);
}
// eslint-disable-next-line
removeTabContent(tabEl) {
const $tabEl = $(tabEl);
$tabEl.html('');
}
removeNavbar(el) {
const router = this;
router.removeEl(el);
}
removePage(el) {
const $el = $(el);
const f7Page = $el && $el[0] && $el[0].f7Page;
const router = this;
if (f7Page && f7Page.route && f7Page.route.route && f7Page.route.route.keepAlive) {
$el.remove();
return;
}
router.removeEl(el);
}
removeEl(el) {
if (!el) return;
const router = this;
const $el = $(el);
if ($el.length === 0) return;
$el.find('.tab').each((tabIndex, tabEl) => {
$(tabEl).children().each((index, tabChild) => {
if (tabChild.f7Component) {
$(tabChild).trigger('tab:beforeremove');
tabChild.f7Component.$destroy();
}
});
});
if ($el[0].f7Component && $el[0].f7Component.$destroy) {
$el[0].f7Component.$destroy();
}
if (!router.params.removeElements) {
return;
}
if (router.params.removeElementsWithTimeout) {
setTimeout(() => {
$el.remove();
}, router.params.removeElementsTimeout);
} else {
$el.remove();
}
}
getPageEl(content) {
const router = this;
if (typeof content === 'string') {
router.tempDom.innerHTML = content;
} else {
if ($(content).hasClass('page')) {
return content;
}
router.tempDom.innerHTML = '';
$(router.tempDom).append(content);
}
return router.findElement('.page', router.tempDom);
}
findElement(stringSelector, container, notStacked) {
const router = this;
const view = router.view;
const app = router.app;
// Modals Selector
const modalsSelector = '.popup, .dialog, .popover, .actions-modal, .sheet-modal, .login-screen, .page';
const $container = $(container);
let selector = stringSelector;
if (notStacked) selector += ':not(.stacked)';
let found = $container
.find(selector)
.filter((index, el) => $(el).parents(modalsSelector).length === 0);
if (found.length > 1) {
if (typeof view.selector === 'string') {
// Search in related view
found = $container.find(`${view.selector} ${selector}`);
}
if (found.length > 1) {
// Search in main view
found = $container.find(`.${app.params.viewMainClass} ${selector}`);
}
}
if (found.length === 1) return found;
// Try to find not stacked
if (!notStacked) found = router.findElement(selector, $container, true);
if (found && found.length === 1) return found;
if (found && found.length > 1) return $(found[0]);
return undefined;
}
flattenRoutes(routes = this.routes) {
const router = this;
let flattenedRoutes = [];
routes.forEach((route) => {
let hasTabRoutes = false;
if ('tabs' in route && route.tabs) {
const mergedPathsRoutes = route.tabs.map((tabRoute) => {
const tRoute = Utils.extend({}, route, {
path: (`${route.path}/${tabRoute.path}`).replace('///', '/').replace('//', '/'),
parentPath: route.path,
tab: tabRoute,
});
delete tRoute.tabs;
delete tRoute.routes;
return tRoute;
});
hasTabRoutes = true;
flattenedRoutes = flattenedRoutes.concat(router.flattenRoutes(mergedPathsRoutes));
}
if ('detailRoutes' in route) {
const mergedPathsRoutes = route.detailRoutes.map((detailRoute) => {
const dRoute = Utils.extend({}, detailRoute);
dRoute.masterRoute = route;
dRoute.masterRoutePath = route.path;
return dRoute;
});
flattenedRoutes = flattenedRoutes.concat(route, router.flattenRoutes(mergedPathsRoutes));
}
if ('routes' in route) {
const mergedPathsRoutes = route.routes.map((childRoute) => {
const cRoute = Utils.extend({}, childRoute);
cRoute.path = (`${route.path}/${cRoute.path}`).replace('///', '/').replace('//', '/');
return cRoute;
});
if (hasTabRoutes) {
flattenedRoutes = flattenedRoutes.concat(router.flattenRoutes(mergedPathsRoutes));
} else {
flattenedRoutes = flattenedRoutes.concat(route, router.flattenRoutes(mergedPathsRoutes));
}
}
if (!('routes' in route) && !('tabs' in route && route.tabs) && !('detailRoutes' in route)) {
flattenedRoutes.push(route);
}
});
return flattenedRoutes;
}
// eslint-disable-next-line
parseRouteUrl(url) {
if (!url) return {};
const query = Utils.parseUrlQuery(url);
const hash = url.split('#')[1];
const params = {};
const path = url.split('#')[0].split('?')[0];
return {
query,
hash,
params,
url,
path,
};
}
// eslint-disable-next-line
constructRouteUrl(route, { params, query } = {}) {
const { path } = route;
const toUrl = PathToRegexp.compile(path);
let url;
try {
url = toUrl(params || {});
} catch (error) {
throw new Error(`Framework7: error constructing route URL from passed params:\nRoute: ${path}\n${error.toString()}`);
}
if (query) {
if (typeof query === 'string') url += `?${query}`;
else url += `?${Utils.serializeObject(query)}`;
}
return url;
}
findTabRoute(tabEl) {
const router = this;
const $tabEl = $(tabEl);
const parentPath = router.currentRoute.route.parentPath;
const tabId = $tabEl.attr('id');
const flattenedRoutes = router.flattenRoutes(router.routes);
let foundTabRoute;
flattenedRoutes.forEach((route) => {
if (
route.parentPath === parentPath
&& route.tab
&& route.tab.id === tabId
) {
foundTabRoute = route;
}
});
return foundTabRoute;
}
findRouteByKey(key, value) {
const router = this;
const routes = router.routes;
const flattenedRoutes = router.flattenRoutes(routes);
let matchingRoute;
flattenedRoutes.forEach((route) => {
if (matchingRoute) return;
if (route[key] === value) {
matchingRoute = route;
}
});
return matchingRoute;
}
findMatchingRoute(url) {
if (!url) return undefined;
const router = this;
const routes = router.routes;
const flattenedRoutes = router.flattenRoutes(routes);
const { path, query, hash, params } = router.parseRouteUrl(url);
let matchingRoute;
flattenedRoutes.forEach((route) => {
if (matchingRoute) return;
const keys = [];
const pathsToMatch = [route.path];
if (route.alias) {
if (typeof route.alias === 'string') pathsToMatch.push(route.alias);
else if (Array.isArray(route.alias)) {
route.alias.forEach((aliasPath) => {
pathsToMatch.push(aliasPath);
});
}
}
let matched;
pathsToMatch.forEach((pathToMatch) => {
if (matched) return;
matched = PathToRegexp(pathToMatch, keys).exec(path);
});
if (matched) {
keys.forEach((keyObj, index) => {
if (typeof keyObj.name === 'number') return;
const paramValue = matched[index + 1];
if (typeof paramValue === 'undefined' || paramValue === null) {
params[keyObj.name] = paramValue;
} else {
params[keyObj.name] = decodeURIComponent(paramValue);
}
});
let parentPath;
if (route.parentPath) {
parentPath = path.split('/').slice(0, route.parentPath.split('/').length - 1).join('/');
}
matchingRoute = {
query,
hash,
params,
url,
path,
parentPath,
route,
name: route.name,
};
}
});
return matchingRoute;
}
// eslint-disable-next-line
replaceRequestUrlParams(url = '', options = {}) {
let compiledUrl = url;
if (typeof compiledUrl === 'string'
&& compiledUrl.indexOf('{{') >= 0
&& options
&& options.route
&& options.route.params
&& Object.keys(options.route.params).length
) {
Object.keys(options.route.params).forEach((paramName) => {
const regExp = new RegExp(`{{${paramName}}}`, 'g');
compiledUrl = compiledUrl.replace(regExp, options.route.params[paramName] || '');
});
}
return compiledUrl;
}
removeFromXhrCache(url) {
const router = this;
const xhrCache = router.cache.xhr;
let index = false;
for (let i = 0; i < xhrCache.length; i += 1) {
if (xhrCache[i].url === url) index = i;
}
if (index !== false) xhrCache.splice(index, 1);
}
xhrRequest(requestUrl, options) {
const router = this;
const params = router.params;
const { ignoreCache } = options;
let url = requestUrl;
let hasQuery = url.indexOf('?') >= 0;
if (params.passRouteQueryToRequest
&& options
&& options.route
&& options.route.query
&& Object.keys(options.route.query).length
) {
url += `${hasQuery ? '&' : '?'}${Utils.serializeObject(options.route.query)}`;
hasQuery = true;
}
if (params.passRouteParamsToRequest
&& options
&& options.route
&& options.route.params
&& Object.keys(options.route.params).length
) {
url += `${hasQuery ? '&' : '?'}${Utils.serializeObject(options.route.params)}`;
hasQuery = true;
}
if (url.indexOf('{{') >= 0) {
url = router.replaceRequestUrlParams(url, options);
}
// should we ignore get params or not
if (params.xhrCacheIgnoreGetParameters && url.indexOf('?') >= 0) {
url = url.split('?')[0];
}
return new Promise((resolve, reject) => {
if (params.xhrCache && !ignoreCache && url.indexOf('nocache') < 0 && params.xhrCacheIgnore.indexOf(url) < 0) {
for (let i = 0; i < router.cache.xhr.length; i += 1) {
const cachedUrl = router.cache.xhr[i];
if (cachedUrl.url === url) {
// Check expiration
if (Utils.now() - cachedUrl.time < params.xhrCacheDuration) {
// Load from cache
resolve(cachedUrl.content);
return;
}
}
}
}
router.xhr = router.app.request({
url,
method: 'GET',
beforeSend(xhr) {
router.emit('routerAjaxStart', xhr, options);
},
complete(xhr, status) {
router.emit('routerAjaxComplete', xhr);
if ((status !== 'error' && status !== 'timeout' && (xhr.status >= 200 && xhr.status < 300)) || xhr.status === 0) {
if (params.xhrCache && xhr.responseText !== '') {
router.removeFromXhrCache(url);
router.cache.xhr.push({
url,
time: Utils.now(),
content: xhr.responseText,
});
}
router.emit('routerAjaxSuccess', xhr, options);
resolve(xhr.responseText);
} else {
router.emit('routerAjaxError', xhr, options);
reject(xhr);
}
},
error(xhr) {
router.emit('routerAjaxError', xhr, options);
reject(xhr);
},
});
});
}
// Remove theme elements
removeThemeElements(el) {
const router = this;
const theme = router.app.theme;
let toRemove;
if (theme === 'ios') {
toRemove = '.md-only, .aurora-only, .if-md, .if-aurora, .if-not-ios, .not-ios';
} else if (theme === 'md') {
toRemove = '.ios-only, .aurora-only, .if-ios, .if-aurora, .if-not-md, .not-md';
} else if (theme === 'aurora') {
toRemove = '.ios-only, .md-only, .if-ios, .if-md, .if-not-aurora, .not-aurora';
}
$(el).find(toRemove).remove();
}
getPageData(pageEl, navbarEl, from, to, route = {}, pageFromEl) {
const router = this;
const $pageEl = $(pageEl).eq(0);
const $navbarEl = $(navbarEl).eq(0);
const currentPage = $pageEl[0].f7Page || {};
let direction;
let pageFrom;
if ((from === 'next' && to === 'current') || (from === 'current' && to === 'previous')) direction = 'forward';
if ((from === 'current' && to === 'next') || (from === 'previous' && to === 'current')) direction = 'backward';
if (currentPage && !currentPage.fromPage) {
const $pageFromEl = $(pageFromEl);
if ($pageFromEl.length) {
pageFrom = $pageFromEl[0].f7Page;
}
}
pageFrom = currentPage.pageFrom || pageFrom;
if (pageFrom && pageFrom.pageFrom) {
pageFrom.pageFrom = null;
}
const page = {
app: router.app,
view: router.view,
router,
$el: $pageEl,
el: $pageEl[0],
$pageEl,
pageEl: $pageEl[0],
$navbarEl,
navbarEl: $navbarEl[0],
name: $pageEl.attr('data-name'),
position: from,
from,
to,
direction,
route: currentPage.route ? currentPage.route : route,
pageFrom,
};
$pageEl[0].f7Page = page;
return page;
}
// Callbacks
pageCallback(callback, pageEl, navbarEl, from, to, options = {}, pageFromEl) {
if (!pageEl) return;
const router = this;
const $pageEl = $(pageEl);
if (!$pageEl.length) return;
const $navbarEl = $(navbarEl);
const { route } = options;
const restoreScrollTopOnBack = router.params.restoreScrollTopOnBack
&& !(
router.params.masterDetailBreakpoint > 0
&& $pageEl.hasClass('page-master')
&& router.app.width >= router.params.masterDetailBreakpoint
);
const keepAlive = $pageEl[0].f7Page && $pageEl[0].f7Page.route && $pageEl[0].f7Page.route.route && $pageEl[0].f7Page.route.route.keepAlive;
if (callback === 'beforeRemove' && keepAlive) {
callback = 'beforeUnmount'; // eslint-disable-line
}
const camelName = `page${callback[0].toUpperCase() + callback.slice(1, callback.length)}`;
const colonName = `page:${callback.toLowerCase()}`;
let page = {};
if (callback === 'beforeRemove' && $pageEl[0].f7Page) {
page = Utils.extend($pageEl[0].f7Page, { from, to, position: from });
} else {
page = router.getPageData($pageEl[0], $navbarEl[0], from, to, route, pageFromEl);
}
page.swipeBack = !!options.swipeBack;
const { on = {}, once = {} } = options.route ? options.route.route : {};
if (options.on) {
Utils.extend(on, options.on);
}
if (options.once) {
Utils.extend(once, options.once);
}
function attachEvents() {
if ($pageEl[0].f7RouteEventsAttached) return;
$pageEl[0].f7RouteEventsAttached = true;
if (on && Object.keys(on).length > 0) {
$pageEl[0].f7RouteEventsOn = on;
Object.keys(on).forEach((eventName) => {
on[eventName] = on[eventName].bind(router);
$pageEl.on(Utils.eventNameToColonCase(eventName), on[eventName]);
});
}
if (once && Object.keys(once).length > 0) {
$pageEl[0].f7RouteEventsOnce = once;
Object.keys(once).forEach((eventName) => {
once[eventName] = once[eventName].bind(router);
$pageEl.once(Utils.eventNameToColonCase(eventName), once[eventName]);
});
}
}
function detachEvents() {
if (!$pageEl[0].f7RouteEventsAttached) return;
if ($pageEl[0].f7RouteEventsOn) {
Object.keys($pageEl[0].f7RouteEventsOn).forEach((eventName) => {
$pageEl.off(Utils.eventNameToColonCase(eventName), $pageEl[0].f7RouteEventsOn[eventName]);
});
}
if ($pageEl[0].f7RouteEventsOnce) {
Object.keys($pageEl[0].f7RouteEventsOnce).forEach((eventName) => {
$pageEl.off(Utils.eventNameToColonCase(eventName), $pageEl[0].f7RouteEventsOnce[eventName]);
});
}
$pageEl[0].f7RouteEventsAttached = null;
$pageEl[0].f7RouteEventsOn = null;
$pageEl[0].f7RouteEventsOnce = null;
delete $pageEl[0].f7RouteEventsAttached;
delete $pageEl[0].f7RouteEventsOn;
delete $pageEl[0].f7RouteEventsOnce;
}
if (callback === 'mounted') {
attachEvents();
}
if (callback === 'init') {
if (restoreScrollTopOnBack && (from === 'previous' || !from) && to === 'current' && router.scrollHistory[page.route.url] && !$pageEl.hasClass('no-restore-scroll')) {
let $pageContent = $pageEl.find('.page-content');
if ($pageContent.length > 0) {
// eslint-disable-next-line
$pageContent = $pageContent.filter((pageContentIndex, pageContentEl) => {
return (
$(pageContentEl).parents('.tab:not(.tab-active)').length === 0
&& !$(pageContentEl).is('.tab:not(.tab-active)')
);
});
}
$pageContent.scrollTop(router.scrollHistory[page.route.url]);
}
attachEvents();
if ($pageEl[0].f7PageInitialized) {
$pageEl.trigger('page:reinit', page);
router.emit('pageReinit', page);
return;
}
$pageEl[0].f7PageInitialized = true;
}
if (restoreScrollTopOnBack && callback === 'beforeOut' && from === 'current' && to === 'previous') {
// Save scroll position
let $pageContent = $pageEl.find('.page-content');
if ($pageContent.length > 0) {
// eslint-disable-next-line
$pageContent = $pageContent.filter((pageContentIndex, pageContentEl) => {
return (
$(pageContentEl).parents('.tab:not(.tab-active)').length === 0
&& !$(pageContentEl).is('.tab:not(.tab-active)')
);
});
}
router.scrollHistory[page.route.url] = $pageContent.scrollTop();
}
if (restoreScrollTopOnBack && callback === 'beforeOut' && from === 'current' && to === 'next') {
// Delete scroll position
delete router.scrollHistory[page.route.url];
}
$pageEl.trigger(colonName, page);
router.emit(camelName, page);
if (callback === 'beforeRemove' || callback === 'beforeUnmount') {
detachEvents();
if (!keepAlive) {
if ($pageEl[0].f7Page && $pageEl[0].f7Page.navbarEl) {
delete $pageEl[0].f7Page.navbarEl.f7Page;
}
$pageEl[0].f7Page = null;
}
}
}
saveHistory() {
const router = this;
router.view.history = router.history;
if (router.params.pushState) {
window.localStorage[`f7router-${router.view.id}-history`] = JSON.stringify(router.history);
}
}
restoreHistory() {
const router = this;
if (router.params.pushState && window.localStorage[`f7router-${router.view.id}-history`]) {
router.history = JSON.parse(window.localStorage[`f7router-${router.view.id}-history`]);
router.view.history = router.history;
}
}
clearHistory() {
const router = this;
router.history = [];
if (router.view) router.view.history = [];
router.saveHistory();
}
updateCurrentUrl(newUrl) {
const router = this;
appRouterCheck(router, 'updateCurrentUrl');
// Update history
if (router.history.length) {
router.history[router.history.length - 1] = newUrl;
} else {
router.history.push(newUrl);
}
// Update current route params
const { query, hash, params, url, path } = router.parseRouteUrl(newUrl);
if (router.currentRoute) {
Utils.extend(router.currentRoute, {
query,
hash,
params,
url,
path,
});
}
if (router.params.pushState) {
const pushStateRoot = router.params.pushStateRoot || '';
History.replace(
router.view.id,
{
url: newUrl,
},
pushStateRoot + router.params.pushStateSeparator + newUrl
);
}
// Save History
router.saveHistory();
router.emit('routeUrlUpdate', router.currentRoute, router);
}
init() {
const router = this;
const { app, view } = router;
// Init Swipeback
if ("universal" !== 'desktop') {
if (
(view && router.params.iosSwipeBack && app.theme === 'ios')
|| (view && router.params.mdSwipeBack && app.theme === 'md')
|| (view && router.params.auroraSwipeBack && app.theme === 'aurora')
) {
SwipeBack(router);
}
}
// Dynamic not separated navbbar
if (router.dynamicNavbar && !router.separateNavbar) {
router.$el.addClass('router-dynamic-navbar-inside');
}
let initUrl = router.params.url;
let documentUrl = document.location.href.split(document.location.origin)[1];
let historyRestored;
const { pushState, pushStateOnLoad, pushStateSeparator, pushStateAnimateOnLoad } = router.params;
let { pushStateRoot } = router.params;
if (window.cordova && pushState && !pushStateSeparator && !pushStateRoot && document.location.pathname.indexOf('index.html')) {
// eslint-disable-next-line
console.warn('Framework7: wrong or not complete pushState configuration, trying to guess pushStateRoot');
pushStateRoot = document.location.pathname.split('index.html')[0];
}
if (!pushState || !pushStateOnLoad) {
if (!initUrl) {
initUrl = documentUrl;
}
if (document.location.search && initUrl.indexOf('?') < 0) {
initUrl += document.location.search;
}
if (document.location.hash && initUrl.indexOf('#') < 0) {
initUrl += document.location.hash;
}
} else {
if (pushStateRoot && documentUrl.indexOf(pushStateRoot) >= 0) {
documentUrl = documentUrl.split(pushStateRoot)[1];
if (documentUrl === '') documentUrl = '/';
}
if (pushStateSeparator.length > 0 && documentUrl.indexOf(pushStateSeparator) >= 0) {
initUrl = documentUrl.split(pushStateSeparator)[1];
} else {
initUrl = documentUrl;
}
router.restoreHistory();
if (router.history.indexOf(initUrl) >= 0) {
router.history = router.history.slice(0, router.history.indexOf(initUrl) + 1);
} else if (router.params.url === initUrl) {
router.history = [initUrl];
} else if (History.state && History.state[view.id] && History.state[view.id].url === router.history[router.history.length - 1]) {
initUrl = router.history[router.history.length - 1];
} else {
router.history = [documentUrl.split(pushStateSeparator)[0] || '/', initUrl];
}
if (router.history.length > 1) {
historyRestored = true;
} else {
router.history = [];
}
router.saveHistory();
}
let currentRoute;
if (router.history.length > 1) {
// Will load page
currentRoute = router.findMatchingRoute(router.history[0]);
if (!currentRoute) {
currentRoute = Utils.extend(router.parseRouteUrl(router.history[0]), {
route: {
url: router.history[0],
path: router.history[0].split('?')[0],
},
});
}
} else {
// Don't load page
currentRoute = router.findMatchingRoute(initUrl);
if (!currentRoute) {
currentRoute = Utils.extend(router.parseRouteUrl(initUrl), {
route: {
url: initUrl,
path: initUrl.split('?')[0],
},
});
}
}
if (router.params.stackPages) {
router.$el.children('.page').each((index, pageEl) => {
const $pageEl = $(pageEl);
router.initialPages.push($pageEl[0]);
if (router.separateNavbar && $pageEl.children('.navbar').length > 0) {
router.initialNavbars.push($pageEl.children('.navbar').find('.navbar-inner')[0]);
}
});
}
if (router.$el.children('.page:not(.stacked)').length === 0 && initUrl) {
// No pages presented in DOM, reload new page
router.navigate(initUrl, {
initial: true,
reloadCurrent: true,
pushState: false,
});
} else {
// Init current DOM page
let hasTabRoute;
router.currentRoute = currentRoute;
router.$el.children('.page:not(.stacked)').each((index, pageEl) => {
const $pageEl = $(pageEl);
let $navbarInnerEl;
$pageEl.addClass('page-current');
if (router.separateNavbar) {
$navbarInnerEl = $pageEl.children('.navbar').children('.navbar-inner');
if ($navbarInnerEl.length > 0) {
if (!router.$navbarEl.parents(document).length) {
router.$el.prepend(router.$navbarEl);
}
$navbarInnerEl.addClass('navbar-current');
router.$navbarEl.append($navbarInnerEl);
if ($navbarInnerEl.children('.title-large').length) {
$navbarInnerEl.addClass('navbar-inner-large');
}
$pageEl.children('.navbar').remove();
} else {
router.$navbarEl.addClass('navbar-hidden');
if ($navbarInnerEl.children('.title-large').length) {
router.$navbarEl.addClass('navbar-hidden navbar-large-hidden');
}
}
}
if (router.currentRoute && router.currentRoute.route && router.currentRoute.route.master && router.params.masterDetailBreakpoint > 0) {
$pageEl.addClass('page-master');
$pageEl.trigger('page:role', { role: 'master' });
if ($navbarInnerEl && $navbarInnerEl.length) {
$navbarInnerEl.addClass('navbar-master');
}
}
const initOptions = {
route: router.currentRoute,
};
if (router.currentRoute && router.currentRoute.route && router.currentRoute.route.options) {
Utils.extend(initOptions, router.currentRoute.route.options);
}
router.currentPageEl = $pageEl[0];
if (router.separateNavbar && $navbarInnerEl.length) {
router.currentNavbarEl = $navbarInnerEl[0];
}
router.removeThemeElements($pageEl);
if (router.separateNavbar && $navbarInnerEl.length) {
router.removeThemeElements($navbarInnerEl);
}
if (initOptions.route.route.tab) {
hasTabRoute = true;
router.tabLoad(initOptions.route.route.tab, Utils.extend({}, initOptions));
}
router.pageCallback('init', $pageEl, $navbarInnerEl, 'current', undefined, initOptions);
});
if (historyRestored) {
router.navigate(initUrl, {
initial: true,
pushState: false,
history: false,
animate: pushStateAnimateOnLoad,
once: {
pageAfterIn() {
const preloadPreviousPage = router.params.preloadPreviousPage || router.params[`${app.theme}SwipeBack`];
if (preloadPreviousPage && router.history.length > 2) {
router.back({ preload: true });
}
},
},
});
}
if (!historyRestored && !hasTabRoute) {
router.history.push(initUrl);
router.saveHistory();
}
}
if (initUrl && pushState && pushStateOnLoad && (!History.state || !History.state[view.id])) {
History.initViewState(view.id, {
url: initUrl,
});
}
router.emit('local::init routerInit', router);
}
destroy() {
let router = this;
router.emit('local::destroy routerDestroy', router);
// Delete props & methods
Object.keys(router).forEach((routerProp) => {
router[routerProp] = null;
delete router[routerProp];
});
router = null;
}
}
// Load
Router.prototype.forward = forward;
Router.prototype.load = load;
Router.prototype.navigate = navigate;
Router.prototype.refreshPage = refreshPage;
// Tab
Router.prototype.tabLoad = tabLoad;
Router.prototype.tabRemove = tabRemove;
// Modal
Router.prototype.modalLoad = modalLoad;
Router.prototype.modalRemove = modalRemove;
// Back
Router.prototype.backward = backward;
Router.prototype.loadBack = loadBack;
Router.prototype.back = back;
// Clear previoius pages from the DOM
Router.prototype.clearPreviousPages = clearPreviousPages;
// Clear history
Router.prototype.clearPreviousHistory = clearPreviousHistory;
export default Router;