abstract-state-router
Advanced tools
Comparing version 5.17.0 to 6.0.0
@@ -0,1 +1,6 @@ | ||
# [6.0.0](https://github.com/TehShrike/abstract-state-router/releases/tag/v6.0.0) | ||
- `Promise` and `Object.assign` polyfills are now required for older browsers | ||
- refactor: updated all the source code to ES2015 (though the published version is still compiled down to ES5) | ||
# [5.17.0](https://github.com/TehShrike/abstract-state-router/releases/tag/v5.17.0) | ||
@@ -2,0 +7,0 @@ |
451
index.js
@@ -1,64 +0,62 @@ | ||
var StateState = require('./lib/state-state') | ||
var StateComparison = require('./lib/state-comparison') | ||
var CurrentState = require('./lib/current-state') | ||
var stateChangeLogic = require('./lib/state-change-logic') | ||
var parse = require('./lib/state-string-parser') | ||
var StateTransitionManager = require('./lib/state-transition-manager') | ||
var defaultRouterOptions = require('./default-router-options.js') | ||
const StateState = require('./lib/state-state') | ||
const StateComparison = require('./lib/state-comparison') | ||
const CurrentState = require('./lib/current-state') | ||
const stateChangeLogic = require('./lib/state-change-logic') | ||
const parse = require('./lib/state-string-parser') | ||
const StateTransitionManager = require('./lib/state-transition-manager') | ||
const defaultRouterOptions = require('./default-router-options.js') | ||
var series = require('./lib/promise-map-series') | ||
var denodeify = require('then-denodeify') | ||
const series = require('./lib/promise-map-series') | ||
const extend = require('./lib/extend.js') | ||
var EventEmitter = require('eventemitter3') | ||
var extend = require('xtend') | ||
var newHashBrownRouter = require('hash-brown-router') | ||
var combine = require('combine-arrays') | ||
var buildPath = require('page-path-builder') | ||
var nextTick = require('iso-next-tick') | ||
const denodeify = require('then-denodeify') | ||
const EventEmitter = require('eventemitter3') | ||
const newHashBrownRouter = require('hash-brown-router') | ||
const combine = require('combine-arrays') | ||
const buildPath = require('page-path-builder') | ||
const nextTick = require('iso-next-tick') | ||
require('native-promise-only/npo') | ||
const getProperty = name => obj => obj[name] | ||
const reverse = ary => ary.slice().reverse() | ||
const isFunction = property => obj => typeof obj[property] === 'function' | ||
const isThenable = object => object && (typeof object === 'object' || typeof object === 'function') && typeof object.then === 'function' | ||
const promiseMe = (fn, ...args) => new Promise(resolve => resolve(fn(...args))) | ||
var expectedPropertiesOfAddState = [ 'name', 'route', 'defaultChild', 'data', 'template', 'resolve', 'activate', 'querystringParameters', 'defaultQuerystringParameters', 'defaultParameters' ] | ||
const expectedPropertiesOfAddState = [ 'name', 'route', 'defaultChild', 'data', 'template', 'resolve', 'activate', 'querystringParameters', 'defaultQuerystringParameters', 'defaultParameters' ] | ||
module.exports = function StateProvider(makeRenderer, rootElement, stateRouterOptions) { | ||
var prototypalStateHolder = StateState() | ||
var lastCompletelyLoadedState = CurrentState() | ||
var lastStateStartedActivating = CurrentState() | ||
var stateProviderEmitter = new EventEmitter() | ||
var compareStartAndEndStates = StateComparison(prototypalStateHolder) | ||
const prototypalStateHolder = StateState() | ||
const lastCompletelyLoadedState = CurrentState() | ||
const lastStateStartedActivating = CurrentState() | ||
const stateProviderEmitter = new EventEmitter() | ||
const compareStartAndEndStates = StateComparison(prototypalStateHolder) | ||
function stateNameToArrayofStates(stateName) { | ||
return parse(stateName).map(function(name) { | ||
return prototypalStateHolder.get(name) | ||
}) | ||
} | ||
const stateNameToArrayofStates = stateName => parse(stateName).map(prototypalStateHolder.get) | ||
StateTransitionManager(stateProviderEmitter) | ||
stateRouterOptions = extend({ | ||
const { throwOnError, pathPrefix } = extend({ | ||
throwOnError: true, | ||
pathPrefix: '#' | ||
pathPrefix: '#', | ||
}, stateRouterOptions) | ||
if (!stateRouterOptions.router) { | ||
stateRouterOptions.router = newHashBrownRouter(defaultRouterOptions) | ||
} | ||
const router = stateRouterOptions.router || newHashBrownRouter(defaultRouterOptions) | ||
stateRouterOptions.router.on('not found', function(route, parameters) { | ||
router.on('not found', (route, parameters) => { | ||
stateProviderEmitter.emit('routeNotFound', route, parameters) | ||
}) | ||
var destroyDom = null | ||
var getDomChild = null | ||
var renderDom = null | ||
var resetDom = null | ||
let destroyDom = null | ||
let getDomChild = null | ||
let renderDom = null | ||
let resetDom = null | ||
var activeDomApis = {} | ||
var activeStateResolveContent = {} | ||
var activeEmitters = {} | ||
let activeStateResolveContent = {} | ||
const activeDomApis = {} | ||
const activeEmitters = {} | ||
function handleError(event, err) { | ||
nextTick(function() { | ||
nextTick(() => { | ||
stateProviderEmitter.emit(event, err) | ||
console.error(event + ' - ' + err.message) | ||
if (stateRouterOptions.throwOnError) { | ||
console.error(`${event} - ${err.message}`) | ||
if (throwOnError) { | ||
throw err | ||
@@ -70,6 +68,6 @@ } | ||
function destroyStateName(stateName) { | ||
var state = prototypalStateHolder.get(stateName) | ||
const state = prototypalStateHolder.get(stateName) | ||
stateProviderEmitter.emit('beforeDestroyState', { | ||
state: state, | ||
domApi: activeDomApis[stateName] | ||
domApi: activeDomApis[stateName], | ||
}) | ||
@@ -82,6 +80,6 @@ | ||
return destroyDom(activeDomApis[stateName]).then(function() { | ||
return destroyDom(activeDomApis[stateName]).then(() => { | ||
delete activeDomApis[stateName] | ||
stateProviderEmitter.emit('afterDestroyState', { | ||
state: state | ||
state, | ||
}) | ||
@@ -92,11 +90,11 @@ }) | ||
function resetStateName(parameters, stateName) { | ||
var domApi = activeDomApis[stateName] | ||
var content = getContentObject(activeStateResolveContent, stateName) | ||
var state = prototypalStateHolder.get(stateName) | ||
const domApi = activeDomApis[stateName] | ||
const content = getContentObject(activeStateResolveContent, stateName) | ||
const state = prototypalStateHolder.get(stateName) | ||
stateProviderEmitter.emit('beforeResetState', { | ||
domApi: domApi, | ||
content: content, | ||
state: state, | ||
parameters: parameters | ||
domApi, | ||
content, | ||
state, | ||
parameters, | ||
}) | ||
@@ -108,7 +106,7 @@ | ||
return resetDom({ | ||
domApi: domApi, | ||
content: content, | ||
domApi, | ||
content, | ||
template: state.template, | ||
parameters: parameters | ||
}).then(function(newDomApi) { | ||
parameters, | ||
}).then(newDomApi => { | ||
if (newDomApi) { | ||
@@ -120,5 +118,5 @@ activeDomApis[stateName] = newDomApi | ||
domApi: activeDomApis[stateName], | ||
content: content, | ||
state: state, | ||
parameters: parameters | ||
content, | ||
state, | ||
parameters, | ||
}) | ||
@@ -129,6 +127,6 @@ }) | ||
function getChildElementForStateName(stateName) { | ||
return new Promise(function(resolve) { | ||
var parent = prototypalStateHolder.getParent(stateName) | ||
return new Promise(resolve => { | ||
const parent = prototypalStateHolder.getParent(stateName) | ||
if (parent) { | ||
var parentDomApi = activeDomApis[parent.name] | ||
const parentDomApi = activeDomApis[parent.name] | ||
resolve(getDomChild(parentDomApi)) | ||
@@ -142,24 +140,24 @@ } else { | ||
function renderStateName(parameters, stateName) { | ||
return getChildElementForStateName(stateName).then(function(childElement) { | ||
var state = prototypalStateHolder.get(stateName) | ||
var content = getContentObject(activeStateResolveContent, stateName) | ||
return getChildElementForStateName(stateName).then(element => { | ||
const state = prototypalStateHolder.get(stateName) | ||
const content = getContentObject(activeStateResolveContent, stateName) | ||
stateProviderEmitter.emit('beforeCreateState', { | ||
state: state, | ||
content: content, | ||
parameters: parameters | ||
state, | ||
content, | ||
parameters, | ||
}) | ||
return renderDom({ | ||
element: childElement, | ||
template: state.template, | ||
content: content, | ||
parameters: parameters | ||
}).then(function(domApi) { | ||
element, | ||
content, | ||
parameters, | ||
}).then(domApi => { | ||
activeDomApis[stateName] = domApi | ||
stateProviderEmitter.emit('afterCreateState', { | ||
state: state, | ||
domApi: domApi, | ||
content: content, | ||
parameters: parameters | ||
state, | ||
domApi, | ||
content, | ||
parameters, | ||
}) | ||
@@ -172,3 +170,3 @@ return domApi | ||
function renderAll(stateNames, parameters) { | ||
return series(stateNames, renderStateName.bind(null, parameters)) | ||
return series(stateNames, stateName => renderStateName(parameters, stateName)) | ||
} | ||
@@ -178,3 +176,3 @@ | ||
try { | ||
var finalDestinationStateName = prototypalStateHolder.applyDefaultChildStates(state.name) | ||
const finalDestinationStateName = prototypalStateHolder.applyDefaultChildStates(state.name) | ||
@@ -186,4 +184,4 @@ if (finalDestinationStateName === state.name) { | ||
var theRouteWeNeedToEndUpAt = makePath(finalDestinationStateName, parameters) | ||
var currentRoute = stateRouterOptions.router.location.get() | ||
const theRouteWeNeedToEndUpAt = makePath(finalDestinationStateName, parameters) | ||
const currentRoute = router.location.get() | ||
@@ -205,11 +203,11 @@ if (theRouteWeNeedToEndUpAt === currentRoute) { | ||
if (typeof state === 'undefined') { | ||
throw new Error('Expected \'state\' to be passed in.') | ||
throw new Error(`Expected 'state' to be passed in.`) | ||
} else if (typeof state.name === 'undefined') { | ||
throw new Error('Expected the \'name\' option to be passed in.') | ||
throw new Error(`Expected the 'name' option to be passed in.`) | ||
} else if (typeof state.template === 'undefined') { | ||
throw new Error('Expected the \'template\' option to be passed in.') | ||
throw new Error(`Expected the 'template' option to be passed in.`) | ||
} | ||
Object.keys(state).filter(function(key) { | ||
Object.keys(state).filter(key => { | ||
return expectedPropertiesOfAddState.indexOf(key) === -1 | ||
}).forEach(function(key) { | ||
}).forEach(key => { | ||
console.warn('Unexpected property passed to addState:', key) | ||
@@ -220,5 +218,5 @@ }) | ||
var route = prototypalStateHolder.buildFullStateRoute(state.name) | ||
const route = prototypalStateHolder.buildFullStateRoute(state.name) | ||
stateRouterOptions.router.add(route, onRouteChange.bind(null, state)) | ||
router.add(route, parameters => onRouteChange(state, parameters)) | ||
} | ||
@@ -238,9 +236,9 @@ | ||
function ifNotCancelled(fn) { | ||
return function() { | ||
return (...args) => { | ||
if (transition.cancelled) { | ||
var err = new Error('The transition to ' + newStateName + 'was cancelled') | ||
const err = new Error(`The transition to ${newStateName} was cancelled`) | ||
err.wasCancelledBySomeoneElse = true | ||
throw err | ||
} else { | ||
return fn.apply(null, arguments) | ||
return fn(...args) | ||
} | ||
@@ -251,81 +249,88 @@ } | ||
return promiseMe(prototypalStateHolder.guaranteeAllStatesExist, newStateName) | ||
.then(function applyDefaultParameters() { | ||
var state = prototypalStateHolder.get(newStateName) | ||
var defaultParams = state.defaultParameters || state.defaultQuerystringParameters || {} | ||
var needToApplyDefaults = Object.keys(defaultParams).some(function missingParameterValue(param) { | ||
return typeof parameters[param] === 'undefined' | ||
}) | ||
.then(function applyDefaultParameters() { | ||
const state = prototypalStateHolder.get(newStateName) | ||
const defaultParams = state.defaultParameters || state.defaultQuerystringParameters || {} | ||
const needToApplyDefaults = Object.keys(defaultParams).some(function missingParameterValue(param) { | ||
return typeof parameters[param] === 'undefined' | ||
}) | ||
if (needToApplyDefaults) { | ||
throw redirector(newStateName, extend(defaultParams, parameters)) | ||
} | ||
return state | ||
}).then(ifNotCancelled(function(state) { | ||
stateProviderEmitter.emit('stateChangeStart', state, parameters, stateNameToArrayofStates(state.name)) | ||
lastStateStartedActivating.set(state.name, parameters) | ||
})).then(function getStateChanges() { | ||
var stateComparisonResults = compareStartAndEndStates(lastCompletelyLoadedState.get().name, lastCompletelyLoadedState.get().parameters, newStateName, parameters) | ||
return stateChangeLogic(stateComparisonResults) // { destroy, change, create } | ||
}).then(ifNotCancelled(function resolveDestroyAndActivateStates(stateChanges) { | ||
return resolveStates(getStatesToResolve(stateChanges), extend(parameters)).catch(function onResolveError(e) { | ||
e.stateChangeError = true | ||
throw e | ||
}).then(ifNotCancelled(function destroyAndActivate(stateResolveResultsObject) { | ||
transition.cancellable = false | ||
if (needToApplyDefaults) { | ||
throw redirector(newStateName, extend(defaultParams, parameters)) | ||
} | ||
return state | ||
}).then(ifNotCancelled(state => { | ||
stateProviderEmitter.emit('stateChangeStart', state, parameters, stateNameToArrayofStates(state.name)) | ||
lastStateStartedActivating.set(state.name, parameters) | ||
})).then(function getStateChanges() { | ||
const stateComparisonResults = compareStartAndEndStates({ | ||
original: lastCompletelyLoadedState.get(), | ||
destination: { | ||
name: newStateName, | ||
parameters, | ||
}, | ||
}) | ||
return stateChangeLogic(stateComparisonResults) // { destroy, change, create } | ||
}).then(ifNotCancelled(function resolveDestroyAndActivateStates(stateChanges) { | ||
return resolveStates(getStatesToResolve(stateChanges), extend(parameters)).catch(function onResolveError(e) { | ||
e.stateChangeError = true | ||
throw e | ||
}).then(ifNotCancelled(function destroyAndActivate(stateResolveResultsObject) { | ||
transition.cancellable = false | ||
function activateAll() { | ||
var statesToActivate = stateChanges.change.concat(stateChanges.create) | ||
const activateAll = () => activateStates( | ||
stateChanges.change.concat(stateChanges.create) | ||
) | ||
return activateStates(statesToActivate) | ||
} | ||
activeStateResolveContent = extend(activeStateResolveContent, stateResolveResultsObject) | ||
activeStateResolveContent = extend(activeStateResolveContent, stateResolveResultsObject) | ||
return series(reverse(stateChanges.destroy), destroyStateName).then(() => { | ||
return series( | ||
reverse(stateChanges.change), | ||
stateName => resetStateName(extend(parameters), stateName) | ||
) | ||
}).then( | ||
() => renderAll(stateChanges.create, extend(parameters)).then(activateAll) | ||
) | ||
})) | ||
return series(reverse(stateChanges.destroy), destroyStateName).then(function() { | ||
return series(reverse(stateChanges.change), resetStateName.bind(null, extend(parameters))) | ||
}).then(function() { | ||
return renderAll(stateChanges.create, extend(parameters)).then(activateAll) | ||
}) | ||
})) | ||
function activateStates(stateNames) { | ||
return stateNames.map(prototypalStateHolder.get).forEach(state => { | ||
const emitter = new EventEmitter() | ||
const context = Object.create(emitter) | ||
context.domApi = activeDomApis[state.name] | ||
context.data = state.data | ||
context.parameters = parameters | ||
context.content = getContentObject(activeStateResolveContent, state.name) | ||
activeEmitters[state.name] = emitter | ||
function activateStates(stateNames) { | ||
return stateNames.map(prototypalStateHolder.get).forEach(function(state) { | ||
var emitter = new EventEmitter() | ||
var context = Object.create(emitter) | ||
context.domApi = activeDomApis[state.name] | ||
context.data = state.data | ||
context.parameters = parameters | ||
context.content = getContentObject(activeStateResolveContent, state.name) | ||
activeEmitters[state.name] = emitter | ||
try { | ||
state.activate && state.activate(context) | ||
} catch (e) { | ||
nextTick(function() { | ||
throw e | ||
}) | ||
} | ||
}) | ||
} | ||
})).then(function stateChangeComplete() { | ||
lastCompletelyLoadedState.set(newStateName, parameters) | ||
try { | ||
stateProviderEmitter.emit('stateChangeEnd', prototypalStateHolder.get(newStateName), parameters, stateNameToArrayofStates(newStateName)) | ||
} catch (e) { | ||
handleError('stateError', e) | ||
} | ||
}).catch(ifNotCancelled(function handleStateChangeError(err) { | ||
if (err && err.redirectTo) { | ||
stateProviderEmitter.emit('stateChangeCancelled', err) | ||
return stateProviderEmitter.go(err.redirectTo.name, err.redirectTo.params, { replace: true }) | ||
} else if (err) { | ||
handleError('stateChangeError', err) | ||
} | ||
})).catch(function handleCancellation(err) { | ||
if (err && err.wasCancelledBySomeoneElse) { | ||
// we don't care, the state transition manager has already emitted the stateChangeCancelled for us | ||
} else { | ||
throw new Error("This probably shouldn't happen, maybe file an issue or something " + err) | ||
} | ||
}) | ||
try { | ||
state.activate && state.activate(context) | ||
} catch (e) { | ||
nextTick(() => { | ||
throw e | ||
}) | ||
} | ||
}) | ||
} | ||
})).then(function stateChangeComplete() { | ||
lastCompletelyLoadedState.set(newStateName, parameters) | ||
try { | ||
stateProviderEmitter.emit('stateChangeEnd', prototypalStateHolder.get(newStateName), parameters, stateNameToArrayofStates(newStateName)) | ||
} catch (e) { | ||
handleError('stateError', e) | ||
} | ||
}).catch(ifNotCancelled(function handleStateChangeError(err) { | ||
if (err && err.redirectTo) { | ||
stateProviderEmitter.emit('stateChangeCancelled', err) | ||
return stateProviderEmitter.go(err.redirectTo.name, err.redirectTo.params, { replace: true }) | ||
} else if (err) { | ||
handleError('stateChangeError', err) | ||
} | ||
})).catch(function handleCancellation(err) { | ||
if (err && err.wasCancelledBySomeoneElse) { | ||
// we don't care, the state transition manager has already emitted the stateChangeCancelled for us | ||
} else { | ||
throw new Error(`This probably shouldn't happen, maybe file an issue or something ${err}`) | ||
} | ||
}) | ||
} | ||
@@ -344,6 +349,6 @@ | ||
var destinationStateName = stateName === null ? getGuaranteedPreviousState().name : stateName | ||
const destinationStateName = stateName === null ? getGuaranteedPreviousState().name : stateName | ||
var destinationState = prototypalStateHolder.get(destinationStateName) || {} | ||
var defaultParams = destinationState.defaultParameters || destinationState.defaultQuerystringParameters | ||
const destinationState = prototypalStateHolder.get(destinationStateName) || {} | ||
const defaultParams = destinationState.defaultParameters || destinationState.defaultQuerystringParameters | ||
@@ -353,35 +358,36 @@ parameters = extend(defaultParams, parameters) | ||
prototypalStateHolder.guaranteeAllStatesExist(destinationStateName) | ||
var route = prototypalStateHolder.buildFullStateRoute(destinationStateName) | ||
const route = prototypalStateHolder.buildFullStateRoute(destinationStateName) | ||
return buildPath(route, parameters || {}) | ||
} | ||
var defaultOptions = { | ||
replace: false | ||
const defaultOptions = { | ||
replace: false, | ||
} | ||
stateProviderEmitter.addState = addState | ||
stateProviderEmitter.go = function go(newStateName, parameters, options) { | ||
stateProviderEmitter.go = (newStateName, parameters, options) => { | ||
options = extend(defaultOptions, options) | ||
var goFunction = options.replace ? stateRouterOptions.router.replace : stateRouterOptions.router.go | ||
const goFunction = options.replace ? router.replace : router.go | ||
return promiseMe(makePath, newStateName, parameters, options).then(goFunction, handleError.bind(null, 'stateChangeError')) | ||
return promiseMe(makePath, newStateName, parameters, options) | ||
.then(goFunction, err => handleError('stateChangeError', err)) | ||
} | ||
stateProviderEmitter.evaluateCurrentRoute = function evaluateCurrentRoute(defaultState, defaultParams) { | ||
return promiseMe(makePath, defaultState, defaultParams).then(function(defaultPath) { | ||
stateRouterOptions.router.evaluateCurrent(defaultPath) | ||
}).catch(function(err) { | ||
handleError('stateError', err) | ||
}) | ||
stateProviderEmitter.evaluateCurrentRoute = (defaultState, defaultParams) => { | ||
return promiseMe(makePath, defaultState, defaultParams).then(defaultPath => { | ||
router.evaluateCurrent(defaultPath) | ||
}).catch(err => handleError('stateError', err)) | ||
} | ||
stateProviderEmitter.makePath = function makePathAndPrependHash(stateName, parameters, options) { | ||
return stateRouterOptions.pathPrefix + makePath(stateName, parameters, options) | ||
stateProviderEmitter.makePath = (stateName, parameters, options) => { | ||
return pathPrefix + makePath(stateName, parameters, options) | ||
} | ||
stateProviderEmitter.stateIsActive = function stateIsActive(stateName, opts) { | ||
var currentState = lastCompletelyLoadedState.get() | ||
return (currentState.name === stateName || currentState.name.indexOf(stateName + '.') === 0) && (typeof opts === 'undefined' || Object.keys(opts).every(function matches(key) { | ||
return opts[key] === currentState.parameters[key] | ||
})) | ||
stateProviderEmitter.stateIsActive = (stateName, parameters = null) => { | ||
const currentState = lastCompletelyLoadedState.get() | ||
const stateNameMatches = currentState.name === stateName || currentState.name.indexOf(stateName + '.') === 0 | ||
const parametersWereNotPassedIn = !parameters | ||
return stateNameMatches | ||
&& (parametersWereNotPassedIn || Object.keys(parameters).every(key => parameters[key] === currentState.parameters[key])) | ||
} | ||
var renderer = makeRenderer(stateProviderEmitter) | ||
const renderer = makeRenderer(stateProviderEmitter) | ||
@@ -397,9 +403,9 @@ destroyDom = denodeify(renderer.destroy) | ||
function getContentObject(stateResolveResultsObject, stateName) { | ||
var allPossibleResolvedStateNames = parse(stateName) | ||
const allPossibleResolvedStateNames = parse(stateName) | ||
return allPossibleResolvedStateNames.filter(function(stateName) { | ||
return stateResolveResultsObject[stateName] | ||
}).reduce(function(obj, stateName) { | ||
return extend(obj, stateResolveResultsObject[stateName]) | ||
}, {}) | ||
return allPossibleResolvedStateNames | ||
.filter(stateName => stateResolveResultsObject[stateName]) | ||
.reduce((obj, stateName) => { | ||
return extend(obj, stateResolveResultsObject[stateName]) | ||
}, {}) | ||
} | ||
@@ -411,4 +417,4 @@ | ||
name: newStateName, | ||
params: parameters | ||
} | ||
params: parameters, | ||
}, | ||
} | ||
@@ -419,16 +425,15 @@ } | ||
function resolveStates(states, parameters) { | ||
var statesWithResolveFunctions = states.filter(isFunction('resolve')) | ||
var stateNamesWithResolveFunctions = statesWithResolveFunctions.map(property('name')) | ||
var resolves = Promise.all(statesWithResolveFunctions.map(function(state) { | ||
return new Promise(function(resolve, reject) { | ||
function resolveCb(err, content) { | ||
err ? reject(err) : resolve(content) | ||
} | ||
const statesWithResolveFunctions = states.filter(isFunction('resolve')) | ||
const stateNamesWithResolveFunctions = statesWithResolveFunctions.map(getProperty('name')) | ||
resolveCb.redirect = function redirect(newStateName, parameters) { | ||
const resolves = Promise.all(statesWithResolveFunctions.map(state => { | ||
return new Promise((resolve, reject) => { | ||
const resolveCb = (err, content) => err ? reject(err) : resolve(content) | ||
resolveCb.redirect = (newStateName, parameters) => { | ||
reject(redirector(newStateName, parameters)) | ||
} | ||
var res = state.resolve(state.data, parameters, resolveCb) | ||
if (res && (typeof res === 'object' || typeof res === 'function') && typeof res.then === 'function') { | ||
const res = state.resolve(state.data, parameters, resolveCb) | ||
if (isThenable(res)) { | ||
resolve(res) | ||
@@ -439,35 +444,11 @@ } | ||
return resolves.then(function(resolveResults) { | ||
return combine({ | ||
return resolves.then(resolveResults => | ||
combine({ | ||
stateName: stateNamesWithResolveFunctions, | ||
resolveResult: resolveResults | ||
}).reduce(function(obj, result) { | ||
resolveResult: resolveResults, | ||
}).reduce((obj, result) => { | ||
obj[result.stateName] = result.resolveResult | ||
return obj | ||
}, {}) | ||
}) | ||
) | ||
} | ||
function property(name) { | ||
return function(obj) { | ||
return obj[name] | ||
} | ||
} | ||
function reverse(ary) { | ||
return ary.slice().reverse() | ||
} | ||
function isFunction(property) { | ||
return function(obj) { | ||
return typeof obj[property] === 'function' | ||
} | ||
} | ||
function promiseMe() { | ||
var fn = Array.prototype.shift.apply(arguments) | ||
var args = arguments | ||
return new Promise(function(resolve) { | ||
resolve(fn.apply(null, args)) | ||
}) | ||
} |
module.exports = function CurrentState() { | ||
var current = { | ||
let current = { | ||
name: '', | ||
parameters: {} | ||
parameters: {}, | ||
} | ||
return { | ||
get: function() { | ||
get() { | ||
return current | ||
}, | ||
set: function(name, parameters) { | ||
set(name, parameters) { | ||
current = { | ||
name: name, | ||
parameters: parameters | ||
name, | ||
parameters, | ||
} | ||
} | ||
}, | ||
} | ||
} |
// Pulled from https://github.com/joliss/promise-map-series and prettied up a bit | ||
var Promise = require('native-promise-only/npo') | ||
module.exports = function sequence(array, iterator, thisArg) { | ||
var current = Promise.resolve() | ||
var cb = arguments.length > 2 ? iterator.bind(thisArg) : iterator | ||
var results = array.map(function(value, i) { | ||
return current = current.then(function(j) { | ||
return cb(value, j, array) | ||
}.bind(null, i)) | ||
}) | ||
return Promise.all(results) | ||
module.exports = function sequence(array, iterator) { | ||
let currentPromise = Promise.resolve() | ||
return Promise.all( | ||
array.map((value, i) => { | ||
return currentPromise = currentPromise.then(() => iterator(value, i, array)) | ||
}) | ||
) | ||
} |
module.exports = function stateChangeLogic(stateComparisonResults) { | ||
var hitChangingState = false | ||
var hitDestroyedState = false | ||
let hitChangingState = false | ||
let hitDestroyedState = false | ||
var output = { | ||
const output = { | ||
destroy: [], | ||
change: [], | ||
create: [] | ||
create: [], | ||
} | ||
stateComparisonResults.forEach(function(state) { | ||
stateComparisonResults.forEach(state => { | ||
hitChangingState = hitChangingState || state.stateParametersChanged | ||
@@ -13,0 +13,0 @@ hitDestroyedState = hitDestroyedState || state.stateNameChanged |
@@ -1,17 +0,19 @@ | ||
var stateStringParser = require('./state-string-parser') | ||
var combine = require('combine-arrays') | ||
var pathToRegexp = require('path-to-regexp-with-reversible-keys') | ||
const stateStringParser = require('./state-string-parser') | ||
const extend = require('./extend.js') | ||
const combine = require('combine-arrays') | ||
const pathToRegexp = require('path-to-regexp-with-reversible-keys') | ||
module.exports = function StateComparison(stateState) { | ||
var getPathParameters = pathParameters() | ||
const getPathParameters = pathParameters() | ||
var parametersChanged = parametersThatMatterWereChanged.bind(null, stateState, getPathParameters) | ||
const parametersChanged = args => parametersThatMatterWereChanged(extend(args, { stateState, getPathParameters })) | ||
return stateComparison.bind(null, parametersChanged) | ||
return args => stateComparison(extend(args, { parametersChanged })) | ||
} | ||
function pathParameters() { | ||
var parameters = {} | ||
const parameters = {} | ||
return function getPathParameters(path) { | ||
return path => { | ||
if (!path) { | ||
@@ -31,26 +33,28 @@ return [] | ||
function parametersThatMatterWereChanged(stateState, getPathParameters, stateName, fromParameters, toParameters) { | ||
var state = stateState.get(stateName) | ||
var querystringParameters = state.querystringParameters || [] | ||
var parameters = getPathParameters(state.route).concat(querystringParameters) | ||
function parametersThatMatterWereChanged({ stateState, getPathParameters, stateName, fromParameters, toParameters }) { | ||
const state = stateState.get(stateName) | ||
const querystringParameters = state.querystringParameters || [] | ||
const parameters = getPathParameters(state.route).concat(querystringParameters) | ||
return Array.isArray(parameters) && parameters.some(function(key) { | ||
return fromParameters[key] !== toParameters[key] | ||
}) | ||
return Array.isArray(parameters) && parameters.some( | ||
key => fromParameters[key] !== toParameters[key] | ||
) | ||
} | ||
function stateComparison(parametersChanged, originalState, originalParameters, newState, newParameters) { | ||
var states = combine({ | ||
start: stateStringParser(originalState), | ||
end: stateStringParser(newState) | ||
function stateComparison({ parametersChanged, original, destination }) { | ||
const states = combine({ | ||
start: stateStringParser(original.name), | ||
end: stateStringParser(destination.name), | ||
}) | ||
return states.map(function(states) { | ||
return { | ||
nameBefore: states.start, | ||
nameAfter: states.end, | ||
stateNameChanged: states.start !== states.end, | ||
stateParametersChanged: states.start === states.end && parametersChanged(states.start, originalParameters, newParameters) | ||
} | ||
}) | ||
return states.map(({ start, end }) => ({ | ||
nameBefore: start, | ||
nameAfter: end, | ||
stateNameChanged: start !== end, | ||
stateParametersChanged: start === end && parametersChanged({ | ||
stateName: start, | ||
fromParameters: original.parameters, | ||
toParameters: destination.parameters, | ||
}), | ||
})) | ||
} |
@@ -1,12 +0,12 @@ | ||
var stateStringParser = require('./state-string-parser') | ||
const stateStringParser = require('./state-string-parser') | ||
module.exports = function StateState() { | ||
var states = {} | ||
const states = {} | ||
function getHierarchy(name) { | ||
var names = stateStringParser(name) | ||
const names = stateStringParser(name) | ||
return names.map(function(name) { | ||
return names.map(name => { | ||
if (!states[name]) { | ||
throw new Error('State ' + name + ' not found') | ||
throw new Error(`State ${name} not found`) | ||
} | ||
@@ -18,3 +18,3 @@ return states[name] | ||
function getParent(name) { | ||
var parentName = getParentName(name) | ||
const parentName = getParentName(name) | ||
@@ -25,6 +25,6 @@ return parentName && states[parentName] | ||
function getParentName(name) { | ||
var names = stateStringParser(name) | ||
const names = stateStringParser(name) | ||
if (names.length > 1) { | ||
var secondToLast = names.length - 2 | ||
const secondToLast = names.length - 2 | ||
@@ -38,9 +38,7 @@ return names[secondToLast] | ||
function guaranteeAllStatesExist(newStateName) { | ||
var stateNames = stateStringParser(newStateName) | ||
var statesThatDontExist = stateNames.filter(function(name) { | ||
return !states[name] | ||
}) | ||
const stateNames = stateStringParser(newStateName) | ||
const statesThatDontExist = stateNames.filter(name => !states[name]) | ||
if (statesThatDontExist.length > 0) { | ||
throw new Error('State ' + statesThatDontExist[statesThatDontExist.length - 1] + ' does not exist') | ||
throw new Error(`State ${statesThatDontExist[statesThatDontExist.length - 1]} does not exist`) | ||
} | ||
@@ -50,18 +48,16 @@ } | ||
function buildFullStateRoute(stateName) { | ||
return getHierarchy(stateName).map(function(state) { | ||
return '/' + (state.route || '') | ||
}).join('').replace(/\/{2,}/g, '/') | ||
return getHierarchy(stateName).map(state => `/${state.route || ''}`) | ||
.join('') | ||
.replace(/\/{2,}/g, '/') | ||
} | ||
function applyDefaultChildStates(stateName) { | ||
var state = states[stateName] | ||
const state = states[stateName] | ||
function getDefaultChildStateName() { | ||
return state && (typeof state.defaultChild === 'function' | ||
const defaultChildStateName = state && ( | ||
typeof state.defaultChild === 'function' | ||
? state.defaultChild() | ||
: state.defaultChild) | ||
} | ||
: state.defaultChild | ||
) | ||
var defaultChildStateName = getDefaultChildStateName() | ||
if (!defaultChildStateName) { | ||
@@ -71,3 +67,3 @@ return stateName | ||
var fullStateName = stateName + '.' + defaultChildStateName | ||
const fullStateName = `${stateName}.${defaultChildStateName}` | ||
@@ -79,15 +75,15 @@ return applyDefaultChildStates(fullStateName) | ||
return { | ||
add: function(name, state) { | ||
add(name, state) { | ||
states[name] = state | ||
}, | ||
get: function(name) { | ||
get(name) { | ||
return name && states[name] | ||
}, | ||
getHierarchy: getHierarchy, | ||
getParent: getParent, | ||
getParentName: getParentName, | ||
guaranteeAllStatesExist: guaranteeAllStatesExist, | ||
buildFullStateRoute: buildFullStateRoute, | ||
applyDefaultChildStates: applyDefaultChildStates | ||
getHierarchy, | ||
getParent, | ||
getParentName, | ||
guaranteeAllStatesExist, | ||
buildFullStateRoute, | ||
applyDefaultChildStates, | ||
} | ||
} |
@@ -1,9 +0,11 @@ | ||
module.exports = function(stateString) { | ||
return stateString.split('.').reduce(function(stateNames, latestNameChunk) { | ||
if (stateNames.length) { | ||
latestNameChunk = stateNames[stateNames.length - 1] + '.' + latestNameChunk | ||
} | ||
stateNames.push(latestNameChunk) | ||
module.exports = stateString => { | ||
return stateString.split('.').reduce((stateNames, latestNameChunk) => { | ||
stateNames.push( | ||
stateNames.length | ||
? stateNames[stateNames.length - 1] + '.' + latestNameChunk | ||
: latestNameChunk | ||
) | ||
return stateNames | ||
}, []) | ||
} |
@@ -1,4 +0,4 @@ | ||
module.exports = function (emitter) { | ||
var currentTransitionAttempt = null | ||
var nextTransition = null | ||
module.exports = emitter => { | ||
let currentTransitionAttempt = null | ||
let nextTransition = null | ||
@@ -12,5 +12,3 @@ function doneTransitioning() { | ||
function isTransitioning() { | ||
return !!currentTransitionAttempt | ||
} | ||
const isTransitioning = () => !!currentTransitionAttempt | ||
@@ -25,3 +23,3 @@ function beginNextTransitionAttempt() { | ||
currentTransitionAttempt.transition.cancelled = true | ||
var err = new Error('State transition cancelled by the state transition manager') | ||
const err = new Error('State transition cancelled by the state transition manager') | ||
err.wasCancelledBySomeoneElse = true | ||
@@ -31,3 +29,3 @@ emitter.emit('stateChangeCancelled', err) | ||
emitter.on('stateChangeAttempt', function(beginStateChange) { | ||
emitter.on('stateChangeAttempt', beginStateChange => { | ||
nextTransition = createStateTransitionAttempt(beginStateChange) | ||
@@ -47,11 +45,11 @@ | ||
function createStateTransitionAttempt(beginStateChange) { | ||
var transition = { | ||
const transition = { | ||
cancelled: false, | ||
cancellable: true | ||
cancellable: true, | ||
} | ||
return { | ||
transition: transition, | ||
beginStateChange: beginStateChange.bind(null, transition) | ||
transition, | ||
beginStateChange: (...args) => beginStateChange(transition, ...args), | ||
} | ||
} | ||
} |
{ | ||
"name": "abstract-state-router", | ||
"version": "5.17.0", | ||
"version": "6.0.0", | ||
"description": "Like ui-router, but without all the Angular. The best way to structure a single-page webapp.", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "tape test/*.js | faucet", | ||
"build": "rollup -c", | ||
"test": "npm run build && tape test/*.js | faucet", | ||
"coverage": "covert test/*.js", | ||
"browserwatch": "watchify test-browser/add-color.js test/*.js -o test-browser/build.js -d", | ||
"browserstackbuild": "browserify test-browser/add-color.js test/*.js node_modules/browserstack-tape-reporter/index.js -o test-browser/build.js", | ||
"browserstack": "npm run browserstackbuild && browserstack-runner" | ||
"browserwatch": "sh -c 'rollup -c -w --dev & npm run watchtests'", | ||
"watchtests": "watchify test-browser/add-color.js test/*.js -o test-browser/build.js -d" | ||
}, | ||
@@ -33,20 +33,24 @@ "repository": { | ||
"iso-next-tick": "1.0.0", | ||
"native-promise-only": "0.8.1", | ||
"page-path-builder": "~1.0.3", | ||
"path-to-regexp-with-reversible-keys": "~1.0.3", | ||
"then-denodeify": "1.0.1", | ||
"xtend": "^4.0.0" | ||
"then-denodeify": "1.0.1" | ||
}, | ||
"devDependencies": { | ||
"babel-core": "6.26.0", | ||
"babel-plugin-external-helpers": "6.22.0", | ||
"babel-preset-es2015": "6.24.1", | ||
"browserify": "14.0.0", | ||
"browserstack-runner": "TehShrike/browserstack-runner#fixing-json-borking", | ||
"browserstack-tape-reporter": "~1.1.0", | ||
"covert": "^1.1.0", | ||
"faucet": "0.0.1", | ||
"rollup": "0.50.0", | ||
"rollup-plugin-babel": "3.0.2", | ||
"rollup-plugin-commonjs": "8.2.1", | ||
"rollup-plugin-node-resolve": "3.0.0", | ||
"rollup-plugin-visualizer": "0.3.1", | ||
"rollup-watch": "4.3.1", | ||
"tap-browser-color": "0.1.2", | ||
"tape": "^4.1.0", | ||
"tape-catch": "1.0.6", | ||
"watchify": "3.9.0", | ||
"webpack": "^1.12.14" | ||
"watchify": "3.9.0" | ||
} | ||
} |
@@ -13,2 +13,8 @@ abstract-state-router lets you build single-page webapps using nested routes/states. Your code doesn't reference routes directly, like `/app/users/josh`, but by name and properties, like `app.user` + `{ name: 'josh' }`. | ||
# 2017-10: major version bump, 6.0! | ||
The first major version bump in two and a half years! Don't worry though, it's entirely backwards compatible, except that the Promise polyfill has been dropped, so the library size should be a bit smaller. | ||
If you're supporting old browsers, you'll need polyfills for `Promise` and `Object.assign`. Check out [polyfill.io](https://polyfill.io/), it makes life super-easy. | ||
**[Read the changelog](./changelog.md)** | ||
@@ -285,9 +291,2 @@ | ||
Automated browser testing provided by [Browserstack](https://www.browserstack.com/). | ||
Tested in Chrome, Firefox, Safari, and IE10+ (IE9 doesn't support [replace](https://developer.mozilla.org/en-US/docs/Web/API/Location/replace)). | ||
[![Build Status](https://travis-ci.org/TehShrike/abstract-state-router.svg?branch=master)](https://travis-ci.org/TehShrike/abstract-state-router) | ||
# State change flow | ||
@@ -294,0 +293,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
1072531
7
14755
16
22
362
4
2
- Removednative-promise-only@0.8.1
- Removedxtend@^4.0.0
- Removednative-promise-only@0.8.1(transitive)