Skip to content

Instantly share code, notes, and snippets.

@russiann
Created September 21, 2017 02:24
Show Gist options
  • Save russiann/641c04c30874cc9696a909296c056492 to your computer and use it in GitHub Desktop.
Save russiann/641c04c30874cc9696a909296c056492 to your computer and use it in GitHub Desktop.
router-class.js
import $ from 'dom7';
import Template7 from 'template7';
import Framework7Class from '../../utils/class';
import Utils from '../../utils/utils';
import Component from '../../utils/component';
import SwipeBack from './swipe-back';
import { forward, load, navigate } from './navigate';
import { tabLoad, tabRemove } from './tab';
import { modalLoad, modalRemove } from './modal';
import { backward, loadBack, back } from './back';
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,
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;
},
});
Utils.extend(router, {
// Load
forward,
load,
navigate,
// Tab
tabLoad,
tabRemove,
// Modal
modalLoad,
modalRemove,
// Back
backward,
loadBack,
back,
});
return router;
}
animateWithCSS(oldPage, newPage, oldNavbarInner, newNavbarInner, direction, callback) {
const router = this;
// Router Animation class
const routerTransitionClass = `router-transition-${direction} router-transition-css-${direction}`;
// AnimationEnd Callback
(direction === 'forward' ? newPage : oldPage).animationEnd(() => {
if (router.dynamicNavbar) {
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();
});
function prepareNavbars() {
let slidingEls;
if (newNavbarInner.hasClass('sliding')) {
slidingEls = newNavbarInner.children('.left, .right, .title, .subnavbar');
} else {
slidingEls = newNavbarInner.find('.sliding');
}
if (!slidingEls) return;
let navbarWidth;
if (!router.separateNavbar) {
navbarWidth = newNavbarInner[0].offsetWidth;
}
let oldNavbarTitleEl;
if (oldNavbarInner.children('.title.sliding').length > 0) {
oldNavbarTitleEl = oldNavbarInner.children('.title.sliding');
} else {
oldNavbarTitleEl = oldNavbarInner.hasClass('sliding') && oldNavbarInner.children('.title');
}
slidingEls.each((index, slidingEl) => {
const $slidingEl = $(slidingEl);
const slidingOffset = direction === 'forward' ? slidingEl.f7NavbarRightOffset : slidingEl.f7NavbarLeftOffset;
if (router.params.iosAnimateNavbarBackIcon && $slidingEl.hasClass('left') && $slidingEl.find('.back .icon').length > 0) {
let iconSlidingOffset = -slidingOffset;
const iconTextEl = $slidingEl.find('.back span').eq(0);
if (!router.separateNavbar) {
if (direction === 'forward') {
iconSlidingOffset -= navbarWidth;
} else {
iconSlidingOffset += navbarWidth / 5;
}
}
$slidingEl.find('.back .icon').transform(`translate3d(${iconSlidingOffset}px,0,0)`);
if (oldNavbarTitleEl && iconTextEl.length > 0) {
oldNavbarTitleEl[0].f7NavbarLeftOffset += iconTextEl[0].offsetLeft;
}
}
$slidingEl.transform(`translate3d(${slidingOffset}px,0,0)`);
});
}
function animateNavbars() {
const animateIcon = router.params.iosAnimateNavbarBackIcon;
let navbarIconOffset = 0;
let oldNavbarWidth;
if (!router.separateNavbar && animateIcon) {
oldNavbarWidth = oldNavbarInner[0].offsetWidth;
if (direction === 'forward') {
navbarIconOffset = oldNavbarWidth / 5;
} else {
navbarIconOffset = -oldNavbarWidth;
}
}
// Old Navbar Sliding
let oldNavbarSlidingEls;
if (oldNavbarInner.hasClass('sliding')) {
oldNavbarSlidingEls = oldNavbarInner.children('.left, .right, .title, .subnavbar');
} else {
oldNavbarSlidingEls = oldNavbarInner.find('.sliding');
}
if (oldNavbarSlidingEls) {
oldNavbarSlidingEls.each((index, slidingEl) => {
const $slidingEl = $(slidingEl);
const offset = direction === 'forward' ? slidingEl.f7NavbarLeftOffset : slidingEl.f7NavbarRightOffset;
$slidingEl.transform(`translate3d(${offset}px,0,0)`);
if (animateIcon) {
if ($slidingEl.hasClass('left') && $slidingEl.find('.back .icon').length > 0) {
$slidingEl.find('.back .icon').transform(`translate3d(${-offset + navbarIconOffset}px,0,0)`);
}
}
});
}
}
if (router.dynamicNavbar) {
// Prepare Navbars
prepareNavbars();
Utils.nextTick(() => {
// Add class, start animation
animateNavbars();
router.$el.addClass(routerTransitionClass);
});
} else {
// Add class, start animation
router.$el.addClass(routerTransitionClass);
}
}
animateWithJS(oldPage, newPage, oldNavbarInner, newNavbarInner, direction, callback) {
const router = this;
const dynamicNavbar = router.dynamicNavbar;
const separateNavbar = router.separateNavbar;
const animateIcon = router.params.iosAnimateNavbarBackIcon;
const ios = router.app.theme === 'ios';
const duration = ios ? 400 : 250;
const routerTransitionClass = `router-transition-${direction} router-transition-js-${direction}`;
let startTime = null;
let done = false;
let newNavEls;
let oldNavEls;
let navbarWidth = 0;
function animatableNavEl(el, navbarInner) {
const $el = $(el);
const isSliding = $el.hasClass('sliding') || navbarInner.hasClass('sliding');
const isSubnavbar = $el.hasClass('subnavbar');
const needsOpacityTransition = isSliding ? !isSubnavbar : true;
const hasIcon = isSliding && animateIcon && $el.hasClass('left') && $el.find('.back .icon').length > 0;
let $iconEl;
if (hasIcon) $iconEl = $el.find('.back .icon');
return {
$el,
$iconEl,
hasIcon,
leftOffset: $el[0].f7NavbarLeftOffset,
rightOffset: $el[0].f7NavbarRightOffset,
isSliding,
isSubnavbar,
needsOpacityTransition,
};
}
if (dynamicNavbar) {
newNavEls = [];
oldNavEls = [];
newNavbarInner.children('.left, .right, .title, .subnavbar').each((index, navEl) => {
newNavEls.push(animatableNavEl(navEl, newNavbarInner));
});
oldNavbarInner.children('.left, .right, .title, .subnavbar').each((index, navEl) => {
oldNavEls.push(animatableNavEl(navEl, oldNavbarInner));
});
if (!separateNavbar) {
navbarWidth = newNavbarInner[0].offsetWidth;
}
[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.$el.hasClass('left') && otherNavEl.hasIcon) {
const iconTextEl = otherNavEl.$el.find('.back span')[0];
n.leftOffset += iconTextEl ? iconTextEl.offsetLeft : 0;
}
});
});
});
}
let $shadowEl;
let $opacityEl;
if (ios) {
$shadowEl = $('<div class="page-shadow-effect"></div>');
$opacityEl = $('<div class="page-opacity-effect"></div>');
if (direction === 'forward') {
newPage.append($shadowEl);
oldPage.append($opacityEl);
} else {
newPage.append($opacityEl);
oldPage.append($shadowEl);
}
}
const easing = Utils.bezier(0.25, 0.1, 0.25, 1);
function onDone() {
newPage.transform('').css('opacity', '');
oldPage.transform('').css('opacity', '');
if (ios) {
$shadowEl.remove();
$opacityEl.remove();
if (dynamicNavbar) {
newNavEls.forEach((navEl) => {
navEl.$el.transform('');
navEl.$el.css('opacity', '');
});
oldNavEls.forEach((navEl) => {
navEl.$el.transform('');
navEl.$el.css('opacity', '');
});
newNavEls = [];
oldNavEls = [];
}
}
router.$el.removeClass(routerTransitionClass);
if (callback) callback();
}
function render() {
const time = Utils.now();
if (!startTime) startTime = time;
const progress = Math.max(Math.min((time - startTime) / duration, 1), 0);
const easeProgress = easing(progress);
if (progress >= 1) {
done = true;
}
const inverter = router.app.rtl ? -1 : 1;
if (ios) {
if (direction === 'forward') {
newPage.transform(`translate3d(${(1 - easeProgress) * 100 * inverter}%,0,0)`);
oldPage.transform(`translate3d(${-easeProgress * 20 * inverter}%,0,0)`);
$shadowEl[0].style.opacity = easeProgress;
$opacityEl[0].style.opacity = easeProgress;
} else {
newPage.transform(`translate3d(${-(1 - easeProgress) * 20 * inverter}%,0,0)`);
oldPage.transform(`translate3d(${easeProgress * 100 * inverter}%,0,0)`);
$shadowEl[0].style.opacity = 1 - easeProgress;
$opacityEl[0].style.opacity = 1 - easeProgress;
}
if (dynamicNavbar) {
newNavEls.forEach((navEl) => {
const $el = navEl.$el;
const offset = direction === 'forward' ? navEl.rightOffset : navEl.leftOffset;
if (navEl.needsOpacityTransition) {
$el[0].style.opacity = easeProgress;
}
if (navEl.isSliding) {
$el.transform(`translate3d(${offset * (1 - easeProgress)}px,0,0)`);
}
if (navEl.hasIcon) {
if (direction === 'forward') {
navEl.$iconEl.transform(`translate3d(${(-offset - navbarWidth) * (1 - easeProgress)}px,0,0)`);
} else {
navEl.$iconEl.transform(`translate3d(${(-offset + (navbarWidth / 5)) * (1 - easeProgress)}px,0,0)`);
}
}
});
oldNavEls.forEach((navEl) => {
const $el = navEl.$el;
const offset = direction === 'forward' ? navEl.leftOffset : navEl.rightOffset;
if (navEl.needsOpacityTransition) {
$el[0].style.opacity = (1 - easeProgress);
}
if (navEl.isSliding) {
$el.transform(`translate3d(${offset * (easeProgress)}px,0,0)`);
}
if (navEl.hasIcon) {
if (direction === 'forward') {
navEl.$iconEl.transform(`translate3d(${(-offset + (navbarWidth / 5)) * (easeProgress)}px,0,0)`);
} else {
navEl.$iconEl.transform(`translate3d(${(-offset - navbarWidth) * (easeProgress)}px,0,0)`);
}
}
});
}
} else if (direction === 'forward') {
newPage.transform(`translate3d(0, ${(1 - easeProgress) * 56}px,0)`);
newPage.css('opacity', easeProgress);
} else {
oldPage.transform(`translate3d(0, ${easeProgress * 56}px,0)`);
oldPage.css('opacity', 1 - easeProgress);
}
if (done) {
onDone();
return;
}
Utils.nextFrame(render);
}
router.$el.addClass(routerTransitionClass);
Utils.nextFrame(render);
}
animate(...args) {
// Args: oldPage, newPage, oldNavbarInner, newNavbarInner, direction, callback
const router = this;
if (router.params.animateCustom) {
router.params.animateCustom.apply(router, args);
} else if (router.params.animateWithJS) {
router.animateWithJS(...args);
} else {
router.animateWithCSS(...args);
}
}
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 router = this;
router.removeEl(el);
}
removeEl(el) {
if (!el) return;
const router = this;
const $el = $(el);
if ($el.length === 0) return;
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) {
let flattenedRoutes = [];
routes.forEach((route) => {
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;
});
flattenedRoutes = flattenedRoutes.concat(route, this.flattenRoutes(mergedPathsRoutes));
} else 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;
return tRoute;
});
flattenedRoutes = flattenedRoutes.concat(this.flattenRoutes(mergedPathsRoutes));
} else {
flattenedRoutes.push(route);
}
});
return flattenedRoutes;
}
findMatchingRoute(url, parseOnly) {
if (!url) return undefined;
const router = this;
const routes = router.routes;
const flattenedRoutes = router.flattenRoutes(routes);
const query = Utils.parseUrlQuery(url);
const hash = url.split('#')[1];
const params = {};
const path = url.split('#')[0].split('?')[0];
const urlParts = path.split('/').filter(part => part !== '');
if (parseOnly) {
return {
query,
hash,
params,
url,
path,
};
}
let matchingRoute;
function parseRoute(str = '') {
const parts = [];
str.split('/').forEach((part) => {
if (part !== '') {
if (part.indexOf(':') === 0) {
parts.push({
name: part.replace(':', ''),
});
} else parts.push(part);
}
});
return parts;
}
flattenedRoutes.forEach((route) => {
if (matchingRoute) return;
const parsedRoute = parseRoute(route.path);
if (parsedRoute.length !== urlParts.length) return;
let matchedParts = 0;
parsedRoute.forEach((routePart, index) => {
if (typeof routePart === 'string' && urlParts[index] === routePart) {
matchedParts += 1;
}
if (typeof routePart === 'object') {
params[routePart.name] = urlParts[index];
matchedParts += 1;
}
});
if (matchedParts === urlParts.length) {
matchingRoute = {
query,
hash,
params,
url,
path,
route,
};
}
});
return matchingRoute;
}
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, ignoreCache) {
const router = this;
const params = router.params;
let url = requestUrl;
// should we ignore get params or not
if (params.xhrCacheIgnoreGetParameters && url.indexOf('?') >= 0) {
url = url.split('?')[0];
}
return Utils.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() {
router.emit('routerAjaxStart', router.xhr);
},
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);
resolve(xhr.responseText);
} else {
router.emit('routerAjaxError', xhr);
reject(xhr);
}
},
error(xhr) {
router.emit('routerAjaxError', xhr);
reject(xhr);
},
});
});
}
// Remove theme elements
removeThemeElements(el) {
const router = this;
const theme = router.app.theme;
$(el).find(`.${theme === 'md' ? 'ios' : 'md'}-only, .if-${theme === 'md' ? 'ios' : 'md'}`).remove();
}
templateLoader(template, templateUrl, options, resolve, reject) {
const router = this;
function compile(t) {
let compiledHtml;
let context;
try {
context = options.context || {};
if (typeof context === 'function') context = context.call(router);
else if (typeof context === 'string') {
try {
context = JSON.parse(context);
} catch (err) {
reject();
throw (err);
}
}
if (typeof t === 'function') {
compiledHtml = t(context);
} else {
compiledHtml = Template7.compile(t)(Utils.extend({}, context || {}, {
$app: router.app,
$root: Utils.extend({}, router.app.data, router.app.methods),
$route: options.route,
$router: router,
$theme: {
ios: router.app.theme === 'ios',
md: router.app.theme === 'md',
},
}));
}
} catch (err) {
reject();
throw (err);
}
resolve(compiledHtml, { context });
}
if (templateUrl) {
// Load via XHR
if (router.xhr) {
router.xhr.abort();
router.xhr = false;
}
router
.xhrRequest(templateUrl)
.then((templateContent) => {
compile(templateContent);
})
.catch(() => {
reject();
});
} else {
compile(template);
}
}
modalTemplateLoader(template, templateUrl, options, resolve, reject) {
const router = this;
return router.templateLoader(template, templateUrl, options, (html) => {
resolve(html);
}, reject);
}
tabTemplateLoader(template, templateUrl, options, resolve, reject) {
const router = this;
return router.templateLoader(template, templateUrl, options, (html) => {
resolve(html);
}, reject);
}
pageTemplateLoader(template, templateUrl, options, resolve, reject) {
const router = this;
return router.templateLoader(template, templateUrl, options, (html, newOptions = {}) => {
resolve(router.getPageEl(html), newOptions);
}, reject);
}
componentLoader(component, componentUrl, options, resolve, reject) {
const router = this;
const url = typeof component === 'string' ? component : componentUrl;
function compile(c) {
const createdComponent = Component.create(c, {
$,
$$: $,
$app: router.app,
$root: Utils.extend({}, router.app.data, router.app.methods),
$route: options.route,
$router: router,
$dom7: $,
$theme: {
ios: router.app.theme === 'ios',
md: router.app.theme === 'md',
},
});
resolve(createdComponent.el, { pageEvents: createdComponent.on });
}
if (component && component.asyncTemplate) {
// Load async created dom
component.asyncTemplate((html) => {
compile(Utils.extend(component, { html }));
});
} else if (url) {
// Load via XHR
if (router.xhr) {
router.xhr.abort();
router.xhr = false;
}
router
.xhrRequest(url)
.then((loadedComponent) => {
compile(Component.parse(loadedComponent));
})
.catch(() => {
reject();
});
} else {
compile(component);
}
}
modalComponentLoader(rootEl, component, componentUrl, options, resolve, reject) {
const router = this;
router.componentLoader(component, componentUrl, options, (el) => {
resolve(el);
}, reject);
}
tabComponentLoader(tabEl, component, componentUrl, options, resolve, reject) {
const router = this;
router.componentLoader(component, componentUrl, options, (el) => {
resolve(el);
}, reject);
}
pageComponentLoader(routerEl, component, componentUrl, options, resolve, reject) {
const router = this;
router.componentLoader(component, componentUrl, options, (el, newOptions = {}) => {
resolve(el, newOptions);
}, reject);
}
getPageData(pageEl, navbarEl, from, to, route = {}, pageFromEl) {
const router = this;
const $pageEl = $(pageEl);
const $navbarEl = $(navbarEl);
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;
}
}
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: currentPage.pageFrom || pageFrom,
};
if ($navbarEl && $navbarEl[0]) {
$navbarEl[0].f7Page = page;
}
$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 { route, on = {} } = options;
const restoreScrollTopOnBack = router.params.restoreScrollTopOnBack;
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, navbarEl, from, to, route, pageFromEl);
}
function attachEvents() {
if ($pageEl[0].f7PageEventsAttached) return;
$pageEl[0].f7PageEventsAttached = true;
if (options.pageEvents && Object.keys(options.pageEvents).length > 0) {
$pageEl[0].f7PageEvents = options.pageEvents;
Object.keys(options.pageEvents).forEach((eventName) => {
$pageEl.on(`page:${eventName.split('page')[1].toLowerCase()}`, options.pageEvents[eventName]);
});
}
}
if (callback === 'mounted') {
attachEvents();
}
if (callback === 'init') {
if (restoreScrollTopOnBack && (from === 'previous' || !from) && to === 'current' && router.scrollHistory[page.route.url]) {
$pageEl.find('.page-content').scrollTop(router.scrollHistory[page.route.url]);
}
attachEvents();
if ($pageEl[0].f7PageInitialized) {
if (on.pageReinit) on.pageReinit(page);
$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
router.scrollHistory[page.route.url] = $pageEl.find('.page-content').scrollTop();
}
if (restoreScrollTopOnBack && callback === 'beforeOut' && from === 'current' && to === 'next') {
// Delete scroll position
delete router.scrollHistory[page.route.url];
}
if (on[camelName]) on[camelName](page);
$pageEl.trigger(colonName, page);
router.emit(camelName, page);
if (callback === 'beforeRemove') {
if ($pageEl[0].f7PageEventsAttached && $pageEl[0].f7PageEvents) {
Object.keys($pageEl[0].f7PageEvents).forEach((eventName) => {
$pageEl.off(`page:${eventName.split('page')[1].toLowerCase()}`, $pageEl[0].f7PageEvents[eventName]);
});
}
$pageEl[0].f7Page = null;
}
}
saveHistory() {
const router = this;
router.view.history = router.history;
if (router.params.pushState) {
window.localStorage[`f7router-view${router.view.index}-history`] = JSON.stringify(router.history);
}
}
restoreHistory() {
const router = this;
if (router.params.pushState && window.localStorage[`f7router-view${router.view.index}-history`]) {
router.history = JSON.parse(window.localStorage[`f7router-view${router.view.index}-history`]);
router.view.history = router.history;
}
}
clearHistory() {
const router = this;
router.history = [];
router.saveHistory();
}
init() {
const router = this;
const app = router.app;
// Init Swipeback
if (process.env.TARGET !== 'desktop') {
if (router.view && router.params.iosSwipeBack && app.theme === 'ios') {
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;
if (!router.params.pushState) {
if (!initUrl) {
initUrl = documentUrl;
}
} else {
if (router.params.pushStateRoot && documentUrl.indexOf(router.params.pushStateRoot) >= 0) {
documentUrl = documentUrl.split(router.params.pushStateRoot)[1];
if (documentUrl === '') documentUrl = '/';
}
if (router.params.pushStateSeparator.length > 0 && documentUrl.indexOf(router.params.pushStateSeparator) >= 0) {
initUrl = documentUrl.split(router.params.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 {
router.history = [documentUrl.split(router.params.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.findMatchingRoute(router.history[0], true), {
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.findMatchingRoute(initUrl, true), {
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, {
reloadCurrent: true,
pushState: false,
});
} else {
// Init current DOM page
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) {
router.$navbarEl.append($navbarInnerEl);
$pageEl.children('.navbar').remove();
} else {
router.$navbarEl.addClass('navbar-hidden');
}
}
router.pageCallback('init', $pageEl, $navbarInnerEl, 'current', undefined, { route: router.currentRoute });
});
if (historyRestored) {
router.navigate(initUrl, {
pushState: false,
history: false,
animate: router.params.pushStateAnimateOnLoad,
on: {
pageAfterIn() {
if (router.history.length > 2) {
router.back({ preload: true });
}
},
},
});
} else {
router.history.push(initUrl);
router.saveHistory();
}
}
router.emit('routerInit', router);
}
destroy() {
let router = this;
router.emit('routerDestroy', router);
// Delete props & methods
Object.keys(router).forEach((routerProp) => {
router[routerProp] = null;
delete router[routerProp];
});
router = null;
}
}
export default Router;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment