Comparing version 2.0.0-beta.1 to 2.0.0-beta.2
@@ -18,7 +18,7 @@ 'use strict'; | ||
var HandlerInfo = function () { | ||
function HandlerInfo(_props) { | ||
function HandlerInfo() { | ||
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
_classCallCheck(this, HandlerInfo); | ||
var props = _props || {}; | ||
// initialize local properties to ensure consistent object shape | ||
@@ -46,3 +46,2 @@ this._handler = DEFAULT_HANDLER; | ||
var handler = this.getHandler(this.name); | ||
return this._processHandler(handler); | ||
@@ -102,16 +101,9 @@ } | ||
value: function resolve(shouldContinue, payload) { | ||
var checkForAbort = this.checkForAbort.bind(this, shouldContinue), | ||
beforeModel = this.runBeforeModelHook.bind(this, payload), | ||
model = this.getModel.bind(this, payload), | ||
afterModel = this.runAfterModelHook.bind(this, payload), | ||
becomeResolved = this.becomeResolved.bind(this, payload), | ||
self = this; | ||
var checkForAbort = this.checkForAbort.bind(this, shouldContinue); | ||
var beforeModel = this.runBeforeModelHook.bind(this, payload); | ||
var model = this.getModel.bind(this, payload); | ||
var afterModel = this.runAfterModelHook.bind(this, payload); | ||
var becomeResolved = this.becomeResolved.bind(this, payload); | ||
return _rsvp.Promise.resolve(this.handlerPromise, this.promiseLabel('Start handler')).then(function (handler) { | ||
// We nest this chain in case the handlerPromise has an error so that | ||
// we don't have to bubble it through every step | ||
return _rsvp.Promise.resolve(handler).then(checkForAbort, null, self.promiseLabel('Check for abort')).then(beforeModel, null, self.promiseLabel('Before model')).then(checkForAbort, null, self.promiseLabel("Check if aborted during 'beforeModel' hook")).then(model, null, self.promiseLabel('Model')).then(checkForAbort, null, self.promiseLabel("Check if aborted in 'model' hook")).then(afterModel, null, self.promiseLabel('After model')).then(checkForAbort, null, self.promiseLabel("Check if aborted in 'afterModel' hook")).then(becomeResolved, null, self.promiseLabel('Become resolved')); | ||
}, function (error) { | ||
throw error; | ||
}); | ||
return _rsvp.Promise.resolve(this.handlerPromise, this.promiseLabel('Start handler')).then(checkForAbort, null, this.promiseLabel('Check for abort')).then(beforeModel, null, this.promiseLabel('Before model')).then(checkForAbort, null, this.promiseLabel("Check if aborted during 'beforeModel' hook")).then(model, null, this.promiseLabel('Model')).then(checkForAbort, null, this.promiseLabel("Check if aborted in 'model' hook")).then(afterModel, null, this.promiseLabel('After model')).then(checkForAbort, null, this.promiseLabel("Check if aborted in 'afterModel' hook")).then(becomeResolved, null, this.promiseLabel('Become resolved')); | ||
} | ||
@@ -118,0 +110,0 @@ }, { |
@@ -7,2 +7,4 @@ 'use strict'; | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
var _routeRecognizer = require('route-recognizer'); | ||
@@ -36,376 +38,427 @@ | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
var pop = Array.prototype.pop; | ||
function Router(_options) { | ||
var options = _options || {}; | ||
this.getHandler = options.getHandler || this.getHandler; | ||
this.getSerializer = options.getSerializer || this.getSerializer; | ||
this.updateURL = options.updateURL || this.updateURL; | ||
this.replaceURL = options.replaceURL || this.replaceURL; | ||
this.didTransition = options.didTransition || this.didTransition; | ||
this.willTransition = options.willTransition || this.willTransition; | ||
this.delegate = options.delegate || this.delegate; | ||
this.triggerEvent = options.triggerEvent || this.triggerEvent; | ||
this.log = options.log || this.log; | ||
this.dslCallBacks = []; // NOTE: set by Ember | ||
this.state = undefined; | ||
this.activeTransition = undefined; | ||
this._changedQueryParams = undefined; | ||
this.oldState = undefined; | ||
this.currentHandlerInfos = undefined; | ||
this.currentSequence = 0; | ||
var Router = function () { | ||
function Router() { | ||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
this.recognizer = new _routeRecognizer2.default(); | ||
this.reset(); | ||
} | ||
_classCallCheck(this, Router); | ||
function getTransitionByIntent(intent, isIntermediate) { | ||
var wasTransitioning = !!this.activeTransition; | ||
var oldState = wasTransitioning ? this.activeTransition.state : this.state; | ||
var newTransition; | ||
this.getHandler = options.getHandler || this.getHandler; | ||
this.getSerializer = options.getSerializer || this.getSerializer; | ||
this.updateURL = options.updateURL || this.updateURL; | ||
this.replaceURL = options.replaceURL || this.replaceURL; | ||
this.didTransition = options.didTransition || this.didTransition; | ||
this.willTransition = options.willTransition || this.willTransition; | ||
this.delegate = options.delegate || this.delegate; | ||
this.triggerEvent = options.triggerEvent || this.triggerEvent; | ||
this.log = options.log || this.log; | ||
this.dslCallBacks = []; // NOTE: set by Ember | ||
var newState = intent.applyToState(oldState, this.recognizer, this.getHandler, isIntermediate, this.getSerializer); | ||
var queryParamChangelist = (0, _utils.getChangelist)(oldState.queryParams, newState.queryParams); | ||
this.state = undefined; | ||
this.activeTransition = undefined; | ||
this._changedQueryParams = undefined; | ||
this.oldState = undefined; | ||
this.currentHandlerInfos = undefined; | ||
this.currentSequence = 0; | ||
if (handlerInfosEqual(newState.handlerInfos, oldState.handlerInfos)) { | ||
// This is a no-op transition. See if query params changed. | ||
if (queryParamChangelist) { | ||
newTransition = this.queryParamsTransition(queryParamChangelist, wasTransitioning, oldState, newState); | ||
if (newTransition) { | ||
newTransition.queryParamsOnly = true; | ||
return newTransition; | ||
} | ||
} | ||
// No-op. No need to create a new transition. | ||
return this.activeTransition || new _transition.Transition(this); | ||
this.recognizer = new _routeRecognizer2.default(); | ||
this.reset(); | ||
} | ||
if (isIntermediate) { | ||
setupContexts(this, newState); | ||
return; | ||
} | ||
/** | ||
The main entry point into the router. The API is essentially | ||
the same as the `map` method in `route-recognizer`. | ||
This method extracts the String handler at the last `.to()` | ||
call and uses it as the name of the whole route. | ||
@param {Function} callback | ||
*/ | ||
// Create a new transition to the destination route. | ||
newTransition = new _transition.Transition(this, intent, newState, undefined, this.activeTransition); | ||
// transition is to same route with same params, only query params differ. | ||
// not caught above probably because refresh() has been used | ||
if (handlerInfosSameExceptQueryParams(newState.handlerInfos, oldState.handlerInfos)) { | ||
newTransition.queryParamsOnly = true; | ||
} | ||
_createClass(Router, [{ | ||
key: 'map', | ||
value: function map(callback) { | ||
this.recognizer.delegate = this.delegate; | ||
// Abort and usurp any previously active transition. | ||
if (this.activeTransition) { | ||
this.activeTransition.abort(); | ||
} | ||
this.activeTransition = newTransition; | ||
this.recognizer.map(callback, function (recognizer, routes) { | ||
for (var i = routes.length - 1, proceed = true; i >= 0 && proceed; --i) { | ||
var route = routes[i]; | ||
recognizer.add(routes, { as: route.handler }); | ||
proceed = route.path === '/' || route.path === '' || route.handler.slice(-6) === '.index'; | ||
} | ||
}); | ||
} | ||
}, { | ||
key: 'hasRoute', | ||
value: function hasRoute(route) { | ||
return this.recognizer.hasRoute(route); | ||
} | ||
}, { | ||
key: 'getHandler', | ||
value: function getHandler() {} | ||
}, { | ||
key: 'getSerializer', | ||
value: function getSerializer() {} | ||
}, { | ||
key: 'queryParamsTransition', | ||
value: function queryParamsTransition(changelist, wasTransitioning, oldState, newState) { | ||
var router = this; | ||
// Transition promises by default resolve with resolved state. | ||
// For our purposes, swap out the promise to resolve | ||
// after the transition has been finalized. | ||
newTransition.promise = newTransition.promise.then(function (result) { | ||
return finalizeTransition(newTransition, result.state); | ||
}, null, (0, _utils.promiseLabel)('Settle transition promise when transition is finalized')); | ||
fireQueryParamDidChange(this, newState, changelist); | ||
if (!wasTransitioning) { | ||
notifyExistingHandlers(this, newState, newTransition); | ||
} | ||
if (!wasTransitioning && this.activeTransition) { | ||
// One of the handlers in queryParamsDidChange | ||
// caused a transition. Just return that transition. | ||
return this.activeTransition; | ||
} else { | ||
// Running queryParamsDidChange didn't change anything. | ||
// Just update query params and be on our way. | ||
fireQueryParamDidChange(this, newState, queryParamChangelist); | ||
// We have to return a noop transition that will | ||
// perform a URL update at the end. This gives | ||
// the user the ability to set the url update | ||
// method (default is replaceState). | ||
var newTransition = new _transition.Transition(this); | ||
newTransition.queryParamsOnly = true; | ||
return newTransition; | ||
} | ||
oldState.queryParams = finalizeQueryParamChange(this, newState.handlerInfos, newState.queryParams, newTransition); | ||
Router.prototype = { | ||
/** | ||
The main entry point into the router. The API is essentially | ||
the same as the `map` method in `route-recognizer`. | ||
This method extracts the String handler at the last `.to()` | ||
call and uses it as the name of the whole route. | ||
@param {Function} callback | ||
*/ | ||
map: function map(callback) { | ||
this.recognizer.delegate = this.delegate; | ||
newTransition.promise = newTransition.promise.then(function (result) { | ||
updateURL(newTransition, oldState, true); | ||
if (router.didTransition) { | ||
router.didTransition(router.currentHandlerInfos); | ||
} | ||
return result; | ||
}, null, (0, _utils.promiseLabel)('Transition complete')); | ||
return newTransition; | ||
} | ||
} | ||
this.recognizer.map(callback, function (recognizer, routes) { | ||
for (var i = routes.length - 1, proceed = true; i >= 0 && proceed; --i) { | ||
var route = routes[i]; | ||
recognizer.add(routes, { as: route.handler }); | ||
proceed = route.path === '/' || route.path === '' || route.handler.slice(-6) === '.index'; | ||
// NOTE: this doesn't really belong here, but here | ||
// it shall remain until our ES6 transpiler can | ||
// handle cyclical deps. | ||
}, { | ||
key: 'transitionByIntent', | ||
value: function transitionByIntent(intent /*, isIntermediate*/) { | ||
try { | ||
return getTransitionByIntent.apply(this, arguments); | ||
} catch (e) { | ||
return new _transition.Transition(this, intent, null, e); | ||
} | ||
}); | ||
}, | ||
} | ||
hasRoute: function hasRoute(route) { | ||
return this.recognizer.hasRoute(route); | ||
}, | ||
/** | ||
Clears the current and target route handlers and triggers exit | ||
on each of them starting at the leaf and traversing up through | ||
its ancestors. | ||
*/ | ||
getHandler: function getHandler() {}, | ||
}, { | ||
key: 'reset', | ||
value: function reset() { | ||
if (this.state) { | ||
(0, _utils.forEach)(this.state.handlerInfos.slice().reverse(), function (handlerInfo) { | ||
var handler = handlerInfo.handler; | ||
(0, _utils.callHook)(handler, 'exit'); | ||
}); | ||
} | ||
getSerializer: function getSerializer() {}, | ||
this.oldState = undefined; | ||
this.state = new _transitionState2.default(); | ||
this.currentHandlerInfos = null; | ||
} | ||
queryParamsTransition: function queryParamsTransition(changelist, wasTransitioning, oldState, newState) { | ||
var router = this; | ||
/** | ||
var handler = handlerInfo.handler; | ||
The entry point for handling a change to the URL (usually | ||
via the back and forward button). | ||
Returns an Array of handlers and the parameters associated | ||
with those parameters. | ||
@param {String} url a URL to process | ||
@return {Array} an Array of `[handler, parameter]` tuples | ||
*/ | ||
fireQueryParamDidChange(this, newState, changelist); | ||
}, { | ||
key: 'handleURL', | ||
value: function handleURL() { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
if (!wasTransitioning && this.activeTransition) { | ||
// One of the handlers in queryParamsDidChange | ||
// caused a transition. Just return that transition. | ||
return this.activeTransition; | ||
} else { | ||
// Running queryParamsDidChange didn't change anything. | ||
// Just update query params and be on our way. | ||
// Perform a URL-based transition, but don't change | ||
// the URL afterward, since it already happened. | ||
var url = args[0]; | ||
if (url.charAt(0) !== '/') { | ||
args[0] = '/' + url; | ||
} | ||
// We have to return a noop transition that will | ||
// perform a URL update at the end. This gives | ||
// the user the ability to set the url update | ||
// method (default is replaceState). | ||
var newTransition = new _transition.Transition(this); | ||
newTransition.queryParamsOnly = true; | ||
return doTransition(this, args).method(null); | ||
} | ||
oldState.queryParams = finalizeQueryParamChange(this, newState.handlerInfos, newState.queryParams, newTransition); | ||
/** | ||
Hook point for updating the URL. | ||
@param {String} url a URL to update to | ||
*/ | ||
newTransition.promise = newTransition.promise.then(function (result) { | ||
updateURL(newTransition, oldState, true); | ||
if (router.didTransition) { | ||
router.didTransition(router.currentHandlerInfos); | ||
} | ||
return result; | ||
}, null, (0, _utils.promiseLabel)('Transition complete')); | ||
return newTransition; | ||
}, { | ||
key: 'updateURL', | ||
value: function updateURL() { | ||
throw new Error('updateURL is not implemented'); | ||
} | ||
}, | ||
// NOTE: this doesn't really belong here, but here | ||
// it shall remain until our ES6 transpiler can | ||
// handle cyclical deps. | ||
transitionByIntent: function transitionByIntent(intent /*, isIntermediate*/) { | ||
try { | ||
return getTransitionByIntent.apply(this, arguments); | ||
} catch (e) { | ||
return new _transition.Transition(this, intent, null, e); | ||
/** | ||
Hook point for replacing the current URL, i.e. with replaceState | ||
By default this behaves the same as `updateURL` | ||
@param {String} url a URL to update to | ||
*/ | ||
}, { | ||
key: 'replaceURL', | ||
value: function replaceURL(url) { | ||
this.updateURL(url); | ||
} | ||
}, | ||
/** | ||
Clears the current and target route handlers and triggers exit | ||
on each of them starting at the leaf and traversing up through | ||
its ancestors. | ||
*/ | ||
reset: function reset() { | ||
if (this.state) { | ||
(0, _utils.forEach)(this.state.handlerInfos.slice().reverse(), function (handlerInfo) { | ||
var handler = handlerInfo.handler; | ||
(0, _utils.callHook)(handler, 'exit'); | ||
}); | ||
/** | ||
Transition into the specified named route. | ||
If necessary, trigger the exit callback on any handlers | ||
that are no longer represented by the target route. | ||
@param {String} name the name of the route | ||
*/ | ||
}, { | ||
key: 'transitionTo', | ||
value: function transitionTo() /*name*/{ | ||
return doTransition(this, arguments); | ||
} | ||
}, { | ||
key: 'intermediateTransitionTo', | ||
value: function intermediateTransitionTo() /*name*/{ | ||
return doTransition(this, arguments, true); | ||
} | ||
}, { | ||
key: 'refresh', | ||
value: function refresh(pivotHandler) { | ||
var previousTransition = this.activeTransition; | ||
var state = previousTransition ? previousTransition.state : this.state; | ||
var handlerInfos = state.handlerInfos; | ||
this.oldState = undefined; | ||
this.state = new _transitionState2.default(); | ||
this.currentHandlerInfos = null; | ||
}, | ||
(0, _utils.log)(this, 'Starting a refresh transition'); | ||
var intent = new _namedTransitionIntent2.default({ | ||
name: handlerInfos[handlerInfos.length - 1].name, | ||
pivotHandler: pivotHandler || handlerInfos[0].handler, | ||
contexts: [], // TODO collect contexts...? | ||
queryParams: this._changedQueryParams || state.queryParams || {} | ||
}); | ||
activeTransition: null, | ||
var newTransition = this.transitionByIntent(intent, false); | ||
/** | ||
var handler = handlerInfo.handler; | ||
The entry point for handling a change to the URL (usually | ||
via the back and forward button). | ||
Returns an Array of handlers and the parameters associated | ||
with those parameters. | ||
@param {String} url a URL to process | ||
@return {Array} an Array of `[handler, parameter]` tuples | ||
*/ | ||
handleURL: function handleURL(url) { | ||
// Perform a URL-based transition, but don't change | ||
// the URL afterward, since it already happened. | ||
var args = _utils.slice.call(arguments); | ||
if (url.charAt(0) !== '/') { | ||
args[0] = '/' + url; | ||
// if the previous transition is a replace transition, that needs to be preserved | ||
if (previousTransition && previousTransition.urlMethod === 'replace') { | ||
newTransition.method(previousTransition.urlMethod); | ||
} | ||
return newTransition; | ||
} | ||
return doTransition(this, args).method(null); | ||
}, | ||
/** | ||
Identical to `transitionTo` except that the current URL will be replaced | ||
if possible. | ||
This method is intended primarily for use with `replaceState`. | ||
@param {String} name the name of the route | ||
*/ | ||
/** | ||
Hook point for updating the URL. | ||
@param {String} url a URL to update to | ||
*/ | ||
updateURL: function updateURL() { | ||
throw new Error('updateURL is not implemented'); | ||
}, | ||
}, { | ||
key: 'replaceWith', | ||
value: function replaceWith() /*name*/{ | ||
return doTransition(this, arguments).method('replace'); | ||
} | ||
/** | ||
Hook point for replacing the current URL, i.e. with replaceState | ||
By default this behaves the same as `updateURL` | ||
@param {String} url a URL to update to | ||
*/ | ||
replaceURL: function replaceURL(url) { | ||
this.updateURL(url); | ||
}, | ||
/** | ||
Take a named route and context objects and generate a | ||
URL. | ||
@param {String} name the name of the route to generate | ||
a URL for | ||
@param {...Object} objects a list of objects to serialize | ||
@return {String} a URL | ||
*/ | ||
/** | ||
Transition into the specified named route. | ||
If necessary, trigger the exit callback on any handlers | ||
that are no longer represented by the target route. | ||
@param {String} name the name of the route | ||
*/ | ||
transitionTo: function transitionTo() /*name*/{ | ||
return doTransition(this, arguments); | ||
}, | ||
}, { | ||
key: 'generate', | ||
value: function generate(handlerName) { | ||
var partitionedArgs = (0, _utils.extractQueryParams)(_utils.slice.call(arguments, 1)), | ||
suppliedParams = partitionedArgs[0], | ||
queryParams = partitionedArgs[1]; | ||
intermediateTransitionTo: function intermediateTransitionTo() /*name*/{ | ||
return doTransition(this, arguments, true); | ||
}, | ||
// Construct a TransitionIntent with the provided params | ||
// and apply it to the present state of the router. | ||
var intent = new _namedTransitionIntent2.default({ | ||
name: handlerName, | ||
contexts: suppliedParams | ||
}); | ||
var state = intent.applyToState(this.state, this.recognizer, this.getHandler, null, this.getSerializer); | ||
refresh: function refresh(pivotHandler) { | ||
var previousTransition = this.activeTransition; | ||
var state = previousTransition ? previousTransition.state : this.state; | ||
var handlerInfos = state.handlerInfos; | ||
var params = {}; | ||
for (var i = 0, len = state.handlerInfos.length; i < len; ++i) { | ||
var handlerInfo = state.handlerInfos[i]; | ||
var handlerParams = handlerInfo.serialize(); | ||
(0, _utils.merge)(params, handlerParams); | ||
} | ||
params.queryParams = queryParams; | ||
(0, _utils.log)(this, 'Starting a refresh transition'); | ||
var intent = new _namedTransitionIntent2.default({ | ||
name: handlerInfos[handlerInfos.length - 1].name, | ||
pivotHandler: pivotHandler || handlerInfos[0].handler, | ||
contexts: [], // TODO collect contexts...? | ||
queryParams: this._changedQueryParams || state.queryParams || {} | ||
}); | ||
return this.recognizer.generate(handlerName, params); | ||
} | ||
}, { | ||
key: 'applyIntent', | ||
value: function applyIntent(handlerName, contexts) { | ||
var intent = new _namedTransitionIntent2.default({ | ||
name: handlerName, | ||
contexts: contexts | ||
}); | ||
var newTransition = this.transitionByIntent(intent, false); | ||
var state = this.activeTransition && this.activeTransition.state || this.state; | ||
// if the previous transition is a replace transition, that needs to be preserved | ||
if (previousTransition && previousTransition.urlMethod === 'replace') { | ||
newTransition.method(previousTransition.urlMethod); | ||
return intent.applyToState(state, this.recognizer, this.getHandler, null, this.getSerializer); | ||
} | ||
}, { | ||
key: 'isActiveIntent', | ||
value: function isActiveIntent(handlerName, contexts, queryParams, _state) { | ||
var state = _state || this.state, | ||
targetHandlerInfos = state.handlerInfos, | ||
handlerInfo = void 0, | ||
len = void 0; | ||
return newTransition; | ||
}, | ||
if (!targetHandlerInfos.length) { | ||
return false; | ||
} | ||
/** | ||
Identical to `transitionTo` except that the current URL will be replaced | ||
if possible. | ||
This method is intended primarily for use with `replaceState`. | ||
@param {String} name the name of the route | ||
*/ | ||
replaceWith: function replaceWith() /*name*/{ | ||
return doTransition(this, arguments).method('replace'); | ||
}, | ||
var targetHandler = targetHandlerInfos[targetHandlerInfos.length - 1].name; | ||
var recogHandlers = this.recognizer.handlersFor(targetHandler); | ||
/** | ||
Take a named route and context objects and generate a | ||
URL. | ||
@param {String} name the name of the route to generate | ||
a URL for | ||
@param {...Object} objects a list of objects to serialize | ||
@return {String} a URL | ||
*/ | ||
generate: function generate(handlerName) { | ||
var partitionedArgs = (0, _utils.extractQueryParams)(_utils.slice.call(arguments, 1)), | ||
suppliedParams = partitionedArgs[0], | ||
queryParams = partitionedArgs[1]; | ||
var index = 0; | ||
for (len = recogHandlers.length; index < len; ++index) { | ||
handlerInfo = targetHandlerInfos[index]; | ||
if (handlerInfo.name === handlerName) { | ||
break; | ||
} | ||
} | ||
// Construct a TransitionIntent with the provided params | ||
// and apply it to the present state of the router. | ||
var intent = new _namedTransitionIntent2.default({ | ||
name: handlerName, | ||
contexts: suppliedParams | ||
}); | ||
var state = intent.applyToState(this.state, this.recognizer, this.getHandler, null, this.getSerializer); | ||
var params = {}; | ||
if (index === recogHandlers.length) { | ||
// The provided route name isn't even in the route hierarchy. | ||
return false; | ||
} | ||
for (var i = 0, len = state.handlerInfos.length; i < len; ++i) { | ||
var handlerInfo = state.handlerInfos[i]; | ||
var handlerParams = handlerInfo.serialize(); | ||
(0, _utils.merge)(params, handlerParams); | ||
} | ||
params.queryParams = queryParams; | ||
var testState = new _transitionState2.default(); | ||
testState.handlerInfos = targetHandlerInfos.slice(0, index + 1); | ||
recogHandlers = recogHandlers.slice(0, index + 1); | ||
return this.recognizer.generate(handlerName, params); | ||
}, | ||
var intent = new _namedTransitionIntent2.default({ | ||
name: targetHandler, | ||
contexts: contexts | ||
}); | ||
applyIntent: function applyIntent(handlerName, contexts) { | ||
var intent = new _namedTransitionIntent2.default({ | ||
name: handlerName, | ||
contexts: contexts | ||
}); | ||
var newState = intent.applyToHandlers(testState, recogHandlers, this.getHandler, targetHandler, true, true, this.getSerializer); | ||
var state = this.activeTransition && this.activeTransition.state || this.state; | ||
return intent.applyToState(state, this.recognizer, this.getHandler, null, this.getSerializer); | ||
}, | ||
var handlersEqual = handlerInfosEqual(newState.handlerInfos, testState.handlerInfos); | ||
if (!queryParams || !handlersEqual) { | ||
return handlersEqual; | ||
} | ||
isActiveIntent: function isActiveIntent(handlerName, contexts, queryParams, _state) { | ||
var state = _state || this.state, | ||
targetHandlerInfos = state.handlerInfos, | ||
handlerInfo, | ||
len; | ||
// Get a hash of QPs that will still be active on new route | ||
var activeQPsOnNewHandler = {}; | ||
(0, _utils.merge)(activeQPsOnNewHandler, queryParams); | ||
if (!targetHandlerInfos.length) { | ||
return false; | ||
var activeQueryParams = state.queryParams; | ||
for (var key in activeQueryParams) { | ||
if (activeQueryParams.hasOwnProperty(key) && activeQPsOnNewHandler.hasOwnProperty(key)) { | ||
activeQPsOnNewHandler[key] = activeQueryParams[key]; | ||
} | ||
} | ||
return handlersEqual && !(0, _utils.getChangelist)(activeQPsOnNewHandler, queryParams); | ||
} | ||
}, { | ||
key: 'isActive', | ||
value: function isActive(handlerName) { | ||
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { | ||
args[_key2 - 1] = arguments[_key2]; | ||
} | ||
var targetHandler = targetHandlerInfos[targetHandlerInfos.length - 1].name; | ||
var recogHandlers = this.recognizer.handlersFor(targetHandler); | ||
var index = 0; | ||
for (len = recogHandlers.length; index < len; ++index) { | ||
handlerInfo = targetHandlerInfos[index]; | ||
if (handlerInfo.name === handlerName) { | ||
break; | ||
var partitionedArgs = (0, _utils.extractQueryParams)(args); | ||
return this.isActiveIntent(handlerName, partitionedArgs[0], partitionedArgs[1]); | ||
} | ||
}, { | ||
key: 'trigger', | ||
value: function trigger() { | ||
for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { | ||
args[_key3] = arguments[_key3]; | ||
} | ||
} | ||
if (index === recogHandlers.length) { | ||
// The provided route name isn't even in the route hierarchy. | ||
return false; | ||
(0, _utils.trigger)(this, this.currentHandlerInfos, false, args); | ||
} | ||
}]); | ||
var testState = new _transitionState2.default(); | ||
testState.handlerInfos = targetHandlerInfos.slice(0, index + 1); | ||
recogHandlers = recogHandlers.slice(0, index + 1); | ||
return Router; | ||
}(); | ||
var intent = new _namedTransitionIntent2.default({ | ||
name: targetHandler, | ||
contexts: contexts | ||
}); | ||
function getTransitionByIntent(intent, isIntermediate) { | ||
var wasTransitioning = !!this.activeTransition; | ||
var oldState = wasTransitioning ? this.activeTransition.state : this.state; | ||
var newTransition; | ||
var newState = intent.applyToHandlers(testState, recogHandlers, this.getHandler, targetHandler, true, true, this.getSerializer); | ||
var newState = intent.applyToState(oldState, this.recognizer, this.getHandler, isIntermediate, this.getSerializer); | ||
var queryParamChangelist = (0, _utils.getChangelist)(oldState.queryParams, newState.queryParams); | ||
var handlersEqual = handlerInfosEqual(newState.handlerInfos, testState.handlerInfos); | ||
if (!queryParams || !handlersEqual) { | ||
return handlersEqual; | ||
if (handlerInfosEqual(newState.handlerInfos, oldState.handlerInfos)) { | ||
// This is a no-op transition. See if query params changed. | ||
if (queryParamChangelist) { | ||
newTransition = this.queryParamsTransition(queryParamChangelist, wasTransitioning, oldState, newState); | ||
if (newTransition) { | ||
newTransition.queryParamsOnly = true; | ||
return newTransition; | ||
} | ||
} | ||
// Get a hash of QPs that will still be active on new route | ||
var activeQPsOnNewHandler = {}; | ||
(0, _utils.merge)(activeQPsOnNewHandler, queryParams); | ||
// No-op. No need to create a new transition. | ||
return this.activeTransition || new _transition.Transition(this); | ||
} | ||
var activeQueryParams = state.queryParams; | ||
for (var key in activeQueryParams) { | ||
if (activeQueryParams.hasOwnProperty(key) && activeQPsOnNewHandler.hasOwnProperty(key)) { | ||
activeQPsOnNewHandler[key] = activeQueryParams[key]; | ||
} | ||
} | ||
if (isIntermediate) { | ||
setupContexts(this, newState); | ||
return; | ||
} | ||
return handlersEqual && !(0, _utils.getChangelist)(activeQPsOnNewHandler, queryParams); | ||
}, | ||
// Create a new transition to the destination route. | ||
newTransition = new _transition.Transition(this, intent, newState, undefined, this.activeTransition); | ||
isActive: function isActive(handlerName) { | ||
var partitionedArgs = (0, _utils.extractQueryParams)(_utils.slice.call(arguments, 1)); | ||
return this.isActiveIntent(handlerName, partitionedArgs[0], partitionedArgs[1]); | ||
}, | ||
// transition is to same route with same params, only query params differ. | ||
// not caught above probably because refresh() has been used | ||
if (handlerInfosSameExceptQueryParams(newState.handlerInfos, oldState.handlerInfos)) { | ||
newTransition.queryParamsOnly = true; | ||
} | ||
trigger: function trigger() /*name*/{ | ||
var args = _utils.slice.call(arguments); | ||
(0, _utils.trigger)(this, this.currentHandlerInfos, false, args); | ||
}, | ||
// Abort and usurp any previously active transition. | ||
if (this.activeTransition) { | ||
this.activeTransition.abort(); | ||
} | ||
this.activeTransition = newTransition; | ||
/** | ||
Hook point for logging transition status updates. | ||
@param {String} message The message to log. | ||
*/ | ||
log: null | ||
}; | ||
// Transition promises by default resolve with resolved state. | ||
// For our purposes, swap out the promise to resolve | ||
// after the transition has been finalized. | ||
newTransition.promise = newTransition.promise.then(function (result) { | ||
return finalizeTransition(newTransition, result.state); | ||
}, null, (0, _utils.promiseLabel)('Settle transition promise when transition is finalized')); | ||
if (!wasTransitioning) { | ||
notifyExistingHandlers(this, newState, newTransition); | ||
} | ||
fireQueryParamDidChange(this, newState, queryParamChangelist); | ||
return newTransition; | ||
} | ||
/** | ||
@@ -682,3 +735,11 @@ @private | ||
if (initial || replaceAndNotAborting || isQueryParamsRefreshTransition) { | ||
// say you are at / and you a `replaceWith(/foo)` is called. Then, that | ||
// transition is aborted with `replaceWith(/bar)`. At the end, we should | ||
// end up with /bar replacing /. We are replacing the replace. We only | ||
// will replace the initial route if all subsequent aborts are also | ||
// replaces. However, there is some ambiguity around the correct behavior | ||
// here. | ||
var replacingReplace = urlMethod === 'replace' && transition.isCausedByAbortingReplaceTransition; | ||
if (initial || replaceAndNotAborting || isQueryParamsRefreshTransition || replacingReplace) { | ||
router.replaceURL(url); | ||
@@ -685,0 +746,0 @@ } else { |
@@ -7,2 +7,4 @@ 'use strict'; | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
var _utils = require('./utils'); | ||
@@ -12,98 +14,109 @@ | ||
function TransitionState() { | ||
this.handlerInfos = []; | ||
this.queryParams = {}; | ||
this.params = {}; | ||
} | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
TransitionState.prototype = { | ||
promiseLabel: function promiseLabel(label) { | ||
var targetName = ''; | ||
(0, _utils.forEach)(this.handlerInfos, function (handlerInfo) { | ||
if (targetName !== '') { | ||
targetName += '.'; | ||
} | ||
targetName += handlerInfo.name; | ||
}); | ||
return (0, _utils.promiseLabel)("'" + targetName + "': " + label); | ||
}, | ||
var TransitionState = function () { | ||
function TransitionState() { | ||
_classCallCheck(this, TransitionState); | ||
resolve: function resolve(shouldContinue, payload) { | ||
// First, calculate params for this state. This is useful | ||
// information to provide to the various route hooks. | ||
var params = this.params; | ||
(0, _utils.forEach)(this.handlerInfos, function (handlerInfo) { | ||
params[handlerInfo.name] = handlerInfo.params || {}; | ||
}); | ||
this.handlerInfos = []; | ||
this.queryParams = {}; | ||
this.params = {}; | ||
} | ||
payload = payload || {}; | ||
payload.resolveIndex = 0; | ||
var currentState = this; | ||
var wasAborted = false; | ||
// The prelude RSVP.resolve() asyncs us into the promise land. | ||
return _rsvp.Promise.resolve(null, this.promiseLabel('Start transition')).then(resolveOneHandlerInfo, null, this.promiseLabel('Resolve handler')).catch(handleError, this.promiseLabel('Handle error')); | ||
function innerShouldContinue() { | ||
return _rsvp.Promise.resolve(shouldContinue(), currentState.promiseLabel('Check if should continue')).catch(function (reason) { | ||
// We distinguish between errors that occurred | ||
// during resolution (e.g. before"Model/model/afterModel), | ||
// and aborts due to a rejecting promise from shouldContinue(). | ||
wasAborted = true; | ||
return _rsvp.Promise.reject(reason); | ||
}, currentState.promiseLabel('Handle abort')); | ||
_createClass(TransitionState, [{ | ||
key: 'promiseLabel', | ||
value: function promiseLabel(label) { | ||
var targetName = ''; | ||
(0, _utils.forEach)(this.handlerInfos, function (handlerInfo) { | ||
if (targetName !== '') { | ||
targetName += '.'; | ||
} | ||
targetName += handlerInfo.name; | ||
}); | ||
return (0, _utils.promiseLabel)("'" + targetName + "': " + label); | ||
} | ||
}, { | ||
key: 'resolve', | ||
value: function resolve(shouldContinue) { | ||
var payload = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
function handleError(error) { | ||
// This is the only possible | ||
// reject value of TransitionState#resolve | ||
var handlerInfos = currentState.handlerInfos; | ||
var errorHandlerIndex = payload.resolveIndex >= handlerInfos.length ? handlerInfos.length - 1 : payload.resolveIndex; | ||
return _rsvp.Promise.reject({ | ||
error: error, | ||
handlerWithError: currentState.handlerInfos[errorHandlerIndex].handler, | ||
wasAborted: wasAborted, | ||
state: currentState | ||
// First, calculate params for this state. This is useful | ||
// information to provide to the various route hooks. | ||
var params = this.params; | ||
(0, _utils.forEach)(this.handlerInfos, function (handlerInfo) { | ||
params[handlerInfo.name] = handlerInfo.params || {}; | ||
}); | ||
} | ||
function proceed(resolvedHandlerInfo) { | ||
var wasAlreadyResolved = currentState.handlerInfos[payload.resolveIndex].isResolved; | ||
payload.resolveIndex = 0; | ||
// Swap the previously unresolved handlerInfo with | ||
// the resolved handlerInfo | ||
currentState.handlerInfos[payload.resolveIndex++] = resolvedHandlerInfo; | ||
var currentState = this; | ||
var wasAborted = false; | ||
if (!wasAlreadyResolved) { | ||
// Call the redirect hook. The reason we call it here | ||
// vs. afterModel is so that redirects into child | ||
// routes don't re-run the model hooks for this | ||
// already-resolved route. | ||
var handler = resolvedHandlerInfo.handler; | ||
(0, _utils.callHook)(handler, 'redirect', resolvedHandlerInfo.context, payload); | ||
// The prelude RSVP.resolve() asyncs us into the promise land. | ||
return _rsvp.Promise.resolve(null, this.promiseLabel('Start transition')).then(resolveOneHandlerInfo, null, this.promiseLabel('Resolve handler')).catch(handleError, this.promiseLabel('Handle error')); | ||
function innerShouldContinue() { | ||
return _rsvp.Promise.resolve(shouldContinue(), currentState.promiseLabel('Check if should continue')).catch(function (reason) { | ||
// We distinguish between errors that occurred | ||
// during resolution (e.g. before"Model/model/afterModel), | ||
// and aborts due to a rejecting promise from shouldContinue(). | ||
wasAborted = true; | ||
return _rsvp.Promise.reject(reason); | ||
}, currentState.promiseLabel('Handle abort')); | ||
} | ||
// Proceed after ensuring that the redirect hook | ||
// didn't abort this transition by transitioning elsewhere. | ||
return innerShouldContinue().then(resolveOneHandlerInfo, null, currentState.promiseLabel('Resolve handler')); | ||
} | ||
function resolveOneHandlerInfo() { | ||
if (payload.resolveIndex === currentState.handlerInfos.length) { | ||
// This is is the only possible | ||
// fulfill value of TransitionState#resolve | ||
return { | ||
error: null, | ||
function handleError(error) { | ||
// This is the only possible | ||
// reject value of TransitionState#resolve | ||
var handlerInfos = currentState.handlerInfos; | ||
var errorHandlerIndex = payload.resolveIndex >= handlerInfos.length ? handlerInfos.length - 1 : payload.resolveIndex; | ||
return _rsvp.Promise.reject({ | ||
error: error, | ||
handlerWithError: currentState.handlerInfos[errorHandlerIndex].handler, | ||
wasAborted: wasAborted, | ||
state: currentState | ||
}; | ||
}); | ||
} | ||
var handlerInfo = currentState.handlerInfos[payload.resolveIndex]; | ||
function proceed(resolvedHandlerInfo) { | ||
var wasAlreadyResolved = currentState.handlerInfos[payload.resolveIndex].isResolved; | ||
return handlerInfo.resolve(innerShouldContinue, payload).then(proceed, null, currentState.promiseLabel('Proceed')); | ||
// Swap the previously unresolved handlerInfo with | ||
// the resolved handlerInfo | ||
currentState.handlerInfos[payload.resolveIndex++] = resolvedHandlerInfo; | ||
if (!wasAlreadyResolved) { | ||
// Call the redirect hook. The reason we call it here | ||
// vs. afterModel is so that redirects into child | ||
// routes don't re-run the model hooks for this | ||
// already-resolved route. | ||
var handler = resolvedHandlerInfo.handler; | ||
(0, _utils.callHook)(handler, 'redirect', resolvedHandlerInfo.context, payload); | ||
} | ||
// Proceed after ensuring that the redirect hook | ||
// didn't abort this transition by transitioning elsewhere. | ||
return innerShouldContinue().then(resolveOneHandlerInfo, null, currentState.promiseLabel('Resolve handler')); | ||
} | ||
function resolveOneHandlerInfo() { | ||
if (payload.resolveIndex === currentState.handlerInfos.length) { | ||
// This is is the only possible | ||
// fulfill value of TransitionState#resolve | ||
return { | ||
error: null, | ||
state: currentState | ||
}; | ||
} | ||
var handlerInfo = currentState.handlerInfos[payload.resolveIndex]; | ||
return handlerInfo.resolve(innerShouldContinue, payload).then(proceed, null, currentState.promiseLabel('Proceed')); | ||
} | ||
} | ||
} | ||
}; | ||
}]); | ||
return TransitionState; | ||
}(); | ||
exports.default = TransitionState; |
@@ -83,2 +83,4 @@ 'use strict'; | ||
this.isCausedByInitialTransition = previousTransition && (previousTransition.isCausedByInitialTransition || previousTransition.sequence === 0); | ||
// Every transition in the chain is a replace | ||
this.isCausedByAbortingReplaceTransition = previousTransition && previousTransition.urlMethod == 'replace' && (!previousTransition.isCausedByAbortingTransition || previousTransition.isCausedByAbortingReplaceTransition); | ||
@@ -85,0 +87,0 @@ if (state) { |
@@ -63,7 +63,8 @@ 'use strict'; | ||
for (var key in queryParams) { | ||
if (typeof queryParams[key] === 'number') { | ||
queryParams[key] = '' + queryParams[key]; | ||
} else if (Array.isArray(queryParams[key])) { | ||
for (var i = 0, l = queryParams[key].length; i < l; i++) { | ||
queryParams[key][i] = '' + queryParams[key][i]; | ||
var val = queryParams[key]; | ||
if (typeof val === 'number') { | ||
queryParams[key] = '' + val; | ||
} else if (Array.isArray(val)) { | ||
for (var i = 0, l = val.length; i < l; i++) { | ||
val[i] = '' + val[i]; | ||
} | ||
@@ -188,7 +189,5 @@ } | ||
} | ||
} else { | ||
if (oldObject[key] !== newObject[key]) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
} | ||
} else if (oldObject[key] !== newObject[key]) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
} | ||
@@ -198,3 +197,3 @@ } | ||
return didChange && results; | ||
return didChange ? results : undefined; | ||
} | ||
@@ -201,0 +200,0 @@ |
import { promiseLabel, applyHook, isPromise } from './utils'; | ||
import { Promise } from 'rsvp'; | ||
var DEFAULT_HANDLER = Object.freeze({}); | ||
const DEFAULT_HANDLER = Object.freeze({}); | ||
export default class HandlerInfo { | ||
constructor(_props) { | ||
var props = _props || {}; | ||
constructor(props = {}) { | ||
// initialize local properties to ensure consistent object shape | ||
@@ -28,4 +26,3 @@ this._handler = DEFAULT_HANDLER; | ||
fetchHandler() { | ||
var handler = this.getHandler(this.name); | ||
let handler = this.getHandler(this.name); | ||
return this._processHandler(handler); | ||
@@ -76,8 +73,7 @@ } | ||
resolve(shouldContinue, payload) { | ||
var checkForAbort = this.checkForAbort.bind(this, shouldContinue), | ||
beforeModel = this.runBeforeModelHook.bind(this, payload), | ||
model = this.getModel.bind(this, payload), | ||
afterModel = this.runAfterModelHook.bind(this, payload), | ||
becomeResolved = this.becomeResolved.bind(this, payload), | ||
self = this; | ||
let checkForAbort = this.checkForAbort.bind(this, shouldContinue); | ||
let beforeModel = this.runBeforeModelHook.bind(this, payload); | ||
let model = this.getModel.bind(this, payload); | ||
let afterModel = this.runAfterModelHook.bind(this, payload); | ||
let becomeResolved = this.becomeResolved.bind(this, payload); | ||
@@ -87,32 +83,23 @@ return Promise.resolve( | ||
this.promiseLabel('Start handler') | ||
).then( | ||
function(handler) { | ||
// We nest this chain in case the handlerPromise has an error so that | ||
// we don't have to bubble it through every step | ||
return Promise.resolve(handler) | ||
.then(checkForAbort, null, self.promiseLabel('Check for abort')) | ||
.then(beforeModel, null, self.promiseLabel('Before model')) | ||
.then( | ||
checkForAbort, | ||
null, | ||
self.promiseLabel("Check if aborted during 'beforeModel' hook") | ||
) | ||
.then(model, null, self.promiseLabel('Model')) | ||
.then( | ||
checkForAbort, | ||
null, | ||
self.promiseLabel("Check if aborted in 'model' hook") | ||
) | ||
.then(afterModel, null, self.promiseLabel('After model')) | ||
.then( | ||
checkForAbort, | ||
null, | ||
self.promiseLabel("Check if aborted in 'afterModel' hook") | ||
) | ||
.then(becomeResolved, null, self.promiseLabel('Become resolved')); | ||
}, | ||
function(error) { | ||
throw error; | ||
} | ||
); | ||
) | ||
.then(checkForAbort, null, this.promiseLabel('Check for abort')) | ||
.then(beforeModel, null, this.promiseLabel('Before model')) | ||
.then( | ||
checkForAbort, | ||
null, | ||
this.promiseLabel("Check if aborted during 'beforeModel' hook") | ||
) | ||
.then(model, null, this.promiseLabel('Model')) | ||
.then( | ||
checkForAbort, | ||
null, | ||
this.promiseLabel("Check if aborted in 'model' hook") | ||
) | ||
.then(afterModel, null, this.promiseLabel('After model')) | ||
.then( | ||
checkForAbort, | ||
null, | ||
this.promiseLabel("Check if aborted in 'afterModel' hook") | ||
) | ||
.then(becomeResolved, null, this.promiseLabel('Become resolved')); | ||
} | ||
@@ -131,3 +118,3 @@ | ||
// the resolved model in afterModel. | ||
var name = this.name; | ||
let name = this.name; | ||
this.stashResolvedModel(payload, resolvedModel); | ||
@@ -134,0 +121,0 @@ |
@@ -22,113 +22,26 @@ import RouteRecognizer from 'route-recognizer'; | ||
function Router(_options) { | ||
var options = _options || {}; | ||
this.getHandler = options.getHandler || this.getHandler; | ||
this.getSerializer = options.getSerializer || this.getSerializer; | ||
this.updateURL = options.updateURL || this.updateURL; | ||
this.replaceURL = options.replaceURL || this.replaceURL; | ||
this.didTransition = options.didTransition || this.didTransition; | ||
this.willTransition = options.willTransition || this.willTransition; | ||
this.delegate = options.delegate || this.delegate; | ||
this.triggerEvent = options.triggerEvent || this.triggerEvent; | ||
this.log = options.log || this.log; | ||
this.dslCallBacks = []; // NOTE: set by Ember | ||
this.state = undefined; | ||
this.activeTransition = undefined; | ||
this._changedQueryParams = undefined; | ||
this.oldState = undefined; | ||
this.currentHandlerInfos = undefined; | ||
this.currentSequence = 0; | ||
class Router { | ||
constructor(options = {}) { | ||
this.getHandler = options.getHandler || this.getHandler; | ||
this.getSerializer = options.getSerializer || this.getSerializer; | ||
this.updateURL = options.updateURL || this.updateURL; | ||
this.replaceURL = options.replaceURL || this.replaceURL; | ||
this.didTransition = options.didTransition || this.didTransition; | ||
this.willTransition = options.willTransition || this.willTransition; | ||
this.delegate = options.delegate || this.delegate; | ||
this.triggerEvent = options.triggerEvent || this.triggerEvent; | ||
this.log = options.log || this.log; | ||
this.dslCallBacks = []; // NOTE: set by Ember | ||
this.recognizer = new RouteRecognizer(); | ||
this.reset(); | ||
} | ||
this.state = undefined; | ||
this.activeTransition = undefined; | ||
this._changedQueryParams = undefined; | ||
this.oldState = undefined; | ||
this.currentHandlerInfos = undefined; | ||
this.currentSequence = 0; | ||
function getTransitionByIntent(intent, isIntermediate) { | ||
var wasTransitioning = !!this.activeTransition; | ||
var oldState = wasTransitioning ? this.activeTransition.state : this.state; | ||
var newTransition; | ||
var newState = intent.applyToState( | ||
oldState, | ||
this.recognizer, | ||
this.getHandler, | ||
isIntermediate, | ||
this.getSerializer | ||
); | ||
var queryParamChangelist = getChangelist( | ||
oldState.queryParams, | ||
newState.queryParams | ||
); | ||
if (handlerInfosEqual(newState.handlerInfos, oldState.handlerInfos)) { | ||
// This is a no-op transition. See if query params changed. | ||
if (queryParamChangelist) { | ||
newTransition = this.queryParamsTransition( | ||
queryParamChangelist, | ||
wasTransitioning, | ||
oldState, | ||
newState | ||
); | ||
if (newTransition) { | ||
newTransition.queryParamsOnly = true; | ||
return newTransition; | ||
} | ||
} | ||
// No-op. No need to create a new transition. | ||
return this.activeTransition || new Transition(this); | ||
this.recognizer = new RouteRecognizer(); | ||
this.reset(); | ||
} | ||
if (isIntermediate) { | ||
setupContexts(this, newState); | ||
return; | ||
} | ||
// Create a new transition to the destination route. | ||
newTransition = new Transition( | ||
this, | ||
intent, | ||
newState, | ||
undefined, | ||
this.activeTransition | ||
); | ||
// transition is to same route with same params, only query params differ. | ||
// not caught above probably because refresh() has been used | ||
if ( | ||
handlerInfosSameExceptQueryParams( | ||
newState.handlerInfos, | ||
oldState.handlerInfos | ||
) | ||
) { | ||
newTransition.queryParamsOnly = true; | ||
} | ||
// Abort and usurp any previously active transition. | ||
if (this.activeTransition) { | ||
this.activeTransition.abort(); | ||
} | ||
this.activeTransition = newTransition; | ||
// Transition promises by default resolve with resolved state. | ||
// For our purposes, swap out the promise to resolve | ||
// after the transition has been finalized. | ||
newTransition.promise = newTransition.promise.then( | ||
function(result) { | ||
return finalizeTransition(newTransition, result.state); | ||
}, | ||
null, | ||
promiseLabel('Settle transition promise when transition is finalized') | ||
); | ||
if (!wasTransitioning) { | ||
notifyExistingHandlers(this, newState, newTransition); | ||
} | ||
fireQueryParamDidChange(this, newState, queryParamChangelist); | ||
return newTransition; | ||
} | ||
Router.prototype = { | ||
/** | ||
@@ -143,3 +56,3 @@ The main entry point into the router. The API is essentially | ||
*/ | ||
map: function(callback) { | ||
map(callback) { | ||
this.recognizer.delegate = this.delegate; | ||
@@ -157,19 +70,14 @@ | ||
}); | ||
}, | ||
} | ||
hasRoute: function(route) { | ||
hasRoute(route) { | ||
return this.recognizer.hasRoute(route); | ||
}, | ||
} | ||
getHandler: function() {}, | ||
getHandler() {} | ||
getSerializer: function() {}, | ||
getSerializer() {} | ||
queryParamsTransition: function( | ||
changelist, | ||
wasTransitioning, | ||
oldState, | ||
newState | ||
) { | ||
var router = this; | ||
queryParamsTransition(changelist, wasTransitioning, oldState, newState) { | ||
let router = this; | ||
@@ -190,3 +98,3 @@ fireQueryParamDidChange(this, newState, changelist); | ||
// method (default is replaceState). | ||
var newTransition = new Transition(this); | ||
let newTransition = new Transition(this); | ||
newTransition.queryParamsOnly = true; | ||
@@ -214,3 +122,3 @@ | ||
} | ||
}, | ||
} | ||
@@ -220,3 +128,3 @@ // NOTE: this doesn't really belong here, but here | ||
// handle cyclical deps. | ||
transitionByIntent: function(intent /*, isIntermediate*/) { | ||
transitionByIntent(intent /*, isIntermediate*/) { | ||
try { | ||
@@ -227,3 +135,3 @@ return getTransitionByIntent.apply(this, arguments); | ||
} | ||
}, | ||
} | ||
@@ -235,3 +143,3 @@ /** | ||
*/ | ||
reset: function() { | ||
reset() { | ||
if (this.state) { | ||
@@ -247,6 +155,4 @@ forEach(this.state.handlerInfos.slice().reverse(), function(handlerInfo) { | ||
this.currentHandlerInfos = null; | ||
}, | ||
} | ||
activeTransition: null, | ||
/** | ||
@@ -264,6 +170,6 @@ var handler = handlerInfo.handler; | ||
*/ | ||
handleURL: function(url) { | ||
handleURL(...args) { | ||
// Perform a URL-based transition, but don't change | ||
// the URL afterward, since it already happened. | ||
var args = slice.call(arguments); | ||
let url = args[0]; | ||
if (url.charAt(0) !== '/') { | ||
@@ -274,3 +180,3 @@ args[0] = '/' + url; | ||
return doTransition(this, args).method(null); | ||
}, | ||
} | ||
@@ -282,5 +188,5 @@ /** | ||
*/ | ||
updateURL: function() { | ||
updateURL() { | ||
throw new Error('updateURL is not implemented'); | ||
}, | ||
} | ||
@@ -294,5 +200,5 @@ /** | ||
*/ | ||
replaceURL: function(url) { | ||
replaceURL(url) { | ||
this.updateURL(url); | ||
}, | ||
} | ||
@@ -307,17 +213,17 @@ /** | ||
*/ | ||
transitionTo: function(/*name*/) { | ||
transitionTo(/*name*/) { | ||
return doTransition(this, arguments); | ||
}, | ||
} | ||
intermediateTransitionTo: function(/*name*/) { | ||
intermediateTransitionTo(/*name*/) { | ||
return doTransition(this, arguments, true); | ||
}, | ||
} | ||
refresh: function(pivotHandler) { | ||
var previousTransition = this.activeTransition; | ||
var state = previousTransition ? previousTransition.state : this.state; | ||
var handlerInfos = state.handlerInfos; | ||
refresh(pivotHandler) { | ||
let previousTransition = this.activeTransition; | ||
let state = previousTransition ? previousTransition.state : this.state; | ||
let handlerInfos = state.handlerInfos; | ||
log(this, 'Starting a refresh transition'); | ||
var intent = new NamedTransitionIntent({ | ||
let intent = new NamedTransitionIntent({ | ||
name: handlerInfos[handlerInfos.length - 1].name, | ||
@@ -329,3 +235,3 @@ pivotHandler: pivotHandler || handlerInfos[0].handler, | ||
var newTransition = this.transitionByIntent(intent, false); | ||
let newTransition = this.transitionByIntent(intent, false); | ||
@@ -338,3 +244,3 @@ // if the previous transition is a replace transition, that needs to be preserved | ||
return newTransition; | ||
}, | ||
} | ||
@@ -349,5 +255,5 @@ /** | ||
*/ | ||
replaceWith: function(/*name*/) { | ||
replaceWith(/*name*/) { | ||
return doTransition(this, arguments).method('replace'); | ||
}, | ||
} | ||
@@ -364,4 +270,4 @@ /** | ||
*/ | ||
generate: function(handlerName) { | ||
var partitionedArgs = extractQueryParams(slice.call(arguments, 1)), | ||
generate(handlerName) { | ||
let partitionedArgs = extractQueryParams(slice.call(arguments, 1)), | ||
suppliedParams = partitionedArgs[0], | ||
@@ -372,7 +278,7 @@ queryParams = partitionedArgs[1]; | ||
// and apply it to the present state of the router. | ||
var intent = new NamedTransitionIntent({ | ||
let intent = new NamedTransitionIntent({ | ||
name: handlerName, | ||
contexts: suppliedParams, | ||
}); | ||
var state = intent.applyToState( | ||
let state = intent.applyToState( | ||
this.state, | ||
@@ -384,4 +290,4 @@ this.recognizer, | ||
); | ||
var params = {}; | ||
let params = {}; | ||
for (var i = 0, len = state.handlerInfos.length; i < len; ++i) { | ||
@@ -395,6 +301,6 @@ var handlerInfo = state.handlerInfos[i]; | ||
return this.recognizer.generate(handlerName, params); | ||
}, | ||
} | ||
applyIntent: function(handlerName, contexts) { | ||
var intent = new NamedTransitionIntent({ | ||
applyIntent(handlerName, contexts) { | ||
let intent = new NamedTransitionIntent({ | ||
name: handlerName, | ||
@@ -404,4 +310,5 @@ contexts: contexts, | ||
var state = | ||
let state = | ||
(this.activeTransition && this.activeTransition.state) || this.state; | ||
return intent.applyToState( | ||
@@ -414,6 +321,6 @@ state, | ||
); | ||
}, | ||
} | ||
isActiveIntent: function(handlerName, contexts, queryParams, _state) { | ||
var state = _state || this.state, | ||
isActiveIntent(handlerName, contexts, queryParams, _state) { | ||
let state = _state || this.state, | ||
targetHandlerInfos = state.handlerInfos, | ||
@@ -427,6 +334,6 @@ handlerInfo, | ||
var targetHandler = targetHandlerInfos[targetHandlerInfos.length - 1].name; | ||
var recogHandlers = this.recognizer.handlersFor(targetHandler); | ||
let targetHandler = targetHandlerInfos[targetHandlerInfos.length - 1].name; | ||
let recogHandlers = this.recognizer.handlersFor(targetHandler); | ||
var index = 0; | ||
let index = 0; | ||
for (len = recogHandlers.length; index < len; ++index) { | ||
@@ -444,7 +351,7 @@ handlerInfo = targetHandlerInfos[index]; | ||
var testState = new TransitionState(); | ||
let testState = new TransitionState(); | ||
testState.handlerInfos = targetHandlerInfos.slice(0, index + 1); | ||
recogHandlers = recogHandlers.slice(0, index + 1); | ||
var intent = new NamedTransitionIntent({ | ||
let intent = new NamedTransitionIntent({ | ||
name: targetHandler, | ||
@@ -454,3 +361,3 @@ contexts: contexts, | ||
var newState = intent.applyToHandlers( | ||
let newState = intent.applyToHandlers( | ||
testState, | ||
@@ -465,3 +372,3 @@ recogHandlers, | ||
var handlersEqual = handlerInfosEqual( | ||
let handlersEqual = handlerInfosEqual( | ||
newState.handlerInfos, | ||
@@ -475,6 +382,6 @@ testState.handlerInfos | ||
// Get a hash of QPs that will still be active on new route | ||
var activeQPsOnNewHandler = {}; | ||
let activeQPsOnNewHandler = {}; | ||
merge(activeQPsOnNewHandler, queryParams); | ||
var activeQueryParams = state.queryParams; | ||
let activeQueryParams = state.queryParams; | ||
for (var key in activeQueryParams) { | ||
@@ -490,6 +397,6 @@ if ( | ||
return handlersEqual && !getChangelist(activeQPsOnNewHandler, queryParams); | ||
}, | ||
} | ||
isActive: function(handlerName) { | ||
var partitionedArgs = extractQueryParams(slice.call(arguments, 1)); | ||
isActive(handlerName, ...args) { | ||
let partitionedArgs = extractQueryParams(args); | ||
return this.isActiveIntent( | ||
@@ -500,17 +407,96 @@ handlerName, | ||
); | ||
}, | ||
} | ||
trigger: function(/*name*/) { | ||
var args = slice.call(arguments); | ||
trigger(...args) { | ||
trigger(this, this.currentHandlerInfos, false, args); | ||
}, | ||
} | ||
} | ||
/** | ||
Hook point for logging transition status updates. | ||
function getTransitionByIntent(intent, isIntermediate) { | ||
var wasTransitioning = !!this.activeTransition; | ||
var oldState = wasTransitioning ? this.activeTransition.state : this.state; | ||
var newTransition; | ||
@param {String} message The message to log. | ||
*/ | ||
log: null, | ||
}; | ||
var newState = intent.applyToState( | ||
oldState, | ||
this.recognizer, | ||
this.getHandler, | ||
isIntermediate, | ||
this.getSerializer | ||
); | ||
var queryParamChangelist = getChangelist( | ||
oldState.queryParams, | ||
newState.queryParams | ||
); | ||
if (handlerInfosEqual(newState.handlerInfos, oldState.handlerInfos)) { | ||
// This is a no-op transition. See if query params changed. | ||
if (queryParamChangelist) { | ||
newTransition = this.queryParamsTransition( | ||
queryParamChangelist, | ||
wasTransitioning, | ||
oldState, | ||
newState | ||
); | ||
if (newTransition) { | ||
newTransition.queryParamsOnly = true; | ||
return newTransition; | ||
} | ||
} | ||
// No-op. No need to create a new transition. | ||
return this.activeTransition || new Transition(this); | ||
} | ||
if (isIntermediate) { | ||
setupContexts(this, newState); | ||
return; | ||
} | ||
// Create a new transition to the destination route. | ||
newTransition = new Transition( | ||
this, | ||
intent, | ||
newState, | ||
undefined, | ||
this.activeTransition | ||
); | ||
// transition is to same route with same params, only query params differ. | ||
// not caught above probably because refresh() has been used | ||
if ( | ||
handlerInfosSameExceptQueryParams( | ||
newState.handlerInfos, | ||
oldState.handlerInfos | ||
) | ||
) { | ||
newTransition.queryParamsOnly = true; | ||
} | ||
// Abort and usurp any previously active transition. | ||
if (this.activeTransition) { | ||
this.activeTransition.abort(); | ||
} | ||
this.activeTransition = newTransition; | ||
// Transition promises by default resolve with resolved state. | ||
// For our purposes, swap out the promise to resolve | ||
// after the transition has been finalized. | ||
newTransition.promise = newTransition.promise.then( | ||
function(result) { | ||
return finalizeTransition(newTransition, result.state); | ||
}, | ||
null, | ||
promiseLabel('Settle transition promise when transition is finalized') | ||
); | ||
if (!wasTransitioning) { | ||
notifyExistingHandlers(this, newState, newTransition); | ||
} | ||
fireQueryParamDidChange(this, newState, queryParamChangelist); | ||
return newTransition; | ||
} | ||
/** | ||
@@ -816,3 +802,17 @@ @private | ||
if (initial || replaceAndNotAborting || isQueryParamsRefreshTransition) { | ||
// say you are at / and you a `replaceWith(/foo)` is called. Then, that | ||
// transition is aborted with `replaceWith(/bar)`. At the end, we should | ||
// end up with /bar replacing /. We are replacing the replace. We only | ||
// will replace the initial route if all subsequent aborts are also | ||
// replaces. However, there is some ambiguity around the correct behavior | ||
// here. | ||
var replacingReplace = | ||
urlMethod === 'replace' && transition.isCausedByAbortingReplaceTransition; | ||
if ( | ||
initial || | ||
replaceAndNotAborting || | ||
isQueryParamsRefreshTransition || | ||
replacingReplace | ||
) { | ||
router.replaceURL(url); | ||
@@ -819,0 +819,0 @@ } else { |
import { forEach, promiseLabel, callHook } from './utils'; | ||
import { Promise } from 'rsvp'; | ||
function TransitionState() { | ||
this.handlerInfos = []; | ||
this.queryParams = {}; | ||
this.params = {}; | ||
} | ||
export default class TransitionState { | ||
constructor() { | ||
this.handlerInfos = []; | ||
this.queryParams = {}; | ||
this.params = {}; | ||
} | ||
TransitionState.prototype = { | ||
promiseLabel: function(label) { | ||
var targetName = ''; | ||
promiseLabel(label) { | ||
let targetName = ''; | ||
forEach(this.handlerInfos, function(handlerInfo) { | ||
@@ -20,8 +20,8 @@ if (targetName !== '') { | ||
return promiseLabel("'" + targetName + "': " + label); | ||
}, | ||
} | ||
resolve: function(shouldContinue, payload) { | ||
resolve(shouldContinue, payload = {}) { | ||
// First, calculate params for this state. This is useful | ||
// information to provide to the various route hooks. | ||
var params = this.params; | ||
let params = this.params; | ||
forEach(this.handlerInfos, function(handlerInfo) { | ||
@@ -31,7 +31,6 @@ params[handlerInfo.name] = handlerInfo.params || {}; | ||
payload = payload || {}; | ||
payload.resolveIndex = 0; | ||
var currentState = this; | ||
var wasAborted = false; | ||
let currentState = this; | ||
let wasAborted = false; | ||
@@ -59,4 +58,4 @@ // The prelude RSVP.resolve() asyncs us into the promise land. | ||
// reject value of TransitionState#resolve | ||
var handlerInfos = currentState.handlerInfos; | ||
var errorHandlerIndex = | ||
let handlerInfos = currentState.handlerInfos; | ||
let errorHandlerIndex = | ||
payload.resolveIndex >= handlerInfos.length | ||
@@ -74,3 +73,3 @@ ? handlerInfos.length - 1 | ||
function proceed(resolvedHandlerInfo) { | ||
var wasAlreadyResolved = | ||
let wasAlreadyResolved = | ||
currentState.handlerInfos[payload.resolveIndex].isResolved; | ||
@@ -87,3 +86,3 @@ | ||
// already-resolved route. | ||
var handler = resolvedHandlerInfo.handler; | ||
let handler = resolvedHandlerInfo.handler; | ||
callHook(handler, 'redirect', resolvedHandlerInfo.context, payload); | ||
@@ -111,3 +110,3 @@ } | ||
var handlerInfo = currentState.handlerInfos[payload.resolveIndex]; | ||
let handlerInfo = currentState.handlerInfos[payload.resolveIndex]; | ||
@@ -118,5 +117,3 @@ return handlerInfo | ||
} | ||
}, | ||
}; | ||
export default TransitionState; | ||
} | ||
} |
@@ -59,2 +59,8 @@ import { Promise } from 'rsvp'; | ||
previousTransition.sequence === 0); | ||
// Every transition in the chain is a replace | ||
this.isCausedByAbortingReplaceTransition = | ||
previousTransition && | ||
(previousTransition.urlMethod == 'replace' && | ||
(!previousTransition.isCausedByAbortingTransition || | ||
previousTransition.isCausedByAbortingReplaceTransition)); | ||
@@ -61,0 +67,0 @@ if (state) { |
@@ -53,7 +53,8 @@ export const slice = Array.prototype.slice; | ||
for (let key in queryParams) { | ||
if (typeof queryParams[key] === 'number') { | ||
queryParams[key] = '' + queryParams[key]; | ||
} else if (Array.isArray(queryParams[key])) { | ||
for (let i = 0, l = queryParams[key].length; i < l; i++) { | ||
queryParams[key][i] = '' + queryParams[key][i]; | ||
let val = queryParams[key]; | ||
if (typeof val === 'number') { | ||
queryParams[key] = '' + val; | ||
} else if (Array.isArray(val)) { | ||
for (let i = 0, l = val.length; i < l; i++) { | ||
val[i] = '' + val[i]; | ||
} | ||
@@ -189,7 +190,5 @@ } | ||
} | ||
} else { | ||
if (oldObject[key] !== newObject[key]) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
} | ||
} else if (oldObject[key] !== newObject[key]) { | ||
results.changed[key] = newObject[key]; | ||
didChange = true; | ||
} | ||
@@ -199,3 +198,3 @@ } | ||
return didChange && results; | ||
return didChange ? results : undefined; | ||
} | ||
@@ -202,0 +201,0 @@ |
{ | ||
"name": "router_js", | ||
"version": "2.0.0-beta.1", | ||
"version": "2.0.0-beta.2", | ||
"description": "A lightweight JavaScript library is built on top of route-recognizer and rsvp.js to provide an API for handling routes", | ||
@@ -32,4 +32,4 @@ "keywords": [ | ||
"devDependencies": { | ||
"babel-preset-env": "^1.6.0", | ||
"backburner.js": "^1.2.0", | ||
"babel-preset-env": "^1.6.1", | ||
"backburner.js": "^2.1.0", | ||
"broccoli-babel-transpiler": "^6.1.2", | ||
@@ -40,14 +40,14 @@ "broccoli-concat": "^3.2.2", | ||
"broccoli-merge-trees": "^2.0.0", | ||
"ember-cli": "2.15.0", | ||
"eslint-config-prettier": "^2.4.0", | ||
"eslint-plugin-prettier": "^2.2.0", | ||
"ember-cli": "2.18.1", | ||
"eslint-config-prettier": "2.9.0", | ||
"eslint-plugin-prettier": "2.6.0", | ||
"loader.js": "^4.6.0", | ||
"prettier": "^1.6.1", | ||
"qunitjs": "^2.4.0", | ||
"route-recognizer": "^0.3.1", | ||
"rsvp": "^4.0.1" | ||
"prettier": "1.10.2", | ||
"qunit": "^2.5.0", | ||
"route-recognizer": "^0.3.3", | ||
"rsvp": "^4.8.1" | ||
}, | ||
"peerDependencies": { | ||
"route-recognizer": "^0.3.0", | ||
"rsvp": "^3.0.0" | ||
"route-recognizer": "^0.3.3", | ||
"rsvp": "^4.8.1" | ||
}, | ||
@@ -54,0 +54,0 @@ "engines": { |
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
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
184869
4301
0