UNPKG

ember-prefetch

Version:

An Ember.js addon for escaping the waterfall.

241 lines (200 loc) 7.14 kB
import { assign } from '@ember/polyfills'; import { gte } from 'ember-compatibility-helpers'; import { assert } from '@ember/debug'; export let diffQPs; export let shouldRefreshModel; export let pathsDiffer; export let paramsDiffer; export let createPrefetchChangeSet; export let pathsRefresh; if (gte('3.6.0')) { // remove guard for Ember 3.8 LTS and rev major // eslint-disable-next-line no-inner-declarations function createList(enumerable) { let out = []; if (enumerable === null) return out; enumerable.find(item => { out.push(item); // using `find` to emulate forEach return false; }); return out; } diffQPs = function(from, to) { let diff = {}; let params = [...Object.keys(from.queryParams), ...Object.keys(to.queryParams)]; for (let param of params) { if (from.queryParams[param] !== to.queryParams[param]) { diff[param] = true; } } return Object.keys(diff); }; shouldRefreshModel = function(routeQueryParams, changedQPs) { let routeQPKeys = Object.keys(routeQueryParams); return routeQPKeys.some(key => { return routeQueryParams[key].refreshModel && changedQPs.indexOf(key) > -1; }); }; // eslint-disable-next-line no-inner-declarations function paramsMatch(from, to) { return to.paramNames.every((paramName, i) => { return from.paramNames[i] === paramName && from.params[paramName] === to.params[paramName]; }); } /** * This method checks if from and to routes are navigating away from current route. * For e.g. one navigating from `profile.view` to `profile.details` * * @method pathsDiffer * @param {Object} from - from route list * @param {Object} to - to route list * @return {Array} An array containing mismatch and pivotIndex. * @public */ pathsDiffer = function(from, to) { let pivotIndex = -1; let mismatch = false; for (let i = 0; i < to.length; i++) { let info = to[i]; if (info.name !== from[i].name || !paramsMatch(info, from[i])) { pivotIndex = i; mismatch = true; break; } } return [mismatch, pivotIndex]; }; /** * This check only validate if from and to routes are identical but contains different * parameters. * * @method paramsDiffer * @param {Object} from - from route list * @param {Object} to - to route list * @return {Array} An array containing mismatch and pivotIndex. * @public */ paramsDiffer = function(from, to) { let pivotIndex = -1; let mismatch = false; if (from.length !== to.length) { return [mismatch, pivotIndex]; } for (let i = 0; i < to.length; i++) { let info = to[i]; let _from = from[i]; if (info.paramNames.length !== _from.paramNames.length || !paramsMatch(_from, info)) { pivotIndex = i; mismatch = true; break; } } return [mismatch, pivotIndex]; }; // eslint-disable-next-line no-inner-declarations function qpsDiffer(privateRouter, to, transition) { let routes = getPrefetched(privateRouter, to); if (transition.from === null) { return { shouldCall: true, for: routes }; } let diff = diffQPs(transition.from, transition.to); if (diff.length > 0) { let prefetchRoutes = []; routes.forEach(info => { let { route } = info; if (shouldRefreshModel(route.queryParams, diff)) { prefetchRoutes.push(info); } }); return { shouldCall: true, for: prefetchRoutes }; } return { shouldCall: false, for: [] }; } // eslint-disable-next-line no-inner-declarations function getPrefetched(privateRouter, to) { let routes = []; for (let i = 0; i < to.length; i++) { let info = to[i]; let route = privateRouter.getRoute(info.name); if (route !== undefined && route !== null) { routes.push({ route, fullParams: assign({}, info.params, { queryParams: info.queryParams }), }); } } return routes; } // This should be invoked if there are no results for queryparams diff(qpsDiff) due to overlapping logic pathsRefresh = function(from, to, intent) { let pivotIndex = -1; let hasMatch = false; if (!from || !intent || from.length !== to.length || !intent.pivotHandler) { return [hasMatch, pivotIndex]; } // only route.refresh and route.refreshModel hook have `NamedTransitionIntent` and has fullRouteName const refreshRouteName = intent.pivotHandler.fullRouteName; for (let i = 0; i < from.length; i++) { if (from[i].name === refreshRouteName) { return [true, i]; } } assert( 'This return section should not be reachable. `refreshRouteName` should be always present for `route.refresh()`' ); return [hasMatch, pivotIndex]; }; /** This function checks transition in sequence 1. param has changed 2. route has changed 3. query param has changed 4. refresh has invoked from route This checking sequence is important, changing sequence could impact in weird ways. For examample, query param invokes route.refresh() if refreshModel is set true on route level. If #4 has invoked prior to #3, it will visit index route of refreshModel hence involves in additional API invocation @method createPrefetchChangeSet @param {Object} privateRouter - router @param {Object} transition - transition object @return {Object} An object containing `shouldCall` to invoke prefetch promise on each route and `for` to iterate through affected routes @public */ createPrefetchChangeSet = function(privateRouter, transition) { let toList = createList(transition.to); let fromList = createList(transition.from); if (fromList.length === 0) { return { shouldCall: true, for: getPrefetched(privateRouter, toList) }; } let paramsResult = paramsDiffer(fromList, toList); let [_paramsDiffer] = paramsResult; // Params Changed if (_paramsDiffer) { let [, pivot] = paramsResult; let pivotHandlers = toList.splice(pivot, toList.length); return { shouldCall: true, for: getPrefetched(privateRouter, pivotHandlers) }; } // Path has changed let pathResult = pathsDiffer(fromList, toList); let [_pathsDiffer] = pathResult; if (_pathsDiffer) { let [, pivot] = pathResult; let pivotHandlers = toList.splice(pivot, toList.length); return { shouldCall: true, for: getPrefetched(privateRouter, pivotHandlers) }; } // Query Params changed let qpsResult = qpsDiffer(privateRouter, toList, transition); if (qpsResult.shouldCall) { return qpsResult; } // route.refresh has invoked let refreshResult = pathsRefresh(fromList, toList, transition.intent); let [_isRefresh] = refreshResult; if (_isRefresh) { let [, pivot] = refreshResult; let pivotHandlers = toList.splice(pivot); return { shouldCall: true, for: getPrefetched(privateRouter, pivotHandlers) }; } return { shouldCall: false }; }; }